Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
4ec9c2a
feat: add stubbed out bridge in flow
partylikeits1983 Dec 16, 2025
0e66481
refactor: clean up agglayer_faucet
partylikeits1983 Dec 16, 2025
dc2d124
feat: add create_claim_note helper function
partylikeits1983 Dec 16, 2025
46fa8dd
refactor: rm extraneous doc comment
partylikeits1983 Dec 16, 2025
613d37d
Update crates/miden-testing/tests/agglayer/bridge_in.rs
partylikeits1983 Dec 16, 2025
f903f02
refactor: cleanup comments & rename param
partylikeits1983 Dec 16, 2025
c7502f4
fix: rustfmt
partylikeits1983 Dec 16, 2025
f329e85
Update crates/miden-lib/asm/agglayer/account_components/local_exit_tr…
partylikeits1983 Dec 16, 2025
23c769f
refactor: cleanup comment
partylikeits1983 Dec 16, 2025
61c14e9
refactor: update agglayer faucet docs
partylikeits1983 Dec 16, 2025
ee40a65
refactor: cleanup doc comments
partylikeits1983 Dec 16, 2025
712fb9d
refactor: make target account consume created P2ID note
partylikeits1983 Dec 16, 2025
65d4ba6
Update crates/miden-lib/asm/agglayer/account_components/agglayer_fauc…
partylikeits1983 Dec 17, 2025
bcf6c19
Update crates/miden-lib/asm/agglayer/account_components/agglayer_fauc…
partylikeits1983 Dec 17, 2025
0ead563
Update crates/miden-lib/asm/agglayer/account_components/agglayer_fauc…
partylikeits1983 Dec 17, 2025
1ea2b86
Update crates/miden-lib/asm/agglayer/account_components/agglayer_fauc…
partylikeits1983 Dec 17, 2025
7a48b7d
Update crates/miden-lib/asm/agglayer/account_components/agglayer_fauc…
partylikeits1983 Dec 17, 2025
b216d6b
fix: propagate name change in aggfaucet
partylikeits1983 Dec 17, 2025
72b887f
Update crates/miden-lib/asm/agglayer/account_components/agglayer_fauc…
partylikeits1983 Dec 17, 2025
c710f12
Apply suggestions from code review
partylikeits1983 Dec 17, 2025
5ff05a3
refactor: improve naming and structure
partylikeits1983 Dec 17, 2025
a777227
feat: add helper functions for agglayer faucet & bridge
partylikeits1983 Dec 17, 2025
9ebaa92
refactor: hardcode P2ID notescript in aggfaucet
partylikeits1983 Dec 18, 2025
9e04720
fix: newline
partylikeits1983 Dec 18, 2025
e434b43
fix: newline end of file
partylikeits1983 Dec 18, 2025
73e637f
fix: newline end of CLAIM note
partylikeits1983 Dec 18, 2025
ff8df66
Merge branch 'agglayer' into ajl-claim-bridge-in
partylikeits1983 Dec 19, 2025
01b7023
fix: update CLAIM error naming
partylikeits1983 Dec 19, 2025
7663ac1
refactor: add comment re CLAIM acc check
partylikeits1983 Dec 19, 2025
d81b9ba
refactor: simplify faucet & bridge creation & doc comments
partylikeits1983 Dec 19, 2025
7d3efca
fix: add testing flag to test helpers fns
partylikeits1983 Dec 19, 2025
bd0c2bf
refactor: improve comments and error messages
partylikeits1983 Dec 19, 2025
cb759d7
fix: newline
partylikeits1983 Dec 19, 2025
caafde6
fix: formatting in aggfaucet masm
partylikeits1983 Dec 19, 2025
c8bb085
chore: merge latest agglayer branch
partylikeits1983 Dec 29, 2025
20cc9cd
wip: commit to GET merkle path and validate in aggfaucet
partylikeits1983 Dec 30, 2025
766371b
wip: refactor CLAIM note & aggfaucet claimAsset flow
partylikeits1983 Jan 1, 2026
068ece3
refactor: call validate_claim proc first in aggfaucet
partylikeits1983 Jan 2, 2026
7f71bbb
refactor: add claim note types
partylikeits1983 Jan 3, 2026
b601cec
refactor: add pad comments to stack in CLAIM note
partylikeits1983 Jan 3, 2026
ad085f5
feat: update CLAIM note inputs to closely match agglayer claimAsset()…
partylikeits1983 Jan 6, 2026
5598664
refactor: refactor CLAIM note inputs
partylikeits1983 Jan 6, 2026
fb6711f
refactor: remove duplicate active_note::get_inputs in aggfaucet
partylikeits1983 Jan 6, 2026
ed44373
fix: doc comment fix
partylikeits1983 Jan 6, 2026
4c0827c
feat: split CLAIM note data into separate keys in AdviceMap
partylikeits1983 Jan 7, 2026
022dd03
refactor: rm unecessary tests
partylikeits1983 Jan 7, 2026
a1bcd66
fix: write data keys to global memory
partylikeits1983 Jan 7, 2026
2e22083
Merge branch 'next' into ajl-claim-bridge-in
partylikeits1983 Jan 7, 2026
7386779
Merge branch 'agglayer' into ajl-claim-bridge-in
partylikeits1983 Jan 7, 2026
2bfa7e3
Update crates/miden-agglayer/asm/note_scripts/CLAIM.masm
partylikeits1983 Jan 7, 2026
d8618e4
Update crates/miden-agglayer/asm/note_scripts/CLAIM.masm
partylikeits1983 Jan 7, 2026
2efe4f7
Update crates/miden-agglayer/asm/bridge/agglayer_faucet.masm
partylikeits1983 Jan 7, 2026
b1edc3d
Update crates/miden-agglayer/asm/bridge/agglayer_faucet.masm
partylikeits1983 Jan 7, 2026
a0c1809
refactor: implmement contants & clean stack comments
partylikeits1983 Jan 7, 2026
4fde5fd
refactor: address nits & comments
partylikeits1983 Jan 8, 2026
1939a26
refactor: improve doc comments & add TODOs
partylikeits1983 Jan 8, 2026
6d66357
fix: fix incorrect comment
partylikeits1983 Jan 8, 2026
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 crates/miden-agglayer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ testing = ["miden-protocol/testing"]
miden-assembly = { workspace = true }
miden-core = { workspace = true }
miden-protocol = { workspace = true }
miden-standards = { workspace = true }
miden-utils-sync = { workspace = true }

[dev-dependencies]
Expand Down
260 changes: 260 additions & 0 deletions crates/miden-agglayer/asm/bridge/agglayer_faucet.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
use miden::agglayer::bridge_in
use miden::agglayer::asset_conversion
use miden::protocol::active_account
use miden::protocol::active_note
use miden::standards::faucets
use miden::protocol::note
use miden::protocol::tx
use miden::core::mem


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

# The slot in this component's storage layout where the bridge account ID is stored.
const BRIDGE_ID_SLOT = word("miden::agglayer::faucet")

const PROOF_DATA_WORD_LEN = 134
const LEAF_DATA_WORD_LEN = 6
const OUTPUT_NOTE_DATA_WORD_LEN = 2

const PROOF_DATA_START_PTR = 0
const LEAF_DATA_START_PTR = 536
const OUTPUT_NOTE_DATA_START_PTR = 568

# Memory Addresses
const PROOF_DATA_KEY_MEM_ADDR = 700
const LEAF_DATA_KEY_MEM_ADDR = 704
const OUTPUT_NOTE_DATA_MEM_ADDR = 708
const CLAIM_NOTE_DATA_MEM_ADDR = 712

const OUTPUT_NOTE_INPUTS_MEM_ADDR = 0
const OUTPUT_NOTE_TAG_MEM_ADDR = 574
const OUTPUT_NOTE_SERIAL_NUM_MEM_ADDR = 568
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_0 = 548
const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_1 = 552

# P2ID output note constants
const P2ID_SCRIPT_ROOT = [7588674509004260508, 4058706621878288170, 5607159951796201570, 5541281552524512743]
const P2ID_NOTE_NUM_INPUTS = 2
const OUTPUT_NOTE_TYPE_PUBLIC = 1
const EXECUTION_HINT_ALWAYS = 1
const OUTPUT_NOTE_AUX = 0

const P2ID_OUTPUT_NOTE_AMOUNT_MEM_PTR = 611
# ERRORS
# =================================================================================================

const ERR_INVALID_CLAIM_PROOF = "invalid claim proof"

#! Inputs: [PROOF_DATA_KEY, LEAF_DATA_KEY]
#! Outputs: []
#!
#! Panics if:
#! - the bridge account ID is not properly configured in storage.
#! - the foreign procedure invocation fails.
#! - the claim proof validation fails.
#!
#! Invocation: exec
proc validate_claim
# Get bridge_in::check_claim_proof procedure MAST root
procref.bridge_in::check_claim_proof
# => [BRIDGE_PROC_MAST_ROOT]

push.BRIDGE_ID_SLOT[0..2]
# => [bridge_id_idx, BRIDGE_PROC_MAST_ROOT]

# Get Bridge AccountId
exec.active_account::get_item
# => [bridge_account_id_prefix, bridge_account_id_suffix, 0, 0, BRIDGE_PROC_MAST_ROOT]

movup.2 drop movup.2 drop
# => [bridge_account_id_prefix, bridge_account_id_suffix, BRIDGE_PROC_MAST_ROOT]

# Call check_claim_proof procedure on Bridge
# Calling: bridge_in::check_claim_proof
exec.tx::execute_foreign_procedure
# => [validation_result]

# Assert valid proof data
assert.err=ERR_INVALID_CLAIM_PROOF drop
# => []
end

# Inputs: []
# Outputs: [U256[0], U256[1]]
proc get_raw_claim_amount
padw mem_loadw_be.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_0
padw mem_loadw_be.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_1
end

# Inputs: [U256[0], U256[1]]
# Outputs: [amount]
proc scale_down_amount
repeat.7 drop end
end

# Inputs: []
# Outputs: [prefix, suffix]
proc get_destination_account_id
mem_load.543 mem_load.544
end

# Inputs: [PROOF_DATA_KEY, LEAF_DATA_KEY, OUTPUT_NOTE_DATA_KEY]
# Outputs: []
proc batch_pipe_double_words
# 1) Verify PROOF_DATA_KEY
mem_storew_be.PROOF_DATA_KEY_MEM_ADDR
adv.push_mapval
# => [PROOF_DATA_KEY]

push.PROOF_DATA_START_PTR push.PROOF_DATA_WORD_LEN
exec.mem::pipe_double_words_preimage_to_memory drop

# 2) Verify LEAF_DATA_KEY
mem_storew_be.LEAF_DATA_KEY_MEM_ADDR
adv.push_mapval
# => [LEAF_DATA_KEY]

push.LEAF_DATA_START_PTR push.LEAF_DATA_WORD_LEN
exec.mem::pipe_double_words_preimage_to_memory drop

# 3) Verify OUTPUT_NOTE_DATA_KEY
mem_storew_be.OUTPUT_NOTE_DATA_MEM_ADDR
adv.push_mapval
# => [OUTPUT_NOTE_DATA_KEY]

push.OUTPUT_NOTE_DATA_START_PTR push.OUTPUT_NOTE_DATA_WORD_LEN
exec.mem::pipe_double_words_preimage_to_memory drop
end

#! Builds a P2ID output note for the claim recipient.
#!
#! This procedure expects the claim data to be already written to memory via batch_pipe_double_words.
#! It reads the destination account ID, amount, and other note parameters from memory to construct
#! the output note.
#!
#! Inputs: []
#! Outputs: []
#!
#! Note: This procedure will be refactored in a follow-up to use leaf data to build the output note.
proc build_p2id_output_note
# Build P2ID output note
push.P2ID_SCRIPT_ROOT[0..4]
# => [SCRIPT_ROOT]

swapw mem_loadw_be.OUTPUT_NOTE_SERIAL_NUM_MEM_ADDR
# => [SERIAL_NUM, SCRIPT_ROOT]

push.P2ID_NOTE_NUM_INPUTS
# => [num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT]

exec.get_destination_account_id
# => [account_id_prefix, account_id_suffix, num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT]

mem_store.0 mem_store.1
# => [num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT]

push.OUTPUT_NOTE_INPUTS_MEM_ADDR
# => [inputs_ptr = 0, num_output_note_inputs, SERIAL_NUM, SCRIPT_ROOT]

exec.note::build_recipient
# => [RECIPIENT]

push.EXECUTION_HINT_ALWAYS push.OUTPUT_NOTE_TYPE_PUBLIC push.OUTPUT_NOTE_AUX
# => [aux, note_type, execution_hint, RECIPIENT]

mem_load.OUTPUT_NOTE_TAG_MEM_ADDR
# => [tag, aux, execution_hint, RECIPIENT]

exec.get_raw_claim_amount
# => [AMOUNT[1], AMOUNT[0], tag, aux, note_type, execution_hint, RECIPIENT]

# TODO: implement scale down logic; stubbed out for now
exec.asset_conversion::scale_u256_to_native_amount
# => [amount, tag, aux, note_type, execution_hint, RECIPIENT]

exec.faucets::distribute
# => [pad(16)]
end

#! Validates a claim against the AggLayer bridge and mints the corresponding asset to the recipient.
#!
#! This procedure validates the rollup exit root Merkle Proof via FPI against the agglayer bridge,
#! and if validation passes, mints the asset and creates an output note for the recipient.
Comment on lines +183 to +184
Copy link
Contributor

Choose a reason for hiding this comment

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

Not for this PR, but we should expand this description to cover the mechanism in more detail. For example, we should explain how we protect against double-claiming (AFAICT, right now we do not, but that's something we should do). Let's add this to the list of follow-ups.

Copy link
Contributor Author

@partylikeits1983 partylikeits1983 Jan 8, 2026

Choose a reason for hiding this comment

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

Double-spend can be prevented in two ways:

  1. While it's possible to create two identical P2ID notes, only one can actually be consumed. If the claim note is consumed twice, only one P2ID output note will be successfully consumed.
  2. We can have a mapping in the bridge or in the faucet that stores consumed claim proofs as a hash -> bool value, or store the index of the leaf that was claimed in a mapping (similar to how it's done in the agglayer solidity contract).

My preference is towards option 2. I still think it might be reasonable to check somewhere on the node that an output note created from a tx doesn't have a corresponding nullifier for that note already in the nullifier db. Right now it would be possible to create multiple CLAIM notes which create multiple identical P2ID notes but only have 1 P2ID note actually consumed. Technically not double spend, but but there is a griefing attack vector where its no longer possible to mint any more assets on the faucet since the MAX_MINT limit has been reached.

cc @mmagician for visibility

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we'll get the first option naturally once we start deterministically deriving the P2ID note serial num from proof data (probably from globalIndex or a combination of globalIndex and the bridge message).

But I agree, we may want to take extra caution here and also put the bridge message into a map. Let's create a separate issue for this though.

#!
#! TODO: Expand this description to cover the double-spend protection mechanism in detail.
#! Double-spend can be prevented in two ways:
#! 1) While it's possible to create two identical P2ID notes, only one can actually be consumed.
#! If the claim note is consumed twice, only one P2ID output note will be successfully consumed.
#! 2) We can have a mapping in the bridge or in the faucet that stores consumed claim proofs
#! as a hash -> bool value (similar to how it's done in the agglayer solidity contract).
#!
#! Inputs: [PROOF_DATA_KEY, LEAF_DATA_KEY, OUTPUT_NOTE_DATA_KEY, pad(4)]
#! Outputs: [pad(16)]
#!
#! Advice map: {
#! PROOF_DATA_KEY => [
#! smtProofLocalExitRoot[256], // SMT proof for local exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH])
#! smtProofRollupExitRoot[256], // SMT proof for rollup exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH])
#! globalIndex[8], // Global index (8 felts, uint256 as 8 u32 felts)
#! mainnetExitRoot[8], // Mainnet exit root hash (8 felts, bytes32 as 8 u32 felts)
#! rollupExitRoot[8], // Rollup exit root hash (8 felts, bytes32 as 8 u32 felts)
#! ],
#! 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)
#! EMPTY_WORD // padding
#! ],
#! OUTPUT_NOTE_DATA_KEY => [
#! output_p2id_serial_num[4], // P2ID note serial number (4 felts, Word)
#! agglayer_faucet_account_id[2], // Agglayer faucet account ID (2 felts, prefix and suffix)
#! output_note_tag[1], // P2ID output note tag
#! ]
#! }
#!
#! Panics if:
#! - the rollup exit root Merkle Proof validation via FPI fails.
#! - any of the validations in faucets::distribute fail.
#!
#! Invocation: call
pub proc claim
# Check AdviceMap values hash to keys & write CLAIM inputs & DATA_KEYs to global memory
exec.batch_pipe_double_words
# => []

# VALIDATE CLAIM
mem_loadw_be.LEAF_DATA_KEY_MEM_ADDR padw
mem_loadw_be.PROOF_DATA_KEY_MEM_ADDR
# => [PROOF_DATA_KEY, LEAF_DATA_KEY]

# Errors on invalid proof
exec.validate_claim
# => []

# Create P2ID output note
exec.build_p2id_output_note
# => []
end

#! Burns the fungible asset from the active note.
#!
#! This procedure retrieves the asset from the active note and burns it. The note must contain
#! exactly one asset, which must be a fungible asset issued by this faucet.
#!
#! Inputs: [pad(16)]
#! Outputs: [pad(16)]
#!
#! Panics if:
#! - the procedure is not called from a note context (active_note::get_assets will fail).
#! - the note does not contain exactly one asset.
#! - the transaction is executed against an account which is not a fungible asset faucet.
#! - the transaction is executed against a faucet which is not the origin of the specified asset.
#! - the amount about to be burned is greater than the outstanding supply of the asset.
#!
#! Invocation: call
pub use ::miden::standards::faucets::basic_fungible::burn
8 changes: 8 additions & 0 deletions crates/miden-agglayer/asm/bridge/asset_conversion.masm
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,11 @@ pub proc scale_native_amount_to_u256
padw swapw
# => [RESULT_U256[0], RESULT_U256[1]]
end

#! TODO: implement scaling down
#!
#! Inputs: [U256[0], U256[1]]
#! Outputs: [amount]
pub proc scale_u256_to_native_amount
repeat.7 drop end
end
43 changes: 43 additions & 0 deletions crates/miden-agglayer/asm/bridge/bridge_in.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use miden::agglayer::crypto_utils

# 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
end

#! Checks the validity of the GET proof
#!
#! Inputs:
#! Operand stack: [PROOF_DATA_KEY, LEAF_DATA_KEY, pad(8)]
#! Advice map: {
#! PROOF_DATA_KEY => [
#! smtProofLocalExitRoot[256], // SMT proof for local exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH])
#! smtProofRollupExitRoot[256], // SMT proof for rollup exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH])
#! globalIndex[8], // Global index (8 felts, uint256 as 8 u32 felts)
#! mainnetExitRoot[8], // Mainnet exit root hash (8 felts, bytes32 as 8 u32 felts)
#! rollupExitRoot[8], // Rollup exit root hash (8 felts, bytes32 as 8 u32 felts)
#! ],
#! 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)
#! EMPTY_WORD // padding
#! ],
#! }
#!
#! Invocation: call
pub proc check_claim_proof
exec.get_rollup_exit_root
# => [GER_ROOT[8], CLAIM_NOTE_RPO_COMMITMENT]

# Check CLAIM note proof data against current GER
exec.crypto_utils::verify_claim_proof
# => [is_valid_claim_proof]

swap drop
end
1 change: 1 addition & 0 deletions crates/miden-agglayer/asm/bridge/bridge_out.masm
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,4 @@ pub proc bridge_out
exec.create_burn_note
# => []
end

Loading