Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [BREAKING] Introduce `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249), [#2252](https://github.com/0xMiden/miden-base/pull/2252), [#2260](https://github.com/0xMiden/miden-base/pull/2260)).
- [BREAKING] Introduce `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249), [#2252](https://github.com/0xMiden/miden-base/pull/2252), [#2260](https://github.com/0xMiden/miden-base/pull/2260), [#2268](https://github.com/0xMiden/miden-base/pull/2268), [#2279](https://github.com/0xMiden/miden-base/pull/2279)).
- Introduce standard `NetworkAccountTarget` attachment for use in network transactions which replaces `NoteTag::NetworkAccount` ([#2257](https://github.com/0xMiden/miden-base/pull/2257)).
- Implemented verification of AggLayer deposits (claims) against GER ([#2295](https://github.com/0xMiden/miden-base/pull/2295), [#2288](https://github.com/0xMiden/miden-base/pull/2288)).

### Changes

Expand Down
93 changes: 83 additions & 10 deletions crates/miden-agglayer/asm/bridge/bridge_in.masm
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
use miden::agglayer::crypto_utils
use miden::core::crypto::hashes::keccak256
use miden::core::mem

# Inputs: []
# Output: [GER_ROOT[8]]
pub proc get_rollup_exit_root
# Push dummy GER (8 elements)
push.0.0.0.0.0.0.0.0 # dummy GER
const PROOF_DATA_PTR = 0
const PROOF_DATA_WORD_LEN = 134
const SMT_PROOF_LOCAL_EXIT_ROOT_PTR = 0 # local SMT proof is first
const GLOBAL_INDEX_PTR = PROOF_DATA_PTR + 2 * 256 # 512
const EXIT_ROOTS_PTR = GLOBAL_INDEX_PTR + 8 # 520
const MAINNET_EXIT_ROOT_PTR = GLOBAL_INDEX_PTR # it's the first exit root

const ERR_BRIDGE_NOT_MAINNET = "bridge not mainnet"
const ERR_LEADING_BITS_NON_ZERO = "leading bits of global index must be zero"
const ERR_ROLLUP_INDEX_NON_ZERO = "rollup index must be zero for a mainnet deposit"

# Inputs: [GER_ROOT[8]]
# Output: []
pub proc assert_valid_ger
# TODO verify that GER is in storage
dropw dropw
end

#! Computes the leaf value and verifies it against the AggLayer bridge.
Expand Down Expand Up @@ -41,17 +54,77 @@ pub proc verify_leaf_bridge
movupw.3 dropw
# => [LEAF_VALUE[8], PROOF_DATA_KEY, pad(4)]

# delegate proof verification (stubbed for now)
# delegate proof verification
exec.verify_leaf
end

#! Verify leaf and checks that it has not been claimed.
#!
#! Inputs:
#! Operand stack: [LEAF_VALUE[8], PROOF_DATA_KEY, pad(4)]
#! Operand stack: [LEAF_VALUE[8], PROOF_DATA_KEY]
#!
#! Outputs: []
#!
#! Invocation: exec
proc verify_leaf
# TODO: Awaiting https://github.com/0xMiden/miden-base/issues/2277
dropw dropw dropw
# => [pad(16)]
movupw.2
# load proof data from the advice map into memory
adv.push_mapval
# => [PROOF_DATA_KEY, LEAF_VALUE[8]]

push.SMT_PROOF_LOCAL_EXIT_ROOT_PTR push.PROOF_DATA_WORD_LEN
exec.mem::pipe_preimage_to_memory drop

# 1. compute GER from mainnet + rollup exit roots
push.EXIT_ROOTS_PTR
# => [exit_roots_ptr, LEAF_VALUE[8]]
exec.crypto_utils::compute_ger
# => [GER[8], LEAF_VALUE[8]]

# 2. assert the GER is valid
exec.assert_valid_ger
# => [LEAF_VALUE[8]]

# 3. load global index from memory
padw mem_loadw_le.GLOBAL_INDEX_PTR
padw push.GLOBAL_INDEX_PTR add.4 mem_loadw_le swapw
# => [GLOBAL_INDEX[8], LEAF_VALUE[8]]

# to see if we're dealing with a deposit from mainnet or from a rollup, process the global index
# TODO currently only implemented for mainnet deposits (mainnet flag must be 1)
exec.process_global_index_mainnet
# => [leaf_index]

# load the pointers to the merkle proof and root, to pass to `verify_merkle_proof`
push.SMT_PROOF_LOCAL_EXIT_ROOT_PTR
push.MAINNET_EXIT_ROOT_PTR

# => [mainnet_exit_root_ptr, smt_proof_local_exit_root_ptr, leaf_index, LEAF_VALUE[8]]
movdn.10
# => [smt_proof_local_exit_root_ptr, leaf_index, LEAF_VALUE[8], mainnet_exit_root_ptr]
# delegate verification to crypto_utils::verify_merkle_proof (stubbed for now)
exec.crypto_utils::verify_merkle_proof
end

#! Assert the global index is valid for a mainnet deposit.
#!
#! Inputs: [GLOBAL_INDEX[8]]
#! Outputs: [leaf_index]
#!
#! Invocation: exec
pub proc process_global_index_mainnet
# for v0.1, let's only implement the mainnet branch
# the top 191 bits of the global index are zero
repeat.5 assertz.err=ERR_LEADING_BITS_NON_ZERO end

# the next element is a u32 mainnet flag bit
# enforce that this limb is one
# => [mainnet_flag, GLOBAL_INDEX[6..8], LEAF_VALUE[8]]
assert.err=ERR_BRIDGE_NOT_MAINNET

# the next element is a u32 rollup index, must be zero for a mainnet deposit
assertz.err=ERR_ROLLUP_INDEX_NON_ZERO

# finally, the leaf index = lowest 32 bits = last limb
# => [leaf_index]
end
63 changes: 40 additions & 23 deletions crates/miden-agglayer/asm/bridge/crypto_utils.masm
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,23 @@ use miden::core::crypto::hashes::keccak256

#! Given the leaf data returns the leaf value.
#!
#! Inputs: [leaf_type, origin_network, ORIGIN_ADDRESS, destination_network, DESTINATION_ADDRESS, amount, METADATA_HASH]
#! Outputs: [LEAF_VALUE]
#!
#! Where:
#! - leaf_type is the leaf type: [0] transfer Ether / ERC20 tokens, [1] message.
#! - origin_network is the origin network identifier.
#! - ORIGIN_ADDRESS is the origin token address (5 elements)
#! - destination_network is the destination network identifier.
#! - DESTINATION_ADDRESS is the destination address (5 elements).
#! - amount is the amount: [0] Amount of tokens/ether, [1] Amount of ether.
#! - METADATA_HASH is the hash of the metadata (8 elements).
#! - LEAF_VALUE is the computed leaf value (8 elements).
#!
#! This function computes the keccak256 hash of the abi.encodePacked data.
#! Inputs:
#! Operand stack: [LEAF_DATA_KEY]
#! Advice map: {
#! LEAF_DATA_KEY => [
#! originNetwork[1], // Origin network identifier (1 felt, uint32)
#! originTokenAddress[5], // Origin token address (5 felts, address as 5 u32 felts)
#! destinationNetwork[1], // Destination network identifier (1 felt, uint32)
#! destinationAddress[5], // Destination address (5 felts, address as 5 u32 felts)
#! amount[8], // Amount of tokens (8 felts, uint256 as 8 u32 felts)
#! metadata[8], // ABI encoded metadata (8 felts, fixed size)
#! ],
#! }
#! Outputs: [LEAF_VALUE[8]]
#!
#! Invocation: exec
pub proc get_leaf_value
# TODO: implement getLeafValue()
# https://github.com/agglayer/agglayer-contracts/blob/e468f9b0967334403069aa650d9f1164b1731ebb/contracts/v2/lib/DepositContractV2.sol#L22

# stubbed out:
push.1.1.1.1
push.1.1.1.1

# exec.keccak256::hash_bytes
# => [LEAF_VALUE[8]]
padw
end

#! Verify leaf and checks that it has not been claimed.
Expand Down Expand Up @@ -80,3 +71,29 @@ pub proc verify_claim_proof
dropw dropw dropw dropw
push.1
end

#! Computes the Global Exit Tree (GET) root from the mainnet and rollup exit roots.
#!
#! Inputs: [exit_roots_ptr]
#! Outputs: [GER_ROOT[8]]
#!
#! Invocation: exec
pub proc compute_ger
push.64 swap
# => [exit_roots_ptr, len_bytes]
exec.keccak256::hash_bytes
# => [GER_ROOT[8]]
end

#! Verifies a Merkle proof for a leaf value against a root.
#!
#! Inputs: [smt_proof_ptr, leaf_index, LEAF_VALUE[8], root_ptr]
#! Outputs: []
#!
pub proc verify_merkle_proof
# TODO pending https://github.com/0xMiden/miden-base/issues/2278
drop
drop
dropw dropw
drop
end
9 changes: 9 additions & 0 deletions crates/miden-agglayer/src/errors/agglayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ pub const ERR_B2AGG_WRONG_NUMBER_OF_ASSETS: MasmError = MasmError::from_static_s
/// Error Message: "B2AGG script expects exactly 6 note inputs"
pub const ERR_B2AGG_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("B2AGG script expects exactly 6 note inputs");

/// Error Message: "bridge not mainnet"
pub const ERR_BRIDGE_NOT_MAINNET: MasmError = MasmError::from_static_str("bridge not mainnet");

/// Error Message: "CLAIM's target account address and transaction address do not match"
pub const ERR_CLAIM_TARGET_ACCT_MISMATCH: MasmError = MasmError::from_static_str("CLAIM's target account address and transaction address do not match");

Expand All @@ -26,8 +29,14 @@ pub const ERR_FELT_OUT_OF_FIELD: MasmError = MasmError::from_static_str("combine
/// Error Message: "invalid claim proof"
pub const ERR_INVALID_CLAIM_PROOF: MasmError = MasmError::from_static_str("invalid claim proof");

/// Error Message: "leading bits of global index must be zero"
pub const ERR_LEADING_BITS_NON_ZERO: MasmError = MasmError::from_static_str("leading bits of global index must be zero");

/// Error Message: "address limb is not u32"
pub const ERR_NOT_U32: MasmError = MasmError::from_static_str("address limb is not u32");

/// Error Message: "rollup index must be zero for a mainnet deposit"
pub const ERR_ROLLUP_INDEX_NON_ZERO: MasmError = MasmError::from_static_str("rollup index must be zero for a mainnet deposit");

/// Error Message: "maximum scaling factor is 18"
pub const ERR_SCALE_AMOUNT_EXCEEDED_LIMIT: MasmError = MasmError::from_static_str("maximum scaling factor is 18");
4 changes: 2 additions & 2 deletions crates/miden-agglayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,14 +545,14 @@ pub fn claim_note_test_inputs(
let smt_proof_local_exit_root = vec![Felt::new(0); 256];
let smt_proof_rollup_exit_root = vec![Felt::new(0); 256];
let global_index = [
Felt::new(12345),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(1), // mainnet flag
Felt::new(0),
Felt::new(2), // leaf index (mainnet)
];

let mainnet_exit_root: [u8; 32] = [
Expand Down
82 changes: 82 additions & 0 deletions crates/miden-testing/tests/agglayer/global_index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
extern crate alloc;

use alloc::sync::Arc;

use miden_agglayer::agglayer_library;
use miden_agglayer::errors::{
ERR_BRIDGE_NOT_MAINNET,
ERR_LEADING_BITS_NON_ZERO,
ERR_ROLLUP_INDEX_NON_ZERO,
};
use miden_assembly::{Assembler, DefaultSourceManager};
use miden_core_lib::CoreLibrary;
use miden_processor::Program;
use miden_testing::assert_execution_error;

use crate::agglayer::test_utils::execute_program_with_default_host;

fn assemble_process_global_index_program(global_index_be_u32_limbs: [u32; 8]) -> Program {
let [g0, g1, g2, g3, g4, g5, g6, g7] = global_index_be_u32_limbs;

let script_code = format!(
r#"
use miden::core::sys
use miden::agglayer::bridge_in
begin
push.{g7}.{g6}.{g5}.{g4}.{g3}.{g2}.{g1}.{g0}
exec.bridge_in::process_global_index_mainnet
exec.sys::truncate_stack
end
"#
);

Assembler::new(Arc::new(DefaultSourceManager::default()))
.with_dynamic_library(CoreLibrary::default())
.unwrap()
.with_dynamic_library(agglayer_library())
.unwrap()
.assemble_program(&script_code)
.unwrap()
}

#[tokio::test]
async fn test_process_global_index_mainnet_returns_leaf_index() -> anyhow::Result<()> {
// 256-bit globalIndex encoded as 8 u32 limbs (big-endian):
// [top 191 bits = 0, mainnet flag = 1, rollup_index = 0, leaf_index = 2]
let global_index = [0, 0, 0, 0, 0, 1, 0, 2];
let program = assemble_process_global_index_program(global_index);

let exec_output = execute_program_with_default_host(program, None).await?;

assert_eq!(exec_output.stack[0].as_int(), 2);
Ok(())
}

#[tokio::test]
async fn test_process_global_index_mainnet_rejects_non_zero_leading_bits() {
let global_index = [1, 0, 0, 0, 0, 1, 0, 2];
let program = assemble_process_global_index_program(global_index);

let err = execute_program_with_default_host(program, None).await;
assert_execution_error!(err, ERR_LEADING_BITS_NON_ZERO);
}

#[tokio::test]
async fn test_process_global_index_mainnet_rejects_flag_limb_upper_bits() {
// limb5 is the mainent flag; only the lowest bit is allowed
let global_index = [0, 0, 0, 0, 0, 3, 0, 2];
let program = assemble_process_global_index_program(global_index);

let err = execute_program_with_default_host(program, None).await;
assert_execution_error!(err, ERR_BRIDGE_NOT_MAINNET);
}

#[tokio::test]
async fn test_process_global_index_mainnet_rejects_non_zero_rollup_index() {
let global_index = [0, 0, 0, 0, 0, 1, 7, 2];
let program = assemble_process_global_index_program(global_index);

let err = execute_program_with_default_host(program, None).await;
assert_execution_error!(err, ERR_ROLLUP_INDEX_NON_ZERO);
}
2 changes: 2 additions & 0 deletions crates/miden-testing/tests/agglayer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod asset_conversion;
mod bridge_in;
mod bridge_out;
mod global_index;
mod solidity_miden_address_conversion;
mod test_utils;
35 changes: 35 additions & 0 deletions crates/miden-testing/tests/agglayer/test_utils.rs
Copy link
Contributor Author

Choose a reason for hiding this comment

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

copied from #2262, they'll eventually be merged

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
extern crate alloc;

use miden_agglayer::agglayer_library;
use miden_core_lib::CoreLibrary;
use miden_processor::fast::{ExecutionOutput, FastProcessor};
use miden_processor::{AdviceInputs, DefaultHost, ExecutionError, Program, StackInputs};
use miden_protocol::transaction::TransactionKernel;

/// Execute a program with default host and optional advice inputs
pub async fn execute_program_with_default_host(
program: Program,
advice_inputs: Option<AdviceInputs>,
) -> Result<ExecutionOutput, ExecutionError> {
let mut host = DefaultHost::default();

let test_lib = TransactionKernel::library();
host.load_library(test_lib.mast_forest()).unwrap();

let std_lib = CoreLibrary::default();
host.load_library(std_lib.mast_forest()).unwrap();

// Register handlers from std_lib
for (event_name, handler) in std_lib.handlers() {
host.register_handler(event_name, handler)?;
}

let agglayer_lib = agglayer_library();
host.load_library(agglayer_lib.mast_forest()).unwrap();

let stack_inputs = StackInputs::new(vec![]).unwrap();
let advice_inputs = advice_inputs.unwrap_or_default();

let processor = FastProcessor::new_debug(stack_inputs.as_slice(), advice_inputs);
processor.execute(&program, &mut host).await
}