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 @@ -4,6 +4,7 @@

### Features

- Store transaction reference block number alongside each registered GER in the bridge's GER map ([#2579](https://github.com/0xMiden/protocol/pull/2579)).
- Added AggLayer faucet registry to bridge account with conversion metadata, `CONFIG_AGG_BRIDGE` note for faucet registration, and FPI-based asset conversion in `bridge_out` ([#2426](https://github.com/0xMiden/miden-base/pull/2426)).
- Enable `CodeBuilder` to add advice map entries to compiled scripts ([#2275](https://github.com/0xMiden/miden-base/pull/2275)).
- Added `BlockNumber::MAX` constant to represent the maximum block number ([#2324](https://github.com/0xMiden/miden-base/pull/2324)).
Expand Down
6 changes: 4 additions & 2 deletions crates/miden-agglayer/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ Asserts the note sender matches the bridge admin stored in
Asserts the note sender matches the GER manager stored in
`miden::agglayer::bridge::ger_manager`, then computes
`KEY = rpo256::merge(GER_UPPER, GER_LOWER)` and stores
`KEY -> [1, 0, 0, 0]` in the `ger` map slot. This marks the GER as "known".
`KEY -> [1, block_num, 0, 0]` in the `ger` map slot, where `block_num` is the
transaction's reference block number. This marks the GER as "known" and records
when it was registered.

#### `bridge_in::verify_leaf_bridge`

Expand All @@ -129,7 +131,7 @@ Verifies a bridge-in claim:

| Slot name | Slot type | Key encoding | Value encoding | Purpose |
|-----------|-----------|-------------|----------------|---------|
| `miden::agglayer::bridge::ger` | Map | `rpo256::merge(GER_UPPER, GER_LOWER)` | `[1, 0, 0, 0]` if known; `[0, 0, 0, 0]` if absent | Known Global Exit Root set |
| `miden::agglayer::bridge::ger` | Map | `rpo256::merge(GER_UPPER, GER_LOWER)` | `[1, block_num, 0, 0]` if known (block_num = reference block at registration); `[0, 0, 0, 0]` if absent | Known Global Exit Root set |
| `miden::agglayer::let` | Map | `[h, 0, 0, 0]` and `[h, 1, 0, 0]` (for h = 0..31) | Per index h: two keys yield one double-word (2 words = 8 felts, a Keccak-256 digest). Absent keys return zeros. | Local Exit Tree MMR frontier |
| `miden::agglayer::let::root_lo` | Value | -- | `[root_0, root_1, root_2, root_3]` | LET root low word (Keccak-256 lower 16 bytes) |
| `miden::agglayer::let::root_hi` | Value | -- | `[root_4, root_5, root_6, root_7]` | LET root high word (Keccak-256 upper 16 bytes) |
Expand Down
22 changes: 13 additions & 9 deletions crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use miden::protocol::account_id
use miden::protocol::active_account
use miden::protocol::active_note
use miden::protocol::native_account
use miden::protocol::tx

# ERRORS
# =================================================================================================
Expand Down Expand Up @@ -31,7 +32,8 @@ const IS_FAUCET_REGISTERED_FLAG=1
#! Updates the Global Exit Root (GER) in the bridge account storage.
#!
#! Computes hash(GER) = rpo256::merge(GER_UPPER, GER_LOWER) and stores it in a map
#! with value [GER_KNOWN_FLAG, 0, 0, 0] to indicate the GER is known.
#! with value [GER_KNOWN_FLAG, block_num, 0, 0] to indicate the GER is known and
#! record the block at which it was registered.
#!
#! Panics if the note sender is not the global exit root manager.
#!
Expand All @@ -49,9 +51,11 @@ pub proc update_ger
exec.rpo256::merge
# => [GER_HASH, pad(12)]

# prepare VALUE = [0, 0, 0, GER_KNOWN_FLAG]
push.GER_KNOWN_FLAG.0.0.0
# => [0, 0, 0, GER_KNOWN_FLAG, GER_HASH, pad(12)]
# prepare VALUE = [0, 0, block_num, GER_KNOWN_FLAG]
push.GER_KNOWN_FLAG
exec.tx::get_block_number
push.0.0
# => [0, 0, block_num, GER_KNOWN_FLAG, GER_HASH, pad(12)]
Comment on lines +54 to +58

swapw
# => [GER_HASH, VALUE, pad(12)]
Expand Down Expand Up @@ -86,13 +90,13 @@ pub proc assert_valid_ger
# => [slot_id_prefix, slot_id_suffix, GER_HASH]

exec.active_account::get_map_item
# => [VALUE]
# => [0, 0, block_num, GER_KNOWN_FLAG]

# assert the GER is known in storage (VALUE = [0, 0, 0, GER_KNOWN_FLAG])
push.GER_KNOWN_FLAG.0.0.0
# => [0, 0, 0, GER_KNOWN_FLAG, VALUE]
# assert the GER is known in storage (check only the flag element)
drop drop drop
# => [GER_KNOWN_FLAG]

assert_eqw.err=ERR_GER_NOT_FOUND
assert.err=ERR_GER_NOT_FOUND
# => []
end

Expand Down
48 changes: 38 additions & 10 deletions crates/miden-agglayer/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;

use miden_core::{Felt, FieldElement, ONE, Word, ZERO};
use miden_core::{Felt, FieldElement, ONE, Word};
use miden_protocol::account::component::AccountComponentMetadata;
use miden_protocol::account::{Account, AccountComponent, AccountId, StorageSlot, StorageSlotName};
use miden_protocol::block::BlockNumber;
use miden_protocol::crypto::hash::rpo::Rpo256;
use miden_utils_sync::LazyLock;
use thiserror::Error;
Expand Down Expand Up @@ -102,7 +103,12 @@ impl AggLayerBridge {
// CONSTANTS
// --------------------------------------------------------------------------------------------

const REGISTERED_GER_MAP_VALUE: Word = Word::new([ONE, ZERO, ZERO, ZERO]);
/// Index of the GER-known flag within the stored GER map value word.
const GER_FLAG_INDEX: usize = 0;
/// Index of the block number within the stored GER map value word.
const GER_BLOCK_NUM_INDEX: usize = 1;
/// Flag value indicating a GER is registered (matches MASM `GER_KNOWN_FLAG`).
const REGISTERED_GER_FLAG: Felt = ONE;

// CONSTRUCTORS
// --------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -158,6 +164,10 @@ impl AggLayerBridge {
/// Returns a boolean indicating whether the provided GER is present in storage of the provided
/// bridge account.
///
/// The GER map stores `[GER_KNOWN_FLAG, block_num, 0, 0]` for registered GERs, where
/// `block_num` is the reference block number at registration time. This method checks only
/// the flag element.
///
/// # Errors
///
/// Returns an error if:
Expand All @@ -166,9 +176,33 @@ impl AggLayerBridge {
ger: ExitRoot,
bridge_account: Account,
) -> Result<bool, AgglayerBridgeError> {
// check that the provided account is a bridge account
Self::assert_bridge_account(&bridge_account)?;
let stored_value = Self::get_ger_value(ger, &bridge_account)?;
Ok(stored_value[Self::GER_FLAG_INDEX] == Self::REGISTERED_GER_FLAG)
}

/// Returns the block number at which the provided GER was registered, or `None` if the GER
/// is not registered.
///
/// # Errors
///
/// Returns an error if:
/// - the provided account is not an [`AggLayerBridge`] account.
pub fn get_ger_block_number(
ger: ExitRoot,
bridge_account: Account,
) -> Result<Option<BlockNumber>, AgglayerBridgeError> {
Self::assert_bridge_account(&bridge_account)?;
let stored_value = Self::get_ger_value(ger, &bridge_account)?;
if stored_value[Self::GER_FLAG_INDEX] != Self::REGISTERED_GER_FLAG {
return Ok(None);
}
let block_num = stored_value[Self::GER_BLOCK_NUM_INDEX].as_int() as u32;
Ok(Some(BlockNumber::from(block_num)))
}
Comment on lines +191 to +202

/// Looks up the raw value stored for the given GER in the bridge account's GER map.
fn get_ger_value(ger: ExitRoot, bridge_account: &Account) -> Result<Word, AgglayerBridgeError> {
// Compute the expected GER hash: rpo256::merge(GER_UPPER, GER_LOWER)
let mut ger_lower: [Felt; 4] = ger.to_elements()[0..4].try_into().unwrap();
let mut ger_upper: [Felt; 4] = ger.to_elements()[4..8].try_into().unwrap();
Expand All @@ -182,18 +216,12 @@ impl AggLayerBridge {
ger_upper.reverse();
let ger_hash = Rpo256::merge(&[ger_upper.into(), ger_lower.into()]);

// Get the value stored by the GER hash. If this GER was registered, the value would be
// equal to [1, 0, 0, 0]
let stored_value = bridge_account
.storage()
.get_map_item(AggLayerBridge::ger_map_slot_name(), ger_hash)
.expect("provided account should have AggLayer Bridge specific storage slots");

if stored_value == Self::REGISTERED_GER_MAP_VALUE {
Ok(true)
} else {
Ok(false)
}
Ok(stored_value)
}

/// Reads the Local Exit Root (double-word) from the bridge account's storage.
Expand Down
11 changes: 10 additions & 1 deletion crates/miden-testing/tests/agglayer/update_ger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,18 @@ async fn update_ger_note_updates_storage() -> anyhow::Result<()> {
let mut updated_bridge_account = bridge_account.clone();
updated_bridge_account.apply_delta(executed_transaction.account_delta())?;

let is_registered = AggLayerBridge::is_ger_registered(ger, updated_bridge_account)?;
let is_registered = AggLayerBridge::is_ger_registered(ger, updated_bridge_account.clone())?;
assert!(is_registered, "GER was not registered in the bridge account");

// Verify the stored block number matches the transaction's reference block
let block_num = AggLayerBridge::get_ger_block_number(ger, updated_bridge_account)?
.expect("GER should have a block number stored");
assert_eq!(
block_num,
mock_chain.latest_block_header().block_num(),
"stored block number should match the transaction's reference block"
);

Ok(())
}

Expand Down
Loading