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
8 changes: 8 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@
path = crates/miden-agglayer/solidity-compat/lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git
branch = release-v4.9
[submodule "crates/miden-agglayer/solidity-compat/lib/openzeppelin-contracts"]
path = crates/miden-agglayer/solidity-compat/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts.git
branch = release-v5.0
[submodule "crates/miden-agglayer/solidity-compat/lib/openzeppelin-contracts-upgradeable5"]
path = crates/miden-agglayer/solidity-compat/lib/openzeppelin-contracts-upgradeable5
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git
branch = release-v5.0
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ generate-solidity-test-vectors: ## Regenerate Solidity MMR test vectors using Fo
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_generateClaimedGlobalIndexHashChainVectors

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

Expand Down
60 changes: 58 additions & 2 deletions crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const LEAF_DATA_NUM_WORDS = 8
#! - the Merkle proof for the provided leaf-index tuple against the computed GER is invalid.
#!
#! Invocation: call
@locals(8) # leaf value
pub proc verify_leaf_bridge
# get the leaf value. We have all the necessary leaf data in the advice map
exec.get_leaf_value
Expand All @@ -96,9 +97,36 @@ pub proc verify_leaf_bridge
movupw.3 dropw
# => [LEAF_VALUE[8], PROOF_DATA_KEY, pad(4)]

# save the leaf value to the local memory to reuse it during the computation of the CGI chain
# hash
loc_storew_le.0 swapw
loc_storew_le.4 swapw
# => [LEAF_VALUE[8], PROOF_DATA_KEY, pad(4)]

# delegate proof verification
exec.verify_leaf
# => [pad(16)]

# compute the claimed global index hash chain
#
# this hash is computed as:
# NEW_CGI_CHAIN_HASH[8] = Keccak256(OLD_CGI_CHAIN_HASH[8], Keccak256(GLOBAL_INDEX[8], LEAF_VALUE[8]))

# load the leaf value back to the stack from local memory
loc_loadw_le.4 swapw loc_loadw_le.0
# => [LEAF_VALUE[8], pad(8)]

# compute the claimed global index chain hash
exec.compute_cgi_hash_chain
# => [NEW_CGI_CHAIN_HASH[8], pad(8)]

# store the computed CGI chain hash
#
# TODO: once the CLAIM note will be consumed against the bridge contract (and not FPIed against
# it), store the computed value. Notice that this value is used during the computation of the
# next CGI chain hash, so the `compute_cgi_hash_chain` procedure should be updated.
dropw dropw
# => [pad(16)]
end

#! Assert the global index is valid for a mainnet deposit.
Expand Down Expand Up @@ -259,8 +287,7 @@ proc verify_leaf
# => [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
push.GLOBAL_INDEX_PTR exec.utils::mem_load_double_word
# => [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
Expand Down Expand Up @@ -360,3 +387,32 @@ proc calculate_root(
padw loc_loadw_le.CUR_HASH_HI_LOCAL padw loc_loadw_le.CUR_HASH_LO_LOCAL
# => [ROOT_LO, ROOT_HI]
end

#! Computes the claimed global index (CGI) chain hash.
#!
#! The resulting hash is computed as a sequential hash of leaf value, global index, and previous
#! value of the CGI chain hash:
#! NEW_CGI_CHAIN_HASH[8] = Keccak256(OLD_CGI_CHAIN_HASH[8], Keccak256(GLOBAL_INDEX[8], LEAF_VALUE[8]))
#!
#! Inputs: [LEAF_VALUE[8]]
#! Outputs: [NEW_CGI_CHAIN_HASH[8]]
#!
#! Invocation: exec
proc compute_cgi_hash_chain
# load the global index onto the stack
push.GLOBAL_INDEX_PTR exec.utils::mem_load_double_word
# => [GLOBAL_INDEX[8], LEAF_VALUE[8]]

exec.keccak256::merge
# => [Keccak256(GLOBAL_INDEX, LEAF_VALUE), pad(8)]

# load the old CGI chain hash
#
# TODO: load the CGI chain hash value here once it will be stored
padw padw
# => [OLD_CGI_CHAIN_HASH[8], Keccak256(GLOBAL_INDEX, LEAF_VALUE), pad(8)]

# compute the new CGI chain hash
exec.keccak256::merge
# => [NEW_CGI_CHAIN_HASH[8], pad(8)]
end
12 changes: 12 additions & 0 deletions crates/miden-agglayer/solidity-compat/foundry.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,22 @@
"rev": "1801b0541f4fda118a10798fd3486bb7051c5dd6"
}
},
"lib/openzeppelin-contracts": {
"branch": {
"name": "release-v5.0",
"rev": "dbb6104ce834628e473d2173bbc9d47f81a9eec3"
}
},
"lib/openzeppelin-contracts-upgradeable": {
"branch": {
"name": "release-v4.9",
"rev": "2d081f24cac1a867f6f73d512f2022e1fa987854"
}
},
"lib/openzeppelin-contracts-upgradeable5": {
"branch": {
"name": "release-v5.0",
"rev": "723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1"
}
}
}
4 changes: 3 additions & 1 deletion crates/miden-agglayer/solidity-compat/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ libs = ["lib"]
optimizer = true
optimizer_runs = 200
out = "out"
solc = "0.8.20"
solc = "0.8.28"
src = "src"
via_ir = true

remappings = [
"@agglayer/=lib/agglayer-contracts/contracts/",
"@openzeppelin/contracts-upgradeable4/=lib/openzeppelin-contracts-upgradeable/contracts/",
"@openzeppelin/contracts-upgradeable5/=lib/openzeppelin-contracts-upgradeable5/contracts/",
"@openzeppelin/contracts5/=lib/openzeppelin-contracts/contracts/",
]

# Emit extra output for test vector generation
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"amount": 100000000000000000000,
"amount": "100000000000000000000",
"deposit_count": 1,
"description": "L1 bridgeAsset transaction test vectors with valid Merkle proofs",
"destination_address": "0x00000000AA0000000000bb000000cc000000Dd00",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"cgi_chain_hash": "0xbce0afc98c69ea85e9cfbf98c87c58a77c12d857551f1858530341392f70c22d",
"global_index": "0x0000000000000000000000000000000000000000000000010000000000000000",
"leaf": "0x9d85d7c56264697df18f458b4b12a457b87b7e7f7a9b16dcb368514729ef680d",
"old_cgi_chain_hash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "@agglayer/v2/sovereignChains/BridgeL2SovereignChain.sol";
import "@agglayer/lib/GlobalExitRootLib.sol";
import "@agglayer/interfaces/IBasePolygonZkEVMGlobalExitRoot.sol";
import "./DepositContractTestHelpers.sol";

contract MockGlobalExitRootManager is IBasePolygonZkEVMGlobalExitRoot {
mapping(bytes32 => uint256) public override globalExitRootMap;

function updateExitRoot(bytes32) external override {}

function setGlobalExitRoot(bytes32 globalExitRoot) external {
globalExitRootMap[globalExitRoot] = block.number;
}
}

/**
* @title ClaimedGlobalIndexHashChainVectors
* @notice Generates a test vector for claimedGlobalIndexHashChain using _verifyLeafBridge.
*
* Run with: forge test -vv --match-test test_generateClaimedGlobalIndexHashChainVectors
*/
contract ClaimedGlobalIndexHashChainVectors is Test, BridgeL2SovereignChain, DepositContractTestHelpers {
function test_generateClaimedGlobalIndexHashChainVectors() public {
string memory obj = "root";

// ====== BRIDGE TRANSACTION PARAMETERS ======
uint8 leafType = 0;
uint32 originNetwork = 0;
address originTokenAddress = 0x2DC70fb75b88d2eB4715bc06E1595E6D97c34DFF;
uint32 destinationNetwork = 20;
address destinationAddress = 0x00000000AA0000000000bb000000cc000000Dd00;
uint256 amount = 100000000000000000000;

bytes memory metadata = abi.encode("Test Token", "TEST", uint8(18));
bytes32 metadataHash = keccak256(metadata);

// ====== COMPUTE LEAF VALUE AND ADD TO TREE ======
bytes32 leafValue = getLeafValue(
leafType,
originNetwork,
originTokenAddress,
destinationNetwork,
destinationAddress,
amount,
metadataHash
);

_addLeaf(leafValue);
uint256 leafIndex = depositCount - 1;
bytes32 localExitRoot = getRoot();

// ====== GENERATE MERKLE PROOF ======
bytes32[32] memory canonicalZeros = _computeCanonicalZeros();
bytes32[32] memory smtProofLocalExitRoot =
_generateLocalProof(leafIndex, canonicalZeros);
bytes32[32] memory smtProofRollupExitRoot;

// ====== COMPUTE EXIT ROOTS ======
bytes32 mainnetExitRoot = localExitRoot;
bytes32 rollupExitRoot = keccak256(abi.encodePacked("rollup_exit_root_simulated"));
bytes32 globalExitRoot = GlobalExitRootLib.calculateGlobalExitRoot(
mainnetExitRoot,
rollupExitRoot
);

// ====== SET GLOBAL EXIT ROOT MANAGER ======
MockGlobalExitRootManager gerManager = new MockGlobalExitRootManager();
gerManager.setGlobalExitRoot(globalExitRoot);
globalExitRootManager = IBasePolygonZkEVMGlobalExitRoot(address(gerManager));

// Use a non-zero network ID to match sovereign-chain requirements
networkID = 10;

// ====== COMPUTE GLOBAL INDEX ======
uint256 globalIndex = (uint256(1) << 64) | uint256(leafIndex);

// ====== COMPUTE CLAIMED GLOBAL INDEX HASH CHAIN ======
bytes32 oldClaimedHashChain = claimedGlobalIndexHashChain;

this.verifyLeafBridgeHarness(
smtProofLocalExitRoot,
smtProofRollupExitRoot,
globalIndex,
mainnetExitRoot,
rollupExitRoot,
leafType,
originNetwork,
originTokenAddress,
destinationNetwork,
destinationAddress,
amount,
metadataHash
);

bytes32 claimedHashChain = claimedGlobalIndexHashChain;

// ====== SERIALIZE OUTPUT ======
vm.serializeBytes32(obj, "global_index", bytes32(globalIndex));
vm.serializeBytes32(obj, "leaf", leafValue);
vm.serializeBytes32(obj, "cgi_chain_hash", claimedHashChain);
string memory json = vm.serializeBytes32(obj, "old_cgi_chain_hash", oldClaimedHashChain);

vm.writeJson(json, "test-vectors/claimed_global_index_hash_chain.json");
}

function verifyLeafBridgeHarness(
bytes32[32] calldata smtProofLocalExitRoot,
bytes32[32] calldata smtProofRollupExitRoot,
uint256 globalIndex,
bytes32 mainnetExitRoot,
bytes32 rollupExitRoot,
uint8 leafType,
uint32 originNetwork,
address originTokenAddress,
uint32 destinationNetwork,
address destinationAddress,
uint256 amount,
bytes32 metadataHash
) external {
_verifyLeafBridge(
smtProofLocalExitRoot,
smtProofRollupExitRoot,
globalIndex,
mainnetExitRoot,
rollupExitRoot,
leafType,
originNetwork,
originTokenAddress,
destinationNetwork,
destinationAddress,
amount,
metadataHash
);
}
}
15 changes: 14 additions & 1 deletion crates/miden-agglayer/src/eth_types/global_index.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use alloc::vec::Vec;

#[cfg(any(test, feature = "testing"))]
use miden_core_lib::handlers::bytes_to_packed_u32_felts;
use miden_protocol::Felt;
use miden_protocol::utils::{HexParseError, hex_to_bytes};
use miden_protocol::{Felt, Word};

// ================================================================================================
// GLOBAL INDEX ERROR
Expand Down Expand Up @@ -99,6 +100,18 @@ impl GlobalIndex {
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}

/// Converts the [`GlobalIndex`] to two [`Word`]s: `[lo, hi]`.
///
/// - `lo` contains the first 4 u32-packed felts (bytes 0..16).
/// - `hi` contains the last 4 u32-packed felts (bytes 16..32).
#[cfg(any(test, feature = "testing"))]
pub fn to_words(&self) -> [Word; 2] {
let elements = self.to_elements();
let lo: [Felt; 4] = elements[0..4].try_into().expect("to_elements returns 8 felts");
let hi: [Felt; 4] = elements[4..8].try_into().expect("to_elements returns 8 felts");
[Word::new(lo), Word::new(hi)]
}
}

#[cfg(test)]
Expand Down
Loading
Loading