Skip to content

fix: Reject circular note dependencies at transaction, batch and block level#2993

Merged
PhilippGackstatter merged 11 commits into
nextfrom
pgackst-fix-circular-note-dep
May 28, 2026
Merged

fix: Reject circular note dependencies at transaction, batch and block level#2993
PhilippGackstatter merged 11 commits into
nextfrom
pgackst-fix-circular-note-dep

Conversation

@PhilippGackstatter
Copy link
Copy Markdown
Contributor

Background

Issue #2796 describes an asset-fabrication path that works because unauthenticated input notes are credited to the consuming account's vault without any provenance check. A user can wrap a forged asset in a self-directed note and consume that note as an unauthenticated input; the kernel adds the asset to the vault, and the transaction's asset-preservation rules are satisfied as long as the note is balanced on the output side.

The structural enabler is a circular dependency between input and output notes that gets erased before the fabricated asset ever has to match any real on-chain state. This PR refactors InputOutputNoteTracker to reject those note cycles.

Cycles at three levels

The cycle that lets a forged asset round-trip in and out of an account's vault can be set up at the transaction, batch, or block layer. Each level uses the same trick (an unauthenticated input note matches an output note, so both sides get erased). Consequently, we need to fix all of them.

Changes

  • Removes InputOutputNoteTracker and replaces it with functions since everything happens in a single function now.
  • Adds a check at ProvenTransaction level that input and output notes sets are disjoint. We should probably add this check in the transaction kernel, but I didn't want to go quite that far in this PR, since that isn't straightforward.
  • Require that transactions are provided in order and reject any unauthenticated input note that is consumed before it is created in the same batch or block. See also the comments in the code for details.
  • I want to point out the choices made regarding whether note erasure or note authentication runs first, since these influence each other. Previously note authentication ran first, which meant that an unauthenticated note for which a proof was provided and which could have been erased, wasn't. This is an edge case, but now note erasure runs first and I don't think it has any negative consequences.
  • Transactions and batches MUST be provided in order now.
  • Notes created and consumed by the same transaction and batch are no longer erased, since this is a form of circular dependency.
  • Adds tests for all three of the mentioned scenarios of circular dependencies.

Unrelated Changes

closes #2796
closes #1768

@PhilippGackstatter PhilippGackstatter added the pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority label May 27, 2026
@PhilippGackstatter PhilippGackstatter force-pushed the pgackst-fix-circular-note-dep branch from 4d690a6 to 3fcc765 Compare May 27, 2026 13:29
@PhilippGackstatter PhilippGackstatter marked this pull request as ready for review May 27, 2026 16:01
Copy link
Copy Markdown
Contributor

@bobbinth bobbinth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thank you! I left a few small comments inline.

Comment thread crates/miden-protocol/src/batch/input_output_note_tracker.rs Outdated
Comment thread crates/miden-protocol/src/batch/input_output_note_tracker.rs Outdated
Comment thread crates/miden-protocol/src/batch/input_output_note_tracker.rs Outdated
Comment on lines +182 to +183
// Skip inserting the erased note into the input notes set.
continue 'input_note_iter;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: is the loop label really needed here? Seems like we are applying the continue to the inner-most loop anyways.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not, I mainly added it for extra clarity. No strong opinion on keeping or removing it. If other reviewers have a preference, please comment.

Comment thread crates/miden-protocol/src/batch/input_output_note_tracker.rs Outdated
Comment thread crates/miden-protocol/src/batch/input_output_note_tracker.rs Outdated
Comment thread crates/miden-protocol/src/batch/input_output_note_tracker.rs Outdated
Comment thread crates/miden-protocol/src/batch/input_output_note_tracker.rs Outdated
if output_notes.iter().any(|output_note| output_note.id() == input_note.id()) {
return Err(ProvenTransactionError::NoteConsumedAndCreated(input_note.id()));
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to confirm: this is the same condition that is enforced by erase_notes_inner (and will be later encoded in the batch kernel).
So this is just a sanity check, rather than enforcement of anything?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not just a sanity check, it is important logic that the batch relies on. Note that the note tracker only asserts that a given container doesn't have the same note in its input and output note set, it doesn't return an error. So in other words, it assumes this condition is upheld.

Previously, I returned an error for this, but this felt odd: A transaction can and should itself ensure that this condition is upheld, so checking it at the batch level felt wrong. Hence, only an assertion ala "this should never happen". I think we should even move this check to the tx kernel, because right now this is only enforced at the Rust API level, but really the tx kernel should ensure this condition.

Comment thread crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs
Copy link
Copy Markdown
Contributor

@Mirko-von-Leipzig Mirko-von-Leipzig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code is probably good; I would personally prefer it be easier to read. So treat this as an approve.

Comment thread CHANGELOG.md
Comment thread crates/miden-protocol/src/transaction/proven_tx.rs Outdated
Comment thread crates/miden-protocol/src/errors/mod.rs Outdated
Comment thread crates/miden-protocol/src/batch/input_output_note_tracker.rs Outdated
Comment thread crates/miden-protocol/src/batch/note_tracker.rs
Comment thread crates/miden-protocol/src/batch/note_tracker.rs
@PhilippGackstatter PhilippGackstatter added this pull request to the merge queue May 28, 2026
Merged via the queue into next with commit 75df7c5 May 28, 2026
20 checks passed
@PhilippGackstatter PhilippGackstatter deleted the pgackst-fix-circular-note-dep branch May 28, 2026 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unauthenticated input notes let any account fabricate NFTs at the vault layer Disallow transactions with note cycles in batches

4 participants