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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Added `AccountComponent::has_procedure(root)` helper ([#2974](https://github.com/0xMiden/protocol/pull/2974)).
- Optimized protocol MASM stack-cleaning sequences, saving 1 cycle per occurrence across 9 single-element-extraction procedures ([#3041](https://github.com/0xMiden/protocol/pull/3041)).
- [BREAKING] Refactored `TokenPolicyManager` by adding `invoke_send_policy` / `invoke_receive_policy` wrappers (stored in the protocol reserved asset callback slots) that read the active policy root from the new `active_send_policy_proc_root` / `active_receive_policy_proc_root` storage slots ([#3047](https://github.com/0xMiden/protocol/pull/3047)).
- [BREAKING] Changed `asset_vault::get_asset` and `asset_vault::peek_asset` to accept a pre-hashed `ASSET_KEY_HASH` instead of a raw `ASSET_KEY`, and made `asset_vault::hash_asset_key` public; fungible add/remove now hash the vault key once, eliminating a redundant `poseidon2::hash` per operation ([#3073](https://github.com/0xMiden/protocol/pull/3073)).
- Added a definition of the Miden operator on the architecture overview page and linked it from the note lifecycle ([#3017](https://github.com/0xMiden/protocol/pull/3017)).
- Clarified Miden's operational roles on the architecture overview page and linked them from the note lifecycle ([#3017](https://github.com/0xMiden/protocol/pull/3017)).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,10 @@ pub proc get_asset
emit.ACCOUNT_VAULT_BEFORE_GET_ASSET_EVENT
# => [ASSET_KEY, vault_root_ptr]

# hash the asset vault key before using it as the SMT key
exec.asset_vault::hash_asset_key
# => [ASSET_KEY_HASH, vault_root_ptr]

# get the asset
exec.asset_vault::get_asset
# => [ASSET_VALUE]
Comment on lines +802 to 808

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would keep key hashing internal to the vault, so that callers of the module's procedure do not have to think about this detail.

So, get_asset stays as it was before the PR (takes ASSET_KEY).

And if we change peek and set asset to this:

#! Inputs:  [ASSET_KEY_HASH, vault_root_ptr]
#! Outputs: [ASSET_VALUE]
pub proc peek_asset

#! Inputs:  [ASSET_VALUE, ASSET_KEY_HASH, VAULT_ROOT]
#! Outputs: [OLD_VALUE, NEW_VAULT_ROOT]
proc set_asset

Then we should be able to avoid double hashing in add_fungible_asset and remove_fungible_asset as well, right? We should be able to keep hash_asset_key private.

That way, all asset key hashing is done in asset_vault and callers always provide ASSET_KEY uniformly.

Expand All @@ -822,6 +826,10 @@ pub proc get_initial_asset
emit.ACCOUNT_VAULT_BEFORE_GET_ASSET_EVENT
# => [ASSET_KEY, init_native_vault_root_ptr]

# hash the asset vault key before using it as the SMT key
exec.asset_vault::hash_asset_key
# => [ASSET_KEY_HASH, init_native_vault_root_ptr]

# get the asset
exec.asset_vault::get_asset
# => [ASSET_VALUE]
Expand Down
122 changes: 55 additions & 67 deletions crates/miden-protocol/asm/kernels/transaction/lib/asset_vault.masm
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,16 @@ const ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND="failed to remove non-exi
# ACCESSORS
# =================================================================================================

#! Returns the ASSET_VALUE associated with the provided asset vault key.
#! Returns the ASSET_VALUE associated with the provided hashed asset vault key.
#!
#! Inputs: [ASSET_KEY, vault_root_ptr]
#! Inputs: [ASSET_KEY_HASH, vault_root_ptr]
#! Outputs: [ASSET_VALUE]
#!
#! Where:
#! - vault_root_ptr is a pointer to the memory location at which the vault root is stored.
#! - ASSET_KEY is the asset vault key of the asset to fetch.
#! - ASSET_KEY_HASH is the hashed asset vault key of the asset to fetch (see hash_asset_key).
#! - ASSET_VALUE is the value of the asset from the vault, which can be the EMPTY_WORD if it isn't present.
pub proc get_asset
# hash the asset vault key before using it as the SMT key
exec.hash_asset_key
# => [ASSET_KEY_HASH, vault_root_ptr]

# load the asset vault root from memory
padw movup.8 mem_loadw_le
# => [ASSET_VAULT_ROOT, ASSET_KEY_HASH]
Expand All @@ -45,7 +41,7 @@ pub proc get_asset
# => [ASSET_VALUE]
end

#! Returns the _peeked_ asset associated with the provided asset vault key.
#! Returns the _peeked_ asset associated with the provided hashed asset vault key.
#!
#! WARNING: Peeked means the asset is loaded from the advice provider, which is susceptible to
#! manipulation from a malicious host. Therefore this should only be used when the inclusion of the
Expand All @@ -61,18 +57,14 @@ end
#! merkle paths from the merkle store, since this is only possible for the account vault. Ensure
#! that the merkle paths are present prior to calling.
#!
#! Inputs: [ASSET_KEY, vault_root_ptr]
#! Inputs: [ASSET_KEY_HASH, vault_root_ptr]
#! Outputs: [ASSET_VALUE]
#!
#! Where:
#! - vault_root_ptr is a pointer to the memory location at which the vault root is stored.
#! - ASSET_KEY is the asset vault key of the asset to fetch.
#! - ASSET_KEY_HASH is the hashed asset vault key of the asset to fetch (see hash_asset_key).
#! - ASSET_VALUE is the retrieved asset.
pub proc peek_asset
# hash the asset vault key before using it as the SMT key
exec.hash_asset_key
# => [ASSET_KEY_HASH, vault_root_ptr]

# load the asset vault root from memory
padw movup.8 mem_loadw_le
# => [ASSET_VAULT_ROOT, ASSET_KEY_HASH]
Expand Down Expand Up @@ -130,41 +122,45 @@ pub proc add_fungible_asset
movup.8 loc_store.0
# => [ASSET_KEY, ASSET_VALUE]

# hash the asset vault key once; it is used as the SMT key for both the peek and the set below
exec.hash_asset_key
# => [ASSET_KEY_HASH, ASSET_VALUE]

dupw loc_load.0 movdn.4
# => [ASSET_KEY, vault_root_ptr, ASSET_KEY, ASSET_VALUE]
# => [ASSET_KEY_HASH, vault_root_ptr, ASSET_KEY_HASH, ASSET_VALUE]

exec.peek_asset
# => [INITIAL_ASSET_VALUE, ASSET_KEY, ASSET_VALUE]
# => [INITIAL_ASSET_VALUE, ASSET_KEY_HASH, ASSET_VALUE]

# since we have peeked the value, we need to later assert that the actual value matches this
# one, so we'll keep a copy for later
swapw dupw.1
# => [INITIAL_ASSET_VALUE, ASSET_KEY, INITIAL_ASSET_VALUE, ASSET_VALUE]
# => [INITIAL_ASSET_VALUE, ASSET_KEY_HASH, INITIAL_ASSET_VALUE, ASSET_VALUE]

movupw.3
# => [ASSET_VALUE, INITIAL_ASSET_VALUE, ASSET_KEY, INITIAL_ASSET_VALUE]
# => [ASSET_VALUE, INITIAL_ASSET_VALUE, ASSET_KEY_HASH, INITIAL_ASSET_VALUE]

# Merge the assets.
# ---------------------------------------------------------------------------------------------

exec.fungible_asset::merge
# => [FINAL_ASSET_VALUE, ASSET_KEY, INITIAL_ASSET_VALUE]
# => [FINAL_ASSET_VALUE, ASSET_KEY_HASH, INITIAL_ASSET_VALUE]

swapw
# => [ASSET_KEY, FINAL_ASSET_VALUE, INITIAL_ASSET_VALUE]
# => [ASSET_KEY_HASH, FINAL_ASSET_VALUE, INITIAL_ASSET_VALUE]

# Insert the merged asset.
# ---------------------------------------------------------------------------------------------

# load the vault root
padw loc_load.0 mem_loadw_le
# => [VAULT_ROOT, ASSET_KEY, FINAL_ASSET_VALUE, INITIAL_ASSET_VALUE]
# => [VAULT_ROOT, ASSET_KEY_HASH, FINAL_ASSET_VALUE, INITIAL_ASSET_VALUE]

swapw dupw.2
# => [FINAL_ASSET_VALUE, ASSET_KEY, VAULT_ROOT, FINAL_ASSET_VALUE, INITIAL_ASSET_VALUE]
# => [FINAL_ASSET_VALUE, ASSET_KEY_HASH, VAULT_ROOT, FINAL_ASSET_VALUE, INITIAL_ASSET_VALUE]

# hash the asset key and update the asset in the vault
exec.set_asset
# update the asset in the vault
exec.smt::set
# => [PREV_VAULT_VALUE, NEW_VAULT_ROOT, FINAL_ASSET_VALUE, INITIAL_ASSET_VALUE]

# assert PREV_VAULT_VALUE = INITIAL_ASSET_VALUE to make sure peek_asset returned the correct asset
Expand Down Expand Up @@ -194,20 +190,24 @@ end
#! Panics if:
#! - the vault already contains the same non-fungible asset.
pub proc add_non_fungible_asset
# hash the asset vault key before using it as the SMT key
exec.hash_asset_key
# => [ASSET_KEY_HASH, ASSET_VALUE, vault_root_ptr]

# Load VAULT_ROOT and insert asset.
# ---------------------------------------------------------------------------------------------

padw dup.12
# => [vault_root_ptr, pad(4), ASSET_KEY, ASSET_VALUE, vault_root_ptr]
# => [vault_root_ptr, pad(4), ASSET_KEY_HASH, ASSET_VALUE, vault_root_ptr]

mem_loadw_le swapw
# => [ASSET_KEY, VAULT_ROOT, ASSET_VALUE, vault_root_ptr]
# => [ASSET_KEY_HASH, VAULT_ROOT, ASSET_VALUE, vault_root_ptr]

dupw.2
# => [ASSET_VALUE, ASSET_KEY, VAULT_ROOT, ASSET_VALUE, vault_root_ptr]
# => [ASSET_VALUE, ASSET_KEY_HASH, VAULT_ROOT, ASSET_VALUE, vault_root_ptr]

# hash the asset key and insert the asset into the vault
exec.set_asset
# insert the asset into the vault
exec.smt::set
# => [OLD_VAL, VAULT_ROOT', ASSET_VALUE, vault_root_ptr]

# assert old value was empty
Expand Down Expand Up @@ -296,38 +296,42 @@ end
#! - the amount of the asset in the vault is less than the amount to be removed.
@locals(4)
pub proc remove_fungible_asset
# hash the asset vault key once; it is used as the SMT key for both the peek and the set below
exec.hash_asset_key
# => [ASSET_KEY_HASH, ASSET_VALUE, vault_root_ptr]

dupw movdnw.2
# => [ASSET_KEY, ASSET_VALUE, ASSET_KEY, vault_root_ptr]
# => [ASSET_KEY_HASH, ASSET_VALUE, ASSET_KEY_HASH, vault_root_ptr]

dup.12 movdn.4
# => [ASSET_KEY, vault_root_ptr, ASSET_VALUE, ASSET_KEY, vault_root_ptr]
# => [ASSET_KEY_HASH, vault_root_ptr, ASSET_VALUE, ASSET_KEY_HASH, vault_root_ptr]

exec.peek_asset
# => [INITIAL_ASSET_VALUE, ASSET_VALUE, ASSET_KEY, vault_root_ptr]
# => [INITIAL_ASSET_VALUE, ASSET_VALUE, ASSET_KEY_HASH, vault_root_ptr]

movdnw.2
# => [ASSET_VALUE, ASSET_KEY, INITIAL_ASSET_VALUE, vault_root_ptr]
# => [ASSET_VALUE, ASSET_KEY_HASH, INITIAL_ASSET_VALUE, vault_root_ptr]

dupw.2 swapw
# => [ASSET_VALUE, INITIAL_ASSET_VALUE, ASSET_KEY, INITIAL_ASSET_VALUE, vault_root_ptr]
# => [ASSET_VALUE, INITIAL_ASSET_VALUE, ASSET_KEY_HASH, INITIAL_ASSET_VALUE, vault_root_ptr]

# compute FINAL_ASSET_VALUE = INITIAL_ASSET_VALUE - ASSET_VALUE
exec.fungible_asset::split
# => [FINAL_ASSET_VALUE, ASSET_KEY, INITIAL_ASSET_VALUE, vault_root_ptr]
# => [FINAL_ASSET_VALUE, ASSET_KEY_HASH, INITIAL_ASSET_VALUE, vault_root_ptr]

# store FINAL_ASSET_VALUE so we can return it at the end
loc_storew_le.0
# => [FINAL_ASSET_VALUE, ASSET_KEY, INITIAL_ASSET_VALUE, vault_root_ptr]
# => [FINAL_ASSET_VALUE, ASSET_KEY_HASH, INITIAL_ASSET_VALUE, vault_root_ptr]

dup.12 padw movup.4 mem_loadw_le
# => [VAULT_ROOT, FINAL_ASSET_VALUE, ASSET_KEY, INITIAL_ASSET_VALUE, vault_root_ptr]
# => [VAULT_ROOT, FINAL_ASSET_VALUE, ASSET_KEY_HASH, INITIAL_ASSET_VALUE, vault_root_ptr]

movdnw.2
# => [FINAL_ASSET_VALUE, ASSET_KEY, VAULT_ROOT, INITIAL_ASSET_VALUE, vault_root_ptr]
# => [FINAL_ASSET_VALUE, ASSET_KEY_HASH, VAULT_ROOT, INITIAL_ASSET_VALUE, vault_root_ptr]

# hash the asset key and update the asset in the vault; the old value is asserted below to be
# equivalent to the peeked value provided via peek_asset
exec.set_asset
# update the asset in the vault; the old value is asserted below to be equivalent to the
# peeked value provided via peek_asset
exec.smt::set
# => [OLD_VALUE, NEW_VAULT_ROOT, INITIAL_ASSET_VALUE, vault_root_ptr]

dupw.2
Expand Down Expand Up @@ -371,16 +375,20 @@ end
#! Panics if:
#! - the non-fungible asset is not found in the vault.
pub proc remove_non_fungible_asset
# hash the asset vault key before using it as the SMT key
exec.hash_asset_key
# => [ASSET_KEY_HASH, ASSET_VALUE, vault_root_ptr]

# load vault root
padw dup.12 mem_loadw_le
# => [VAULT_ROOT, ASSET_KEY, ASSET_VALUE, vault_root_ptr]
# => [VAULT_ROOT, ASSET_KEY_HASH, ASSET_VALUE, vault_root_ptr]

# prepare insertion of an EMPTY_WORD into the vault at the asset key to remove the asset
# prepare insertion of an EMPTY_WORD into the vault at the hashed asset key to remove the asset
swapw padw
# => [EMPTY_WORD, ASSET_KEY, VAULT_ROOT, ASSET_VALUE, vault_root_ptr]
# => [EMPTY_WORD, ASSET_KEY_HASH, VAULT_ROOT, ASSET_VALUE, vault_root_ptr]

# hash the asset key and insert the empty word into the vault to remove the asset
exec.set_asset
# insert the empty word into the vault to remove the asset
exec.smt::set
# => [REMOVED_ASSET_VALUE, NEW_VAULT_ROOT, ASSET_VALUE, vault_root_ptr]

# dup ASSET_VALUE so it survives the assert; the assert proves it equals REMOVED_ASSET_VALUE
Expand Down Expand Up @@ -447,27 +455,7 @@ end
#!
#! Inputs: [ASSET_KEY]
#! Outputs: [ASSET_KEY_HASH]
proc hash_asset_key
pub proc hash_asset_key
exec.poseidon2::hash
# => [ASSET_KEY_HASH]
end

#! Hashes the raw asset vault key and writes ASSET_VALUE into the asset vault SMT at the hashed key,
#! returning the previous value stored there.
#!
#! Inputs: [ASSET_VALUE, ASSET_KEY, VAULT_ROOT]
#! Outputs: [OLD_VALUE, NEW_VAULT_ROOT]
#!
#! Where:
#! - ASSET_KEY is the raw (unhashed) asset vault key.
#! - ASSET_VALUE is the value to write into the vault at the hashed key.
#! - VAULT_ROOT is the current root of the asset vault SMT.
#! - OLD_VALUE is the value previously stored at the hashed key.
#! - NEW_VAULT_ROOT is the root of the asset vault SMT after the write.
proc set_asset
swapw exec.hash_asset_key swapw
# => [ASSET_VALUE, ASSET_KEY_HASH, VAULT_ROOT]

exec.smt::set
# => [OLD_VALUE, NEW_VAULT_ROOT]
end
11 changes: 6 additions & 5 deletions crates/miden-protocol/asm/kernels/transaction/lib/epilogue.masm
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ const EPILOGUE_AUTH_PROC_END_EVENT=event("miden::protocol::epilogue::auth_proc_e
# number of cycles, and so we only need to add the difference.
const SMT_SET_ADDITIONAL_CYCLES=250

# An upper-bound estimate of the cycles needed to hash the asset vault key before the fee-removing
# smt::set. This cost is incurred only when a fee is actually removed from the asset vault, and is
# kept separate from SMT_SET_ADDITIONAL_CYCLES (which models smt::set's own best/worst-case spread).
# An upper-bound estimate of the cycles needed to hash the asset vault key inside the fee-removing
# remove_fungible_asset (the key is hashed once, at the start of the procedure). This cost is
# incurred only when a fee is actually removed from the asset vault, and is kept separate from
# SMT_SET_ADDITIONAL_CYCLES (which models smt::set's own best/worst-case spread).
# It is additive rather than double-counted: the lowest-observed NUM_POST_COMPUTE_FEE_CYCLES below
# comes from a zero-fee transaction that skips fee removal entirely, and so excludes this hashing.
const VAULT_KEY_HASH_CYCLES=50
const VAULT_KEY_HASH_CYCLES=30

# The number of cycles the epilogue is estimated to take after compute_fee has been executed,
# including an unknown cycle number of the above-mentioned call to smt::set. It is safe to assume
Expand All @@ -61,7 +62,7 @@ const VAULT_KEY_HASH_CYCLES=50
const NUM_POST_COMPUTE_FEE_CYCLES=608

# Upper bound on the post-compute_fee cycle count; it must stay an upper bound or the verification
# fee is undercharged. Worst case observed is 863 cycles (45 of margin); re-measure when the
# fee is undercharged. Worst case observed is 843 cycles (45 of margin); re-measure when the
# epilogue or smt::set changes. An adversary cannot inflate the fee-removal smt::set cost: vault
# keys are hashed before insertion (see AssetVaultKey::hash). The multi-leaf smt::set worst case is
# not yet exercised (see test_fee.rs TODO).
Expand Down
4 changes: 4 additions & 0 deletions crates/miden-testing/src/kernel_tests/tx/test_asset_vault.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ async fn peek_asset_returns_correct_asset() -> anyhow::Result<()> {
emit.event("miden::protocol::account::vault_before_get_asset")
# => [ASSET_KEY, account_vault_root_ptr]

# hash the asset vault key before using it as the SMT key
exec.asset_vault::hash_asset_key
# => [ASSET_KEY_HASH, account_vault_root_ptr]

exec.asset_vault::peek_asset
# => [PEEKED_ASSET_VALUE]

Expand Down
5 changes: 5 additions & 0 deletions crates/miden-testing/src/kernel_tests/tx/test_faucet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ async fn test_mint_fungible_asset_succeeds() -> anyhow::Result<()> {
# assert the input vault has been updated
push.{INPUT_VAULT_ROOT_PTR}
push.{FUNGIBLE_ASSET_KEY}
exec.asset_vault::hash_asset_key
exec.asset_vault::get_asset
# => [ASSET_VALUE]

Expand Down Expand Up @@ -256,6 +257,7 @@ async fn test_mint_non_fungible_asset_succeeds() -> anyhow::Result<()> {
# assert the input vault has been updated.
push.{INPUT_VAULT_ROOT_PTR}
push.{NON_FUNGIBLE_ASSET_KEY}
exec.asset_vault::hash_asset_key
exec.asset_vault::get_asset
push.{NON_FUNGIBLE_ASSET_VALUE}
assert_eqw.err="vault should contain asset"
Expand Down Expand Up @@ -406,6 +408,7 @@ async fn test_burn_fungible_asset_succeeds() -> anyhow::Result<()> {
push.{INPUT_VAULT_ROOT_PTR}

push.{FUNGIBLE_ASSET_KEY}
exec.asset_vault::hash_asset_key
exec.asset_vault::get_asset
# => [ASSET_VALUE]

Expand Down Expand Up @@ -555,6 +558,7 @@ async fn test_burn_non_fungible_asset_succeeds() -> anyhow::Result<()> {
# check that the non-fungible asset is presented in the input vault
push.{INPUT_VAULT_ROOT_PTR}
push.{NON_FUNGIBLE_ASSET_KEY}
exec.asset_vault::hash_asset_key
exec.asset_vault::get_asset
push.{NON_FUNGIBLE_ASSET_VALUE}
assert_eqw.err="input vault should contain the asset"
Expand All @@ -568,6 +572,7 @@ async fn test_burn_non_fungible_asset_succeeds() -> anyhow::Result<()> {
# assert the input vault has been updated and does not have the burnt asset
push.{INPUT_VAULT_ROOT_PTR}
push.{NON_FUNGIBLE_ASSET_KEY}
exec.asset_vault::hash_asset_key
exec.asset_vault::get_asset
# the returned word should be empty, indicating the asset is absent
padw assert_eqw.err="input vault should not contain burned asset"
Expand Down
2 changes: 1 addition & 1 deletion crates/miden-testing/src/kernel_tests/tx/test_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async fn num_tx_cycles_after_compute_fee_are_less_than_estimated(
// These constants should always be updated together with the equivalent constants in
// epilogue.masm.
const SMT_SET_ADDITIONAL_CYCLES: usize = 250;
const VAULT_KEY_HASH_CYCLES: usize = 50;
const VAULT_KEY_HASH_CYCLES: usize = 30;
const NUM_POST_COMPUTE_FEE_CYCLES: usize = 608;

assert!(
Expand Down
Loading