Skip to content

feat(agglayer): add outbound message bridging (leafType=1)#3012

Draft
Dominik1999 wants to merge 17 commits into
nextfrom
feat/message-bridging
Draft

feat(agglayer): add outbound message bridging (leafType=1)#3012
Dominik1999 wants to merge 17 commits into
nextfrom
feat/message-bridging

Conversation

@Dominik1999

@Dominik1999 Dominik1999 commented May 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds bridge_message procedure to the AggLayer bridge for sending arbitrary cross-chain messages from Miden to Ethereum/other chains
  • Creates leafType=1 leaves in the Local Exit Tree (mirrors Solidity's bridgeMessage())
  • No asset burn/lock involved — pure message passing with amount=0

Closes #2697

Changes

  • eth_address.masm: Add from_account_id (inverse of to_account_id) for deriving origin address from note sender
  • bridge_out.masm: Add bridge_message public procedure — populates leaf data with leafType=1, derives origin from sender, appends to LET
  • bridge.masm component: Re-export bridge_message
  • bridge_message.masm note script: Entry point for message bridging (reclaim + bridge paths)
  • message_note.rs: MessageNote Rust type with create() builder
  • bridge.rs: Register BRIDGE_MESSAGE in allowed_notes()

Scope

Outbound only (Miden → other chains). Inbound message processing (claimMessage on Miden side) is deferred.

Follow-up: message + value transfer (bridge_message_with_value)

This PR enables pure messages (amount=0). The next step is combining assets and messages in a single leaf, which would enable use cases like:

  • Depositing into Aave from Miden — send USDC + deposit() calldata in one atomic operation
  • Cross-chain contract calls with value — any DeFi interaction that requires both tokens and calldata

This mirrors Solidity's bridgeMessageWETH(). The leaf format already has the amount field (hardcoded to 0 in this PR), so the extension is additive: accept an asset + metadata_hash, do the faucet lookup + burn/lock (reuse from bridge_out), set the scaled amount in the leaf, and append as leafType=1.

Inbound message processing (receiving messages from Ethereum on Miden) is a separate follow-up.

Test plan

9 integration tests covering:

  • Basic LET update (num_leaves, root, no output notes, empty vault)
  • Leaf hash verification (on-chain Keccak matches independent Rust computation)
  • MASM hash pipeline unit test
  • from_account_id MASM↔Rust conversion verification
  • 3 consecutive messages with correct LET evolution
  • Destination == MIDEN_NETWORK_ID rejected
  • Non-target account cannot consume
  • Different metadata hashes produce different roots
  • Sender reclaim path is no-op
  • All 70 agglayer tests pass (zero regressions)

Add the inverse of to_account_id for converting AccountId [suffix, prefix]
into Ethereum address format [limb0, limb1, limb2, limb3, limb4]. This is
needed by the bridge_message procedure to derive the origin Ethereum address
from the note sender's AccountId.
Adds a new public procedure that creates a leafType=1 leaf in the Local
Exit Tree for outbound message bridging. Unlike bridge_out (asset transfers),
this procedure does not involve faucet lookup, burn, or lock operations.
The origin address is derived from the note sender via eth_address::from_account_id
and the amount is always zero.
Create message_note.rs following the B2AggNote pattern but for asset-less
message bridging. The note carries destination_network, destination_address,
and metadata_hash in its storage (14 felts total) with no assets attached.
Fix stack depth bug in bridge_message procedure: within a `call` frame,
the stack floor is 16 elements, so direct consumption of input values via
mem_store cannot reduce depth below 16. Restructured to save inputs to
locals first, then reload above the floor for memory operations.

The test verifies:
- LET num_leaves increments by 1 after consuming a MessageNote
- Local exit root becomes non-zero
- No output notes are produced (messages don't create BURN notes)
- Bridge vault remains empty (no assets involved)
…twork

The origin_network field must be stored in LE byte order in the leaf data
memory layout (matching how bridge_out stores it via convert_asset). Without
the swap, the Keccak leaf hash would not match Solidity's computation.
…aim + leaf hash tests

- get_sender already returns [suffix, prefix], matching from_account_id's expected input
- Add bridge_message_reclaim_is_noop test (passing)
- Add bridge_message_leaf_hash_matches_independent_computation test (ignored pending
  MASM instrumentation to diagnose keccak input byte mismatch)
… leaf hash test

u32split returns [lo, hi] (lo on top), not [hi, lo]. Added swap after each
u32split to get hi on top before byte-swapping. This caused the origin address
limbs to be swapped within each 8-byte word, producing incorrect leaf hashes.

Also adds:
- test_from_account_id_matches_rust: verifies MASM matches Rust conversion
- bridge_message_leaf_hash_masm_unit_test: verifies hash pipeline in isolation
- Un-ignores leaf hash comparison test (now passes)
…ry offsets

Replace raw numeric indices (loc_store.0, loc_load.13, etc.) with named
BRIDGE_MSG_*_LOC constants, matching the established convention used by
bridge_out, create_burn_note, and unlock_and_send procedures.
…ature

Add input (suffix: felt, prefix: felt) and return type (EthereumAddressFormat)
to match the convention used by compute_leaf_value, verify_merkle_proof, and
other typed public procedures in the codebase.
… validate no assets

- Replace reused ERR_B2AGG_* errors with ERR_BRIDGE_MSG_NOTE_MUST_BE_PUBLIC and
  ERR_BRIDGE_MSG_DESTINATION_NETWORK_IS_MIDEN so error messages correctly identify
  the BRIDGE_MESSAGE note, not B2AGG
- Fix get_sender stack comment in note script: returns [suffix, prefix] not
  [prefix, suffix]
- Add assertz on asset count to prevent silent asset loss if a BRIDGE_MESSAGE
  note is manually constructed with assets
…MASM unit tests

- Note script: replace 14 individual mem_load instructions with 4 idiomatic
  padw + mem_loadw_le word loads (matching b2agg.masm pattern)
- message_note.rs: document LE byte-swap convention in build_note_storage
- Move test_from_account_id_matches_rust to solidity_miden_address_conversion.rs
- Move compute_leaf_value message-type test to leaf_utils.rs
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.

AggLayer: Add message bridging

1 participant