-
Notifications
You must be signed in to change notification settings - Fork 115
Add Agglayer CLAIM note & bridging in functionality
#2188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4ec9c2a
0e66481
dc2d124
46fa8dd
613d37d
f903f02
c7502f4
f329e85
23c769f
61c14e9
ee40a65
712fb9d
65d4ba6
bcf6c19
0ead563
1ea2b86
7a48b7d
b216d6b
72b887f
c710f12
5ff05a3
a777227
9ebaa92
9e04720
e434b43
73e637f
ff8df66
01b7023
7663ac1
d81b9ba
7d3efca
bd0c2bf
cb759d7
caafde6
c8bb085
20cc9cd
766371b
068ece3
7f71bbb
b601cec
ad085f5
5598664
fb6711f
ed44373
4c0827c
022dd03
a1bcd66
2e22083
7386779
2bfa7e3
d8618e4
2efe4f7
b1edc3d
a0c1809
4fde5fd
1939a26
6d66357
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
partylikeits1983 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 | ||
bobbinth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #! 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Double-spend can be prevented in two ways:
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 cc @mmagician for visibility
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 | ||
partylikeits1983 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # Check AdviceMap values hash to keys & write CLAIM inputs & DATA_KEYs to global memory | ||
| exec.batch_pipe_double_words | ||
| # => [] | ||
partylikeits1983 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # 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] | ||
partylikeits1983 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Errors on invalid proof | ||
| exec.validate_claim | ||
| # => [] | ||
partylikeits1983 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Create P2ID output note | ||
| exec.build_p2id_output_note | ||
bobbinth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # => [] | ||
| 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 | ||
| 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 | ||
partylikeits1983 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| #! | ||
| #! 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 | ||
partylikeits1983 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -160,3 +160,4 @@ pub proc bridge_out | |
| exec.create_burn_note | ||
| # => [] | ||
| end | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.