Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,12 @@ build-no-std-testing: ## Build without the standard library. Includes the `testi

.PHONY: generate-solidity-test-vectors
generate-solidity-test-vectors: ## Regenerate Solidity test vectors using Foundry
cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateVectors
cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateCanonicalZeros
cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateVerificationProofData
cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateLeafValueVectors
cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateClaimAssetVectors
cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateLeafValueVectors
cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateVerificationProofData
cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generate_MTF_vectors
cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateExitRootVectors

# --- benchmarking --------------------------------------------------------------------------------

Expand Down
4 changes: 2 additions & 2 deletions bin/bench-transaction/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ async fn main() -> Result<()> {
run_scenario(ExecutionBenchmark::CreateSingleP2ID, tx_create_single_p2id_note()?).await?,
run_scenario(
ExecutionBenchmark::ConsumeClaimNoteL1ToMiden,
tx_consume_claim_note(ClaimDataSource::SimulatedL1ToMiden).await?,
tx_consume_claim_note(ClaimDataSource::L1ToMiden).await?,
)
.await?,
run_scenario(
ExecutionBenchmark::ConsumeClaimNoteL2ToMiden,
tx_consume_claim_note(ClaimDataSource::SimulatedL2ToMiden).await?,
tx_consume_claim_note(ClaimDataSource::L2ToMiden).await?,
)
.await?,
run_scenario(ExecutionBenchmark::ConsumeB2AggNote, tx_consume_b2agg_note().await?).await?,
Expand Down
12 changes: 4 additions & 8 deletions bin/bench-transaction/src/time_counting_benchmarks/prove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ fn core_benchmarks(c: &mut Criterion) {
});

execute_group.bench_function(BENCH_EXECUTE_TX_CONSUME_CLAIM_L1, |b| {
bench_async_execute(b, || tx_consume_claim_note(ClaimDataSource::SimulatedL1ToMiden));
bench_async_execute(b, || tx_consume_claim_note(ClaimDataSource::L1ToMiden));
});

execute_group.bench_function(BENCH_EXECUTE_TX_CONSUME_CLAIM_L2, |b| {
bench_async_execute(b, || tx_consume_claim_note(ClaimDataSource::SimulatedL2ToMiden));
bench_async_execute(b, || tx_consume_claim_note(ClaimDataSource::L2ToMiden));
});

execute_group.bench_function(BENCH_EXECUTE_TX_CONSUME_B2AGG, |b| {
Expand Down Expand Up @@ -162,15 +162,11 @@ fn core_benchmarks(c: &mut Criterion) {
});

execute_and_prove_group.bench_function(BENCH_EXECUTE_AND_PROVE_TX_CONSUME_CLAIM_L1, |b| {
bench_async_execute_and_prove(b, || {
tx_consume_claim_note(ClaimDataSource::SimulatedL1ToMiden)
});
bench_async_execute_and_prove(b, || tx_consume_claim_note(ClaimDataSource::L1ToMiden));
});

execute_and_prove_group.bench_function(BENCH_EXECUTE_AND_PROVE_TX_CONSUME_CLAIM_L2, |b| {
bench_async_execute_and_prove(b, || {
tx_consume_claim_note(ClaimDataSource::SimulatedL2ToMiden)
});
bench_async_execute_and_prove(b, || tx_consume_claim_note(ClaimDataSource::L2ToMiden));
});

execute_and_prove_group.bench_function(BENCH_EXECUTE_AND_PROVE_TX_CONSUME_B2AGG, |b| {
Expand Down
21 changes: 12 additions & 9 deletions crates/miden-agglayer/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ The crate `miden-agglayer` implements the AggLayer bridging protocol on the Mide
A user initiates a bridge-out by creating a [`B2AGG`](#41-b2agg) note containing a single fungible
asset and the destination network/address. The bridge account consumes this note:

1. Validates that the asset's faucet is registered in the faucet registry.
1. Validates that the asset's faucet is registered in the faucet registry, and that the
destination network is not Miden's AggLayer network ID.
2. FPIs to the faucet (`agglayer_faucet::asset_to_origin_asset`) to obtain the scaled
U256 amount, origin token address, and origin network.
3. FPIs to the faucet (`agglayer_faucet::get_metadata_hash`) to obtain the metadata hash.
Expand Down Expand Up @@ -79,8 +80,8 @@ The `CLAIM` note is consumed by the bridge account:
The faucet consumes the `MINT` note, mints the specified amount, and creates a [`P2ID`](#46-p2id-generated) note
that delivers the minted assets to the recipient's Miden account.

TODO: Destination network from the leaf data is not validated against Miden's own network
ID ([#2698](https://github.com/0xMiden/protocol/issues/2698)).
Inside `bridge_in::claim`, immediately after proof and leaf data are piped into memory, the bridge asserts the leaf's `destination_network` equals the global MASM constant `MIDEN_NETWORK_ID` in `asm/agglayer/common/constants.masm` (after `swap_u32_bytes` on the LE-packed memory limb). The same value is exposed to Rust as `AggLayerBridge::MIDEN_NETWORK_ID`, matching Solidity test vectors.
This mirrors Solidity `claimAsset` destination-network checks.

TODO: The leaf type field is not validated to be `LEAF_TYPE_ASSET` (0)
([#2699](https://github.com/0xMiden/protocol/issues/2699)).
Expand Down Expand Up @@ -223,12 +224,14 @@ Asserts the note sender matches the GER manager stored in
| **Inputs** | `[PROOF_DATA_KEY, LEAF_DATA_KEY, faucet_mint_amount, pad(7)]` on the operand stack; proof data and leaf data in the advice map keyed by `PROOF_DATA_KEY` and `LEAF_DATA_KEY` respectively |
| **Outputs** | `[pad(16)]` |
| **Context** | Consuming a `CLAIM` note on the bridge account |
| **Panics** | GER not known; global index invalid; Merkle proof verification failed; origin token address not in token registry; claim already spent; amount conversion mismatch |
| **Panics** | Leaf `destination_network` does not match `agglayer::common::constants::MIDEN_NETWORK_ID`; invalid leaf type; GER not known; global index invalid; Merkle proof verification failed; origin token address not in token registry; claim already spent; amount conversion mismatch |

Validates a bridge-in claim and creates a MINT note targeting the faucet:

1. Pipes proof data and leaf data from the advice map into memory, verifying preimage
integrity.
integrity, then asserts the leaf's `destination_network` matches the global
`MIDEN_NETWORK_ID` constant (`asm/agglayer/common/constants.masm`) after `swap_u32_bytes` on
the LE-packed limb (same convention as other AggLayer bridge-in u32 felts in memory).
2. Extracts the destination account ID from the leaf data's destination address
(via `eth_address::to_account_id`).
3. Validates the Merkle proof via `verify_leaf_bridge`: computes the leaf
Expand Down Expand Up @@ -267,7 +270,7 @@ Validates a bridge-in claim and creates a MINT note targeting the faucet:
| `agglayer::bridge::ger_manager_account_id` | Value | -- | `[0, 0, mgr_suffix, mgr_prefix]` | GER manager account ID for UPDATE_GER note authorization |

Initial state: all map slots empty, all value slots `[0, 0, 0, 0]` except
`admin_account_id` and `ger_manager_account_id` which are set at account creation time.
`admin_account_id` and `ger_manager_account_id` (set at account creation time).

### 3.2 Faucet Account Component

Expand Down Expand Up @@ -495,9 +498,9 @@ The storage is divided into three logical regions: proof data (felts 0-535), lea
advice map as two keyed entries (`PROOF_DATA_KEY`, `LEAF_DATA_KEY`).
4. The `miden_claim_amount` is read from memory.
5. `bridge_in::claim` is called with `[PROOF_DATA_KEY, LEAF_DATA_KEY, miden_claim_amount]`
on the stack. The bridge validates the proof, checks the claim nullifier, looks up the
faucet via the token registry, verifies the amount conversion, then builds a MINT
output note targeting the faucet.
on the stack. The bridge asserts the leaf's `destination_network` matches the global
`MIDEN_NETWORK_ID` MASM constant, validates the proof, checks the claim nullifier, looks up the faucet via the token
registry, verifies the amount conversion, then builds a MINT output note targeting the faucet.

#### Permissions

Expand Down
84 changes: 63 additions & 21 deletions crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use agglayer::bridge::bridge_config
use agglayer::bridge::leaf_utils
use agglayer::common::constants::MIDEN_NETWORK_ID
use agglayer::common::utils
use agglayer::common::asset_conversion
use agglayer::common::eth_address
Expand Down Expand Up @@ -33,6 +34,7 @@ const ERR_SMT_ROOT_VERIFICATION_FAILED = "merkle proof verification failed: prov
const ERR_CLAIM_ALREADY_SPENT = "claim note has already been spent"
const ERR_SOURCE_BRIDGE_NETWORK_OVERFLOW = "source bridge network overflowed u32"
const ERR_INVALID_LEAF_TYPE = "invalid leaf type: only asset claims (leafType=0) are supported"
const ERR_CLAIM_LEAF_DESTINATION_NETWORK_MISMATCH = "claim leaf destination network does not match Miden AggLayer network ID"

# CONSTANTS
# =================================================================================================
Expand Down Expand Up @@ -107,26 +109,33 @@ const CLAIM_LEAF_DATA_KEY_MEM_ADDR = 704
const CLAIM_LEAF_INDEX_MEM_ADDR = 900
const CLAIM_SOURCE_BRIDGE_NETWORK_MEM_ADDR = 901

# Memory addresses for leaf data fields (derived from leaf data layout at CLAIM_LEAF_DATA_START_PTR=536)
const LEAF_TYPE_ADDRESS = 536
const ORIGIN_TOKEN_ADDRESS_0 = 538
const ORIGIN_TOKEN_ADDRESS_1 = 539
const ORIGIN_TOKEN_ADDRESS_2 = 540
const ORIGIN_TOKEN_ADDRESS_3 = 541
const ORIGIN_TOKEN_ADDRESS_4 = 542
const DESTINATION_ADDRESS_0 = 544
const DESTINATION_ADDRESS_1 = 545
const DESTINATION_ADDRESS_2 = 546
const DESTINATION_ADDRESS_3 = 547
const DESTINATION_ADDRESS_4 = 548
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_0 = 549
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_1 = 550
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_2 = 551
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_3 = 552
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_4 = 553
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_5 = 554
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_6 = 555
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_7 = 556
# Memory addresses for leaf data fields (felts relative to CLAIM_LEAF_DATA_START_PTR):
const LEAF_TYPE_ADDRESS = CLAIM_LEAF_DATA_START_PTR

# CLAIM_LEAF_DATA_START_PTR + 1 holds origin network identifier, but it is not accessed anywhere yet

const ORIGIN_TOKEN_ADDRESS_0 = CLAIM_LEAF_DATA_START_PTR + 2
const ORIGIN_TOKEN_ADDRESS_1 = CLAIM_LEAF_DATA_START_PTR + 3
const ORIGIN_TOKEN_ADDRESS_2 = CLAIM_LEAF_DATA_START_PTR + 4
const ORIGIN_TOKEN_ADDRESS_3 = CLAIM_LEAF_DATA_START_PTR + 5
const ORIGIN_TOKEN_ADDRESS_4 = CLAIM_LEAF_DATA_START_PTR + 6
Comment on lines +113 to +121
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.

that's a nice addition but the changes are unrelated to the feature described in this PR; ideally should have been a separate PR

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.

I agree, but I was updating this addresses anyway, and since this change is quite small, I decide to include it into this PR.


const DESTINATION_NETWORK_ID_MEM_ADDR = CLAIM_LEAF_DATA_START_PTR + 7

const DESTINATION_ADDRESS_0 = CLAIM_LEAF_DATA_START_PTR + 8
const DESTINATION_ADDRESS_1 = CLAIM_LEAF_DATA_START_PTR + 9
const DESTINATION_ADDRESS_2 = CLAIM_LEAF_DATA_START_PTR + 10
const DESTINATION_ADDRESS_3 = CLAIM_LEAF_DATA_START_PTR + 11
const DESTINATION_ADDRESS_4 = CLAIM_LEAF_DATA_START_PTR + 12

const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_0 = CLAIM_LEAF_DATA_START_PTR + 13
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_1 = CLAIM_LEAF_DATA_START_PTR + 14
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_2 = CLAIM_LEAF_DATA_START_PTR + 15
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_3 = CLAIM_LEAF_DATA_START_PTR + 16
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_4 = CLAIM_LEAF_DATA_START_PTR + 17
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_5 = CLAIM_LEAF_DATA_START_PTR + 18
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_6 = CLAIM_LEAF_DATA_START_PTR + 19
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_7 = CLAIM_LEAF_DATA_START_PTR + 20

# Memory addresses for MINT note output construction
const MINT_NOTE_STORAGE_MEM_ADDR_0 = 800
Expand Down Expand Up @@ -186,6 +195,7 @@ const CLAIM_DEST_ID_SUFFIX_LOCAL = 1
#!
#! Panics if:
#! - the leaf type is not 0 (not an asset claim).
#! - the leaf destination network does not match the global `MIDEN_NETWORK_ID` constant.
#! - the Merkle proof validation fails.
#! - the origin token address is not registered in the bridge's token registry.
#!
Expand All @@ -200,6 +210,10 @@ pub proc claim
exec.claim_batch_pipe_double_words
# => [pad(16)]

# check that the destination network stored in the leaf data matches the Miden network ID
exec.assert_claim_leaf_destination_network
# => [pad(16)]

exec.load_destination_address
exec.eth_address::to_account_id
loc_store.CLAIM_DEST_ID_SUFFIX_LOCAL loc_store.CLAIM_DEST_ID_PREFIX_LOCAL
Expand Down Expand Up @@ -522,7 +536,7 @@ pub proc get_leaf_value(leaf_data_key: word) -> DoubleWord
exec.mem::pipe_preimage_to_memory drop
# => []

# compute the leaf value for elements in memory starting at LEAF_DATA_START_PTR
# compute the leaf value from elements in memory starting at LEAF_DATA_START_PTR
push.LEAF_DATA_START_PTR
exec.leaf_utils::compute_leaf_value
# => [LEAF_VALUE[8]]
Expand Down Expand Up @@ -1110,3 +1124,31 @@ proc store_cgi_chain_hash
exec.native_account::set_item dropw
# => []
end

#! Asserts the claim leaf's `destination_network` matches the global `MIDEN_NETWORK_ID`.
#!
#! `claim_batch_pipe_double_words` stores leaf felts as LE-packed u32 limbs. `swap_u32_bytes`
#! converts the loaded limb to the canonical u32 value so it can be compared to `MIDEN_NETWORK_ID`
#! from `agglayer::common::constants`.
#!
#! Inputs: []
#! Outputs: []
#!
#! Panics if:
#! - the leaf destination network does not match the Miden AggLayer network ID constant.
#!
#! Invocation: exec
proc assert_claim_leaf_destination_network
# load the destination network ID onto the stack
mem_load.DESTINATION_NETWORK_ID_MEM_ADDR
# => [destination_network_id_le]

# change the endianness to BE to compare it with the Miden network ID
exec.utils::swap_u32_bytes
# => [destination_network_id_be]

# assert that the destination network ID matches the Miden network ID
push.MIDEN_NETWORK_ID
assert_eq.err=ERR_CLAIM_LEAF_DESTINATION_NETWORK_MISMATCH
# => []
end
32 changes: 32 additions & 0 deletions crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ use miden::standards::note::execution_hint::ALWAYS
use miden::protocol::types::MemoryAddress
use miden::protocol::output_note
use miden::core::crypto::hashes::poseidon2
use agglayer::common::constants::MIDEN_NETWORK_ID
use agglayer::common::utils
use agglayer::faucet -> agglayer_faucet
use agglayer::bridge::bridge_config
use agglayer::bridge::leaf_utils
use agglayer::bridge::merkle_tree_frontier
use agglayer::common::eth_address::EthereumAddressFormat

# ERRORS
# =================================================================================================

const ERR_B2AGG_DESTINATION_NETWORK_IS_MIDEN = "B2AGG note destination network ID must not be Miden's AggLayer network ID"

# CONSTANTS
# =================================================================================================

Expand Down Expand Up @@ -100,6 +106,9 @@ const BURN_NOTE_NUM_STORAGE_ITEMS=0
#! - dest_network_id is the u32 destination network/chain ID.
#! - dest_address(5) are 5 u32 values representing a 20-byte Ethereum address.
#!
#! Panics if:
#! - destination network ID is Miden's AggLayer network ID.
#!
#! Invocation: call
@locals(14)
pub proc bridge_out
Expand All @@ -110,6 +119,9 @@ pub proc bridge_out
exec.asset::store
# => [dest_network_id, dest_address(5), pad(10)]

dup exec.assert_destination_id_not_miden_id
# => [dest_network_id, dest_address(5), pad(10)]

loc_store.DESTINATION_NETWORK_LOC
loc_store.DESTINATION_ADDRESS_0_LOC
loc_store.DESTINATION_ADDRESS_1_LOC
Expand Down Expand Up @@ -222,6 +234,26 @@ end
# HELPER PROCEDURES
# =================================================================================================

#! Asserts that the bridge-out destination network ID is not equal to the Miden's AggLayer network
#! ID.
#!
#! Inputs: [dest_network_id]
#! Outputs: []
#!
#! Panics if:
#! - the destination network ID equals `MIDEN_NETWORK_ID`.
#!
#! Invocation: exec
proc assert_destination_id_not_miden_id
# change the endianness to BE to compare it with the Miden network ID
exec.utils::swap_u32_bytes
# => [destination_network_id_be]

# assert that the destination network ID is not equal to the Miden network ID
push.MIDEN_NETWORK_ID neq assert.err=ERR_B2AGG_DESTINATION_NETWORK_IS_MIDEN
# => []
end

#! Validates that a faucet is registered in the bridge's faucet registry, then performs an FPI call
#! to the faucet's `asset_to_origin_asset` procedure to obtain the scaled amount, origin token
#! address, and origin network.
Expand Down
10 changes: 10 additions & 0 deletions crates/miden-agglayer/asm/agglayer/common/constants.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# AggLayer-wide numeric constants shared by bridge-in and other modules.
#
# Each `const NAME = <decimal u32 value>` line is parsed at crate build time into
# `pub const NAME: u32 = value` in `agglayer_constants.rs` (see `build.rs`).

# NETWORK IDS
# =================================================================================================

# AggLayer-assigned network ID for this Miden chain.
const MIDEN_NETWORK_ID = 77
1 change: 1 addition & 0 deletions crates/miden-agglayer/asm/note_scripts/b2agg.masm
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const ERR_B2AGG_TARGET_ACCOUNT_MISMATCH="B2AGG note attachment target account do
#! - The note does not contain exactly 6 storage items.
#! - The note does not contain exactly 1 asset.
#! - The note attachment does not target the consuming account.
#! - The destination network ID equals Miden's AggLayer network ID.
@note_script
pub proc main
dropw
Expand Down
Loading
Loading