-
Notifications
You must be signed in to change notification settings - Fork 106
Implement Keccak-based MMR frontier #2202
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
787e3e8
d4db19d
81a9040
191adeb
a4f98a7
bd5f15b
96d1c37
facd51f
b6e5295
3feb14a
a1cdf82
a0f5f08
de4774a
69f392c
ce37ad5
c538b88
24d6195
e0059ee
f1931e1
107d4a2
7c41b56
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,262 @@ | ||||||||||||||
| use miden::core::crypto::hashes::keccak256 | ||||||||||||||
|
|
||||||||||||||
| # Module description: TBD | ||||||||||||||
|
|
||||||||||||||
| # CONSTANTS | ||||||||||||||
| # ================================================================================================= | ||||||||||||||
|
|
||||||||||||||
| # The maximum number of leaves which could be added to the MMR. | ||||||||||||||
| const MAX_LEAVES_NUM = 32 | ||||||||||||||
|
|
||||||||||||||
| # The total height of the full MMR tree, which root represents the the commitment to the current | ||||||||||||||
| # frontier. | ||||||||||||||
| const TREE_HEIGHT = 5 | ||||||||||||||
|
|
||||||||||||||
| # The number of the stack elements which one node occupy. | ||||||||||||||
| const NODE_SIZE = 8 | ||||||||||||||
|
|
||||||||||||||
| # The offset of the number of leaves in the current MMR state. | ||||||||||||||
| const NUM_LEAVES_OFFSET = 0 | ||||||||||||||
|
|
||||||||||||||
| # The offset of the MMR root. | ||||||||||||||
| # | ||||||||||||||
| # Q: do we need to store the root? It seems like we never return it | ||||||||||||||
| const MMR_ROOT_OFFSET = 4 | ||||||||||||||
|
|
||||||||||||||
| # The offset of the array of the zero hashes of respective heights. | ||||||||||||||
| const ZEROS_OFFSET = 12 # 6 double words, 48 felts in total | ||||||||||||||
|
|
||||||||||||||
| # The offset of the array of the frontier nodes of respective heights. | ||||||||||||||
| const FRONTIER_OFFSET = 60 # 6 double words, 48 felts in total | ||||||||||||||
|
Comment on lines
+21
to
+30
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. Would be great to add a description of the expected memory layout, like so:
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 would probably store the zeros separately from the above expected layout. So in practical terms, the canonical zeros could be stored at the very end. |
||||||||||||||
|
|
||||||||||||||
| # PUBLIC API | ||||||||||||||
| # ================================================================================================= | ||||||||||||||
|
|
||||||||||||||
| #! Sets the zeros in the zero array, sets the root to the ZERO_5 (root of the zero tree of height 5) | ||||||||||||||
| #! | ||||||||||||||
| #! Inputs: [mmr_frontier_ptr] | ||||||||||||||
| #! Outputs: [] | ||||||||||||||
| pub proc new | ||||||||||||||
| # prepare the pointers to the zero array and to the root | ||||||||||||||
| dup add.MMR_ROOT_OFFSET swap add.ZEROS_OFFSET | ||||||||||||||
| # => [zero_array_ptr, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the zero for the height 0 | ||||||||||||||
| push.1676014350.378744630.4127735880.2512168523.3362732168.2839872470.2825030484.3656125737 | ||||||||||||||
|
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. We should hardcode those constants at the top of the file |
||||||||||||||
| # => [ZERO_0_LO, ZERO_0_HI, zero_array_ptr, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| exec.mem_store_double_word dropw dropw | ||||||||||||||
| # => [zero_array_ptr, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the zero for the height 1 | ||||||||||||||
| add.NODE_SIZE | ||||||||||||||
| push.501060780.3871659214.3009211592.2623812180.1269040150.2402257162.1716941530.3619962211 | ||||||||||||||
| # => [ZERO_1_LO, ZERO_1_HI, zero_array_ptr, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| exec.mem_store_double_word dropw dropw | ||||||||||||||
| # => [zero_array_ptr+8, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the zero for the height 2 | ||||||||||||||
| add.NODE_SIZE | ||||||||||||||
| push.2113874120.866443917.1466696484.1577749685.3266301349.2378900196.2613970667.2822768521 | ||||||||||||||
| # => [ZERO_2_LO, ZERO_2_HI, zero_array_ptr, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| exec.mem_store_double_word dropw dropw | ||||||||||||||
| # => [zero_array_ptr+16, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the zero for the height 3 | ||||||||||||||
| add.NODE_SIZE | ||||||||||||||
| push.3624627744.3896701049.3259095475.2541063347.2174359630.3386860883.819752706.2663419451 | ||||||||||||||
| # => [ZERO_3_LO, ZERO_3_HI, zero_array_ptr, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| exec.mem_store_double_word dropw dropw | ||||||||||||||
| # => [zero_array_ptr+24, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the zero for the height 4 | ||||||||||||||
| add.NODE_SIZE | ||||||||||||||
| push.3672259025.1408703058.2325593427.3764368977.1768984761.3986407010.3179733816.3993949676 | ||||||||||||||
| # => [ZERO_4_LO, ZERO_4_HI, zero_array_ptr, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| exec.mem_store_double_word dropw dropw | ||||||||||||||
| # => [zero_array_ptr+32, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the zero for the height 5 | ||||||||||||||
| add.NODE_SIZE | ||||||||||||||
| push.2772295100.1943789808.3975148557.2783120662.1954699067.4078189558.55884592.3556179934 | ||||||||||||||
| # => [ZERO_5_LO, ZERO_5_HI, zero_array_ptr+40, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| exec.mem_store_double_word | ||||||||||||||
|
|
||||||||||||||
| # update the root | ||||||||||||||
| movup.8 drop | ||||||||||||||
| # => [ZERO_5_LO, ZERO_5_HI, mmr_root_ptr] | ||||||||||||||
|
|
||||||||||||||
| exec.mem_store_double_word | ||||||||||||||
| dropw dropw drop | ||||||||||||||
| # => [] | ||||||||||||||
| end | ||||||||||||||
|
|
||||||||||||||
| #! Updates the existing frontier with the new leaf, returns a new leaf count and a new MMR root. | ||||||||||||||
| #! | ||||||||||||||
| #! Inputs: [NEW_LEAF_LO, NEW_LEAF_HI, mmr_frontier_ptr] | ||||||||||||||
| #! Outputs: [NEW_ROOT_LO, NEW_ROOT_HI] | ||||||||||||||
| @locals(8) | ||||||||||||||
| pub proc append_and_update_frontier | ||||||||||||||
| # store the new leaf to the local memory | ||||||||||||||
| loc_storew.0 dropw | ||||||||||||||
| loc_storew.4 dropw | ||||||||||||||
|
Comment on lines
+105
to
+107
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.
Suggested change
(see a comment below about where |
||||||||||||||
| # => [mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # get the leaves number | ||||||||||||||
| dup add.NUM_LEAVES_OFFSET mem_load | ||||||||||||||
| # => [num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the updated leaves number | ||||||||||||||
| dup add.1 dup.2 add.NUM_LEAVES_OFFSET | ||||||||||||||
| # => [num_leaves_ptr, num_leaves+1, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| mem_store | ||||||||||||||
| # => [num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # iterate `TREE_HEIGHT` times to get the root of the tree | ||||||||||||||
| # | ||||||||||||||
| # iter_counter in that case will show the current tree height | ||||||||||||||
| push.0 push.1 | ||||||||||||||
| # => [loop_flag=1, iter_counter=0, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| while.true | ||||||||||||||
| # => [iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # get the pointer to the frontier node of the current height | ||||||||||||||
|
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. what is the initial state of the frontier array? Is it zeros, or canonical zeros at tree height? Or something else? |
||||||||||||||
| dup.2 add.FRONTIER_OFFSET dup.1 mul.NODE_SIZE add | ||||||||||||||
| # => [frontier[iter_counter]_ptr, iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # determine whether the last `num_leaves` bit is 1 (is `num_leaves` odd) | ||||||||||||||
| dup.2 u32and.1 | ||||||||||||||
| # => [is_odd, frontier[iter_counter]_ptr, iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| if.true | ||||||||||||||
| # => [frontier[iter_counter]_ptr, iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
| # | ||||||||||||||
| # this height already had a subtree root stored in frontier[iter_counter], merge into | ||||||||||||||
| # parent. | ||||||||||||||
| exec.mem_load_double_word | ||||||||||||||
| # => [FRONTIER[iter_counter]_LO, FRONTIER[iter_counter]_HI, iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # load the current hash from the local memory back to the stack | ||||||||||||||
| padw loc_loadw.4 padw loc_loadw.0 swapdw | ||||||||||||||
| # => [FRONTIER[iter_counter]_LO, FRONTIER[iter_counter]_HI, CUR_HASH_LO, CUR_HASH_HI, iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
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. Following the algorithm description here, |
||||||||||||||
|
|
||||||||||||||
| # merge the frontier node of this height with the current hash to get the current hash | ||||||||||||||
| # of the next height (merge(frontier[h], cur)) | ||||||||||||||
| exec.keccak256::merge | ||||||||||||||
| # => [CUR_HASH_LO', CUR_HASH_HI', iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the current hash of the next height back to the local memory | ||||||||||||||
| loc_storew.0 dropw | ||||||||||||||
| loc_storew.4 dropw | ||||||||||||||
| # => [iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
| else | ||||||||||||||
| # => [frontier[iter_counter]_ptr, iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
| # | ||||||||||||||
| # this height wasn't "occupied" yet: store the current hash as the subtree root | ||||||||||||||
| # (frontier node) at height `iter_counter` | ||||||||||||||
| padw loc_loadw.4 padw loc_loadw.0 | ||||||||||||||
| # => [CUR_HASH_LO, CUR_HASH_HI, frontier[iter_counter]_ptr, iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the CUR_HASH to the frontier[iter_counter]_ptr | ||||||||||||||
| exec.mem_store_double_word movdn.8 drop | ||||||||||||||
| # => [CUR_HASH_LO, CUR_HASH_HI, iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # get the pointer to the zero node of the current height | ||||||||||||||
| dup.10 add.ZEROS_OFFSET dup.9 mul.NODE_SIZE add | ||||||||||||||
| # => [zeros[iter_counter], CUR_HASH_LO, CUR_HASH_HI, iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # load the zero node to the stack | ||||||||||||||
| exec.mem_load_double_word swapdw | ||||||||||||||
| # => [CUR_HASH_LO, CUR_HASH_HI, ZERO_H_LO, ZERO_H_HI, iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # merge the current hash with the zero node of this height to get the current hash of | ||||||||||||||
| # the next height (merge(cur, zeroes[h])) | ||||||||||||||
| exec.keccak256::merge | ||||||||||||||
| # => [CUR_HASH_LO', CUR_HASH_HI', iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the current hash of the next height back to the local memory | ||||||||||||||
| loc_storew.0 dropw | ||||||||||||||
| loc_storew.4 dropw | ||||||||||||||
| # => [iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
| end | ||||||||||||||
| # => [iter_counter, num_leaves, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # update the counter | ||||||||||||||
| push.1 add | ||||||||||||||
|
|
||||||||||||||
| # update the `num_leaves` (shift it right by 1 bit) | ||||||||||||||
|
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 would avoid using let mut idx = num_leaves;at the beginning of the procedure, and just use |
||||||||||||||
| swap u32shr.1 swap | ||||||||||||||
| # => [iter_counter+1, num_leaves>>1, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # compute the cycle flag | ||||||||||||||
| dup neq.TREE_HEIGHT | ||||||||||||||
| # => [loop_flag, iter_counter+1, num_leaves>>1, mmr_frontier_ptr] | ||||||||||||||
| end | ||||||||||||||
| # => [iter_counter=5, num_leaves=0, mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # clean the stack | ||||||||||||||
| drop drop | ||||||||||||||
| # => [mmr_frontier_ptr] | ||||||||||||||
|
|
||||||||||||||
| # update the frontier[5] (frontier[tree_height]) value with the current (final) hash -- in case | ||||||||||||||
| # we have a full tree (32 leaves) this only node will represent the frontier | ||||||||||||||
| # | ||||||||||||||
| # at the same time we can update the root, since the final hash is the root of the full MMR | ||||||||||||||
|
|
||||||||||||||
| # load the root pointer and the pointer to the 5th frontier node onto the stack | ||||||||||||||
| dup add.FRONTIER_OFFSET push.TREE_HEIGHT.NODE_SIZE mul add | ||||||||||||||
| swap add.MMR_ROOT_OFFSET | ||||||||||||||
| # => [mmr_root_ptr, frontier[tree_height]_ptr] | ||||||||||||||
|
|
||||||||||||||
| # load the final hash | ||||||||||||||
| padw loc_loadw.4 padw loc_loadw.0 | ||||||||||||||
| # => [FIN_HASH_LO, FIN_HASH_HI, mmr_root_ptr, frontier[tree_height]_ptr] | ||||||||||||||
|
|
||||||||||||||
| # save the final hash as root of the MMR | ||||||||||||||
| exec.mem_store_double_word movup.8 drop | ||||||||||||||
| # => [FIN_HASH_LO, FIN_HASH_HI, frontier[tree_height]_ptr] | ||||||||||||||
|
|
||||||||||||||
| # store the final hash to the frontier[tree_height]_ptr (frontier[5]) | ||||||||||||||
|
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. Doesn't this step already happen as part of the loop (for the case when the last bit is zero)? |
||||||||||||||
| exec.mem_store_double_word movup.8 drop | ||||||||||||||
| # => [FIN_HASH_LO, FIN_HASH_HI] | ||||||||||||||
| # sine the final hash represents the tree root, the resulting stack state could be represented | ||||||||||||||
| # as: | ||||||||||||||
| # => [NEW_ROOT_LO, NEW_ROOT_HI] | ||||||||||||||
| end | ||||||||||||||
|
|
||||||||||||||
| # HELPER PROCEDURES | ||||||||||||||
| # ================================================================================================= | ||||||||||||||
|
|
||||||||||||||
| #! Stores two words to the provided global memory address. | ||||||||||||||
| #! | ||||||||||||||
| #! Inputs: [WORD_1, WORD_2, ptr] | ||||||||||||||
| #! Outputs: [WORD_1, WORD_2, ptr] | ||||||||||||||
| proc mem_store_double_word | ||||||||||||||
| dup.8 mem_storew_be swapw | ||||||||||||||
| # => [WORD_2, WORD_1, ptr] | ||||||||||||||
|
|
||||||||||||||
| dup.8 add.4 mem_storew_be swapw | ||||||||||||||
| # => [WORD_1, WORD_2, ptr] | ||||||||||||||
| end | ||||||||||||||
|
Comment on lines
+239
to
+247
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. a future note: these might be useful across the board, let's leave as-is for now but keep in mind that we might want to place these helpers in a more accessible location for other procedures to make use of. |
||||||||||||||
|
|
||||||||||||||
| #! Loads two words from the provided global memory address. | ||||||||||||||
| #! | ||||||||||||||
| #! Inputs: [ptr] | ||||||||||||||
| #! Outputs: [WORD_1, WORD_2] | ||||||||||||||
| proc mem_load_double_word | ||||||||||||||
| padw dup.4 mem_loadw_be | ||||||||||||||
| # => [WORD_1, ptr] | ||||||||||||||
|
|
||||||||||||||
| padw movup.8 add.4 mem_loadw_be | ||||||||||||||
| # => [WORD_2, WORD_1] | ||||||||||||||
|
|
||||||||||||||
| swapw | ||||||||||||||
| # => [WORD_1, WORD_2] | ||||||||||||||
| end | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| use miden::native_account | ||
| use miden::output_note | ||
|
|
||
| # CONSTANTS | ||
| # ================================================================================================= | ||
| const PUBLIC_NOTE=1 | ||
|
|
||
| #! Adds the provided asset to the active account. | ||
| #! | ||
| #! Inputs: [ASSET, pad(12)] | ||
| #! Outputs: [pad(16)] | ||
| #! | ||
| #! Where: | ||
| #! - ASSET is the asset to be received, can be fungible or non-fungible | ||
| #! | ||
| #! Panics if: | ||
| #! - the same non-fungible asset already exists in the account. | ||
| #! - adding a fungible asset would result in amount overflow, i.e., | ||
| #! the total amount would be greater than 2^63. | ||
| #! | ||
| #! Invocation: call | ||
| pub proc receive_asset | ||
| exec.native_account::add_asset | ||
| # => [ASSET', pad(12)] | ||
|
|
||
| # drop the final asset | ||
| dropw | ||
| # => [pad(16)] | ||
| end | ||
|
|
||
| #! Removes the specified asset from the account and adds it to the output note with the specified | ||
| #! index. | ||
| #! | ||
| #! This procedure is expected to be invoked using a `call` instruction. It makes no guarantees about | ||
| #! the contents of the `PAD` elements shown below. It is the caller's responsibility to make sure | ||
| #! these elements do not contain any meaningful data. | ||
| #! | ||
| #! Inputs: [ASSET, note_idx, pad(11)] | ||
| #! Outputs: [ASSET, note_idx, pad(11)] | ||
| #! | ||
| #! Where: | ||
| #! - note_idx is the index of the output note. | ||
| #! - ASSET is the fungible or non-fungible asset of interest. | ||
| #! | ||
| #! Panics if: | ||
| #! - the fungible asset is not found in the vault. | ||
| #! - the amount of the fungible asset in the vault is less than the amount to be removed. | ||
| #! - the non-fungible asset is not found in the vault. | ||
| #! | ||
| #! Invocation: call | ||
| pub proc move_asset_to_note | ||
| # remove the asset from the account | ||
| exec.native_account::remove_asset | ||
| # => [ASSET, note_idx, pad(11)] | ||
|
|
||
| dupw dup.8 movdn.4 | ||
| # => [ASSET, note_idx, ASSET, note_idx, pad(11)] | ||
|
|
||
| exec.output_note::add_asset | ||
| # => [ASSET, note_idx, pad(11)] | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose we don't have to store it as part of the
append_and_update_frontierprocedure. It will be the caller's responsibility to retrieve the result ofappend_and_update_frontierand update that root in its account storage.