Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
787e3e8
refactor: rust code updated
Fumuran Dec 10, 2025
d4db19d
refactor: building state
Fumuran Dec 10, 2025
81a9040
refactor: updating tests part 1
Fumuran Dec 10, 2025
191adeb
refactor: updating tests -- event error WiP
Fumuran Dec 10, 2025
a4f98a7
refactor: update component procs usage, update proc imports
Fumuran Dec 11, 2025
bd5f15b
Merge branch 'next' into andrew-migrate-to-v20-vm
Fumuran Dec 11, 2025
96d1c37
refactor: update imports in nullifier_tree, update formatting
Fumuran Dec 11, 2025
facd51f
chore: update changelog
Fumuran Dec 11, 2025
b6e5295
docs: fix docs
Fumuran Dec 11, 2025
3feb14a
chore: use 0.20.1 version of the VM
Fumuran Dec 15, 2025
a1cdf82
Merge branch 'next' into andrew-migrate-to-v20-vm
Fumuran Dec 15, 2025
a0f5f08
docs: remove LibraryPath mention from docs
Fumuran Dec 15, 2025
de4774a
refactor: rename stdlib to core lib
Fumuran Dec 15, 2025
69f392c
refactor: remove in_debug_mode
Fumuran Dec 15, 2025
ce37ad5
Merge branch 'next' into andrew-migrate-to-v20-vm
bobbinth Dec 16, 2025
c538b88
refactor: update changelog, clean code, rename get_procedure_root_by_…
Fumuran Dec 16, 2025
24d6195
Merge branch 'andrew-migrate-to-v20-vm' of https://github.com/0xMiden…
Fumuran Dec 16, 2025
e0059ee
feat: initial implementation
Fumuran Dec 18, 2025
f1931e1
refactor: update mem_store_double_word proc, rename file
Fumuran Dec 19, 2025
107d4a2
chore: update changelog
Fumuran Dec 19, 2025
7c41b56
Merge branch 'next' into andrew-keccak-mmr-frontier
Fumuran Dec 22, 2025
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [BREAKING] Refactored storage slots to be accessed by names instead of indices ([#1987](https://github.com/0xMiden/miden-base/pull/1987), [#2025](https://github.com/0xMiden/miden-base/pull/2025), [#2149](https://github.com/0xMiden/miden-base/pull/2149), [#2150](https://github.com/0xMiden/miden-base/pull/2150), [#2153](https://github.com/0xMiden/miden-base/pull/2153), [#2154](https://github.com/0xMiden/miden-base/pull/2154), [#2160](https://github.com/0xMiden/miden-base/pull/2160), [#2161](https://github.com/0xMiden/miden-base/pull/2161), [#2170](https://github.com/0xMiden/miden-base/pull/2170)).
- [BREAKING] Allowed account components to share identical account code procedures ([#2164](https://github.com/0xMiden/miden-base/pull/2164)).
- Add `From<&ExecutedTransaction> for TransactionHeader` implementation ([#2178](https://github.com/0xMiden/miden-base/pull/2178)).
- Implement keccak-based MMR frontier for the newly created `agglayer::collections` module ([#2202](https://github.com/0xMiden/miden-base/pull/2202)).

### Changes

Expand All @@ -26,6 +27,7 @@
- [BREAKING] Renamed `AccountProcedureInfo` into `AccountProcedureRoot` and remove storage offset and size ([#2162](https://github.com/0xMiden/miden-base/pull/2162)).
- [BREAKING] Made `AccountProcedureIndexMap` construction infallible ([#2163](https://github.com/0xMiden/miden-base/pull/2163)).
- [BREAKING] Renamed `tracked_procedure_roots_slot` to `trigger_procedure_roots_slot` in ACL auth components for naming consistency ([#2166](https://github.com/0xMiden/miden-base/pull/2166)).
- [BREAKING] Migrated to `miden-vm` v0.20 and `miden-crypto` v0.19 ([#2158](https://github.com/0xMiden/miden-base/pull/2158)).
- [BREAKING] Refactor `miden-objects` and `miden-lib` into `miden-protocol` and `miden-standards` ([#2184](https://github.com/0xMiden/miden-base/pull/2184), [#2191](https://github.com/0xMiden/miden-base/pull/2191), [#2197](https://github.com/0xMiden/miden-base/pull/2197)).
- [BREAKING] Migrated to `miden-vm` v0.20 and `miden-crypto` v0.19 ([#2158](https://github.com/0xMiden/miden-base/pull/2158)).
- [BREAKING] Refactored `AccountStorageDelta` to use a new `StorageSlotDelta` type ([#2182](https://github.com/0xMiden/miden-base/pull/2182)).
Expand Down
262 changes: 262 additions & 0 deletions crates/miden-lib/asm/agglayer/collections/mmr_frontier32_keccak.masm
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
Comment on lines +23 to +24
Copy link
Contributor

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_frontier procedure. It will be the caller's responsibility to retrieve the result of append_and_update_frontier and update that root in its account storage.


# 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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:

[num_leaves, root, frontier_L[0], frontier_R[0], frontier_L[1], frontier_R[1], ... ]

Copy link
Contributor

Choose a reason for hiding this comment

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

I would probably store the zeros separately from the above expected layout.
The reason is that this layout is expected to be provided by the caller of this procedure, whereas the zeros are internal, and not provided by the user.

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# store the new leaf to the local memory
loc_storew.0 dropw
loc_storew.4 dropw
# set CUR_HASH = NEW_LEAF and store to local memory
loc_storew.0 dropw
loc_storew.4 dropw

(see a comment below about where CUR_HASH comes from

# => [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
Copy link
Contributor

Choose a reason for hiding this comment

The 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]
Copy link
Contributor

Choose a reason for hiding this comment

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

Following the algorithm description here, CUR_HASH has the value of NEW_LEAF in the first iteration - let's make this explicit in the inline comments, because it's not clear w/o knowing the algorithm in detail where CUR_HASH suddenly appears from


# 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)
Copy link
Contributor

Choose a reason for hiding this comment

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

I would avoid using num_leaves as a mutable variable (the actual number of leaves doesn't change throughout the algorithm), and instead follow the convention to set:

let mut idx = num_leaves;

at the beginning of the procedure, and just use idx later

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])
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
61 changes: 61 additions & 0 deletions crates/miden-lib/asm/miden/contracts/wallets/basic.masm
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
Loading