Skip to content

fix(agglayer): enforce NoteType::Public for B2AGG bridge-out notes (#2988)#3005

Merged
mmagician merged 4 commits into
nextfrom
ajl-claude/cherry-pick-active-note-is-public-private
May 29, 2026
Merged

fix(agglayer): enforce NoteType::Public for B2AGG bridge-out notes (#2988)#3005
mmagician merged 4 commits into
nextfrom
ajl-claude/cherry-pick-active-note-is-public-private

Conversation

@partylikeits1983
Copy link
Copy Markdown
Contributor

@partylikeits1983 partylikeits1983 commented May 28, 2026

Cherry-picks #2988 (merged to agglayer) back to next.

The B2AGG note type lives in NoteMetadata, not in the recipient commitment, so an attacker could submit a B2AGG note with an identical recipient/attachment/asset but NoteType::Private. The bridge would accept it, fold the leaf into the on-chain Local Exit Tree, but aggkit could never recover the pre-image off-chain — the LET mirror would permanently desync and the bridge-out path would brick for everyone.

This PR:

  • Adds active_note::is_public and active_note::is_private to the protocol (built on note::metadata_into_note_type from #2738).
  • Asserts active_note::is_public at the top of bridge::bridge_out::bridge_out with ERR_B2AGG_NOTE_MUST_BE_PUBLIC.
  • Parameterises the bridge-out integration test into test_bridge_out_rejects_invalid_b2agg_note covering both the DestinationIsMiden and PrivateNoteType cases.

note::metadata_into_note_type (from #2738) is reused instead of porting note::extract_note_type_from_metadata from #2988 — the two procs decode different bit positions because next and agglayer lay out the metadata header differently, and metadata_into_note_type is the correct primitive for next.

@partylikeits1983 partylikeits1983 requested review from PhilippGackstatter and mmagician and removed request for PhilippGackstatter May 28, 2026 15:52
@partylikeits1983 partylikeits1983 marked this pull request as ready for review May 28, 2026 15:53
@partylikeits1983 partylikeits1983 changed the title feat(protocol): add active_note::is_public / is_private (#2988) feat(protocol): add active_note::is_public / is_private May 28, 2026
@partylikeits1983 partylikeits1983 self-assigned this May 28, 2026
Copy link
Copy Markdown
Collaborator

@mmagician mmagician left a comment

Choose a reason for hiding this comment

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

I'm not sure I fully understand: it says it intentionally skips agglayer-specific changes, but the original issue was meant to solve a concrete agglayer bug.

So is this only a first in the series of PRs? i.e. the first adds the protocol-level changes, the second applies it to AggLayer contracts?

Cherry-pick the reusable protocol-level building blocks from #2988,
which was merged to the agglayer branch as an agglayer-specific
B2AGG bridge-out hardening:

  - active_note::is_public
  - active_note::is_private

Both procs read the active note's metadata and decode its note
type via the existing note::metadata_into_note_type primitive
(from #2738), then compare against NOTE_TYPE_PUBLIC /
NOTE_TYPE_PRIVATE from miden::protocol::util::note. A parameterized
kernel test covers the Public and Private cases.

The agglayer-only files from #2988 (bridge_out.masm, B2AGG.masm,
tests/agglayer/bridge_out.rs) and the constant-value flip the PR
applied on agglayer's NoteType encoding are intentionally not
brought along: next already encodes Private=0, Public=1, and the
existing metadata_into_note_type is tailored to next's metadata
bit layout.

Co-Authored-By: Claude (Opus) <noreply@anthropic.com>
@partylikeits1983 partylikeits1983 marked this pull request as draft May 28, 2026 17:40
@partylikeits1983 partylikeits1983 force-pushed the ajl-claude/cherry-pick-active-note-is-public-private branch from 217efd5 to 7b8de8d Compare May 28, 2026 17:49
@partylikeits1983 partylikeits1983 marked this pull request as ready for review May 28, 2026 17:49
@partylikeits1983 partylikeits1983 changed the title feat(protocol): add active_note::is_public / is_private fix(agglayer): enforce NoteType::Public for B2AGG bridge-out notes (#2988) May 28, 2026
@partylikeits1983 partylikeits1983 force-pushed the ajl-claude/cherry-pick-active-note-is-public-private branch 2 times, most recently from 51bb95d to c43fab9 Compare May 28, 2026 18:01
…2988)

Cherry-pick the bridge-side half of #2988 to next. The B2AGG note
type lives in NoteMetadata, not in the recipient commitment, so an
attacker could submit a note with an identical recipient,
attachment, and asset but NoteType::Private. consensus would accept
it, the leaf would be folded into the on-chain Local Exit Tree, but
aggkit could never recover the pre-image off-chain — the LET mirror
would permanently desync and the bridge-out path would brick.

Add an active_note::is_public assert at the top of bridge_out,
extend the bridge_out and b2agg.masm doc comments, and parameterize
the existing destination-network-is-Miden test into
test_bridge_out_rejects_invalid_b2agg_note covering the
recipient-identical private-note case too.

Co-Authored-By: Claude (Opus) <noreply@anthropic.com>
@partylikeits1983 partylikeits1983 force-pushed the ajl-claude/cherry-pick-active-note-is-public-private branch from c43fab9 to 162cc26 Compare May 28, 2026 18:07
Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter 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!

Comment thread crates/miden-testing/tests/agglayer/bridge_out.rs Outdated
Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com>
@mmagician mmagician enabled auto-merge May 29, 2026 10:31
@mmagician mmagician added this pull request to the merge queue May 29, 2026
Merged via the queue into next with commit 24dcb9b May 29, 2026
20 checks passed
@mmagician mmagician deleted the ajl-claude/cherry-pick-active-note-is-public-private branch May 29, 2026 10:49
mmagician added a commit that referenced this pull request May 29, 2026
…2988) (#3005)

* feat(protocol): add active_note::is_public / is_private (#2988)

Cherry-pick the reusable protocol-level building blocks from #2988,
which was merged to the agglayer branch as an agglayer-specific
B2AGG bridge-out hardening:

  - active_note::is_public
  - active_note::is_private

Both procs read the active note's metadata and decode its note
type via the existing note::metadata_into_note_type primitive
(from #2738), then compare against NOTE_TYPE_PUBLIC /
NOTE_TYPE_PRIVATE from miden::protocol::util::note. A parameterized
kernel test covers the Public and Private cases.

The agglayer-only files from #2988 (bridge_out.masm, B2AGG.masm,
tests/agglayer/bridge_out.rs) and the constant-value flip the PR
applied on agglayer's NoteType encoding are intentionally not
brought along: next already encodes Private=0, Public=1, and the
existing metadata_into_note_type is tailored to next's metadata
bit layout.

Co-Authored-By: Claude (Opus) <noreply@anthropic.com>

* fix(agglayer): enforce NoteType::Public for B2AGG bridge-out notes (#2988)

Cherry-pick the bridge-side half of #2988 to next. The B2AGG note
type lives in NoteMetadata, not in the recipient commitment, so an
attacker could submit a note with an identical recipient,
attachment, and asset but NoteType::Private. consensus would accept
it, the leaf would be folded into the on-chain Local Exit Tree, but
aggkit could never recover the pre-image off-chain — the LET mirror
would permanently desync and the bridge-out path would brick.

Add an active_note::is_public assert at the top of bridge_out,
extend the bridge_out and b2agg.masm doc comments, and parameterize
the existing destination-network-is-Miden test into
test_bridge_out_rejects_invalid_b2agg_note covering the
recipient-identical private-note case too.

Co-Authored-By: Claude (Opus) <noreply@anthropic.com>

* Apply suggestions from code review

Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com>

* chore: fmt files

---------

Co-authored-by: Claude (Opus) <noreply@anthropic.com>
Co-authored-by: Marti <marti@miden.team>
Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants