feat: hash AssetVaultKey before insertion into asset vault SMT#2912
feat: hash AssetVaultKey before insertion into asset vault SMT#2912partylikeits1983 wants to merge 9 commits into
AssetVaultKey before insertion into asset vault SMT#2912Conversation
AssetVaultKey before insertion into asset vault SMT
|
This is something we need to do but it'll also complicate asset retrieval from the |
PhilippGackstatter
left a comment
There was a problem hiding this comment.
I looked at parts of the asset vault related changes and left some comments.
Two non-fungible assets issued by the same faucet share the third element of their raw AssetVaultKey (the faucet ID suffix), which the SMT uses to determine leaf membership. Without hashing, they always landed in the same leaf, risking the per-leaf cap and harming tree balance. Mirrors the existing raw-key/hashed-key pattern used by StorageMap: - AssetVaultKey::to_smt_key() returns Poseidon2 hash of the raw key. - AssetVault and PartialVault store the SMT keyed by hashed keys, plus a BTreeMap<AssetVaultKey, Word> of raw keys for iteration and proofs. - AssetWitness carries both the SmtProof and the raw key(s) it covers. - The MASM kernel injects exec.poseidon2::hash before every smt::set, smt::get, smt::peek in asset_vault.masm (same as hash_map_key in account.masm). Closes #2518.
- Make `PartialVault::add` atomic: extend `entries` only after SMT proof insertion succeeds, preserving the entries-subset-of-SMT invariant on error. - Filter zero-amount fungible assets out of `AssetVault::new`'s `entries` map so the map and the underlying SMT stay in sync (the SMT treats empty values as no-ops). - Correct the suffix/prefix wording: `LeafIndex<Word>` uses `value[3]`, which is the faucet ID prefix, not the suffix. Fixed in `vault_key.rs`, `vault/mod.rs` doc + test, and `docs/src/asset.md`. - Tighten `AssetWitness::new_unchecked`: document the proof-vs-pair precondition explicitly and `debug_assert!` it. - Add `PartialVault::with_witnesses` and `PartialVault::add` tests: happy-path (with round-trip serialization for `with_witnesses`) and root-mismatch failure paths (the `add` failure doubles as a regression test for the atomicity fix above).
cbb11d4 to
59521ea
Compare
- Rename `AssetVaultKey::to_smt_key` to `hash` and have it return a new
`AssetVaultKeyHash` newtype (mirrors `StorageMapKey::hash` ->
`StorageMapKeyHash`). Updates all call sites and the MASM
`hash_asset_key` doc to point at `AssetVaultKey::hash`.
- Restrict `AssetWitness::entries` to `pub(super)`; it is an internal
helper used by `PartialVault` and not part of the public surface.
- Rework `Deserializable for PartialVault` to use `read_many_iter` for
`num_entries` (protects against unbounded length prefixes) and route
through a new private `from_partial_smt_and_keys` constructor that
performs the per-entry validation. Repurposes the previously-dead
`PartialAssetVaultError::InvalidAssetInSmt` variant as
`InvalidAssetForKey { key, value, source }`.
…masm Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com>
…-vault-key-smt # Conflicts: # crates/miden-protocol/src/asset/vault/mod.rs # crates/miden-protocol/src/asset/vault/vault_key.rs # crates/miden-protocol/src/errors/mod.rs
Hashing the AssetVaultKey before the asset vault SMT set adds a fixed cost to the fee-removal smt::set in the epilogue, pushing the measured post-compute-fee cycle count (863) past the old estimate (858). Add a dedicated VAULT_KEY_HASH_CYCLES=50 constant (in epilogue.masm and the mirrored test) rather than inflating SMT_SET_ADDITIONAL_CYCLES, whose documented meaning is smt::set's worst-minus-best spread. The fee estimate stays a safe upper bound (608+250+50=908 > 863).
Fumuran
left a comment
There was a problem hiding this comment.
Looks good! For now I only (yet) reviewed the masm code, and left a few comments and questions
| # ASSET KEY HASHING | ||
| #! Hashes the raw asset vault key into the key used by the asset vault SMT. | ||
| #! | ||
| #! See [`AssetVaultKey::to_smt_key`] in Rust for the rationale. | ||
| #! | ||
| #! Inputs: [ASSET_KEY] | ||
| #! Outputs: [HASHED_ASSET_KEY] | ||
| proc hash_asset_key | ||
| exec.poseidon2::hash | ||
| # => [HASHED_ASSET_KEY] | ||
| end | ||
|
|
There was a problem hiding this comment.
nit: I would move it to the bottom of the file, since it is kind of helper procedure
| # hash the asset key before using it as the SMT key | ||
| swapw exec.hash_asset_key swapw | ||
| # => [MERGED_ASSET_VALUE, HASHED_ASSET_KEY, VAULT_ROOT, CUR_VAULT_VALUE, MERGED_ASSET_VALUE] | ||
|
|
There was a problem hiding this comment.
question: I wonder whether should we create a set_asset procedure to accompany the get_asset one, which would hash the provided key internally. It is fine as is, I just thought that it will be a bit more consistent and will save a bit of code: we are setting an asset at least four times in this module.
| # fee is undercharged. The most recently observed worst case is 863 cycles, leaving 45 cycles of | ||
| # margin against this 908-cycle budget; the margin is empirical, so re-measure when the epilogue or | ||
| # smt::set changes. The fee-removal smt::set cost cannot be inflated by an adversary: vault keys are | ||
| # hashed before insertion (see AssetVaultKey::hash), so leaf indices are uniformly distributed and | ||
| # entries cannot be concentrated into the fee asset's leaf without breaking the hash's collision | ||
| # resistance. The multi-leaf smt::set worst case is not yet exercised (see test_fee.rs TODO). | ||
| const ESTIMATED_AFTER_COMPUTE_FEE_CYCLES=NUM_POST_COMPUTE_FEE_CYCLES+SMT_SET_ADDITIONAL_CYCLES+VAULT_KEY_HASH_CYCLES |
There was a problem hiding this comment.
nit: probably we can slightly shorten this description?
There was a problem hiding this comment.
yeah good catch :)
claude likes extremely long descriptions (they don't always make sense)
Summary
Closes #2518.
Two non-fungible assets issued by the same faucet share the third element of their raw
AssetVaultKey(the faucet ID suffix), which the SMT uses to determine leaf membership. Without hashing, they always landed in the same leaf, risking the per-leaf cap and degrading tree balance.This PR mirrors the existing raw-key/hashed-key pattern already used by
StorageMapin this crate:AssetVaultKey::to_smt_key()returns the Poseidon2 hash of the raw key.to_leaf_indexis rerouted through it.AssetVaultandPartialVaultnow store the SMT keyed by hashed keys, plus aBTreeMap<AssetVaultKey, Word>of raw keys for iteration and proof reconstruction.AssetWitnesscarries both theSmtProofand the raw key(s) it covers;newvalidates each(key, value)pair against the proof.hash_asset_keyhelper (exec.poseidon2::hash) and injects it before everysmt::set/smt::get/smt::peekinasset_vault.masm(same pattern ashash_map_keyinaccount.masm).read_vault_asset/read_vault_asset_witnessesupdated for the new layout.A regression test (
two_non_fungible_assets_from_same_faucet_use_different_leaves) covers the original bug end-to-end.