Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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]

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