diff --git a/CHANGELOG.md b/CHANGELOG.md index 98f6e294b7..252d1a478e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ ### Changes - Added validation of leaf type on CLAIM note processing to prevent message leaves from being processed as asset claims ([#2730](https://github.com/0xMiden/protocol/pull/2730)). - - Added `AssetAmount` wrapper type for validated fungible asset amounts ([#2721](https://github.com/0xMiden/protocol/pull/2721)). - [BREAKING] Renamed `ProvenBatch::new` to `new_unchecked` ([#2687](https://github.com/0xMiden/miden-base/issues/2687)). - Added `ShortCapitalString` type and related `TokenSymbol` and `RoleSymbol` types ([#2690](https://github.com/0xMiden/protocol/pull/2690)). @@ -12,6 +11,7 @@ - Added shared `ProcedurePolicy` for AuthMultisig ([#2670](https://github.com/0xMiden/protocol/pull/2670)). - [BREAKING] Changed `NoteType` encoding from 2 bits to 1 and makes `NoteType::Private` the default ([#2691](https://github.com/0xMiden/miden-base/issues/2691)). - Added `BlockNumber::saturating_sub()` ([#2660](https://github.com/0xMiden/protocol/issues/2660)). +- Added `NullifierTreeBackendReader` and `AccountTreeBackendReader` traits ([#2755](https://github.com/0xMiden/protocol/pull/2755)). ## 0.14.3 (2026-04-07) diff --git a/Cargo.lock b/Cargo.lock index 62e6e8f525..bcb8634dac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1659,8 +1659,7 @@ dependencies = [ [[package]] name = "miden-crypto" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0a034a460e27723dcfdf25effffab84331c3b46b13e7a1bd674197cc71bfe" +source = "git+https://github.com/0xmiden/crypto?branch=sergerad-largesmt-reader-trait#652f6c0b7d459ed6a4de84eb7a872a994a81e3af" dependencies = [ "blake3", "cc", @@ -1702,8 +1701,7 @@ dependencies = [ [[package]] name = "miden-crypto-derive" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8bf6ebde028e79bcc61a3632d2f375a5cc64caa17d014459f75015238cb1e08" +source = "git+https://github.com/0xmiden/crypto?branch=sergerad-largesmt-reader-trait#652f6c0b7d459ed6a4de84eb7a872a994a81e3af" dependencies = [ "quote", "syn 2.0.117", @@ -1730,8 +1728,7 @@ dependencies = [ [[package]] name = "miden-field" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38011348f4fb4c9e5ce1f471203d024721c00e3b60a91aa91aaefe6738d8b5ea" +source = "git+https://github.com/0xmiden/crypto?branch=sergerad-largesmt-reader-trait#652f6c0b7d459ed6a4de84eb7a872a994a81e3af" dependencies = [ "miden-serde-utils", "num-bigint", @@ -1921,8 +1918,7 @@ dependencies = [ [[package]] name = "miden-serde-utils" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff78082e9b4ca89863e68da01b35f8a4029ee6fd912e39fa41fde4273a7debab" +source = "git+https://github.com/0xmiden/crypto?branch=sergerad-largesmt-reader-trait#652f6c0b7d459ed6a4de84eb7a872a994a81e3af" dependencies = [ "p3-field", "p3-goldilocks", diff --git a/Cargo.toml b/Cargo.toml index 932913e36b..27d75e57b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,3 +70,7 @@ rstest = { version = "0.26" } serde = { default-features = false, version = "1.0" } thiserror = { default-features = false, version = "2.0" } tokio = { default-features = false, features = ["sync"], version = "1" } + +[patch.crates-io] +miden-crypto = { branch = "sergerad-largesmt-reader-trait", git = "https://github.com/0xmiden/crypto" } +miden-serde-utils = { branch = "sergerad-largesmt-reader-trait", git = "https://github.com/0xmiden/crypto" } diff --git a/crates/miden-protocol/src/block/account_tree/backend.rs b/crates/miden-protocol/src/block/account_tree/backend.rs index 58963f0e44..bdcc8e4329 100644 --- a/crates/miden-protocol/src/block/account_tree/backend.rs +++ b/crates/miden-protocol/src/block/account_tree/backend.rs @@ -5,19 +5,22 @@ use super::{AccountId, AccountIdKey, AccountIdPrefix, AccountTree, AccountTreeEr use crate::Word; use crate::crypto::merkle::MerkleError; #[cfg(feature = "std")] -use crate::crypto::merkle::smt::{LargeSmt, LargeSmtError, SmtStorage}; +use crate::crypto::merkle::smt::{LargeSmt, LargeSmtError, SmtStorage, SmtStorageReader}; use crate::crypto::merkle::smt::{LeafIndex, MutationSet, SMT_DEPTH, Smt, SmtLeaf, SmtProof}; -// ACCOUNT TREE BACKEND +// ACCOUNT TREE BACKEND READER // ================================================================================================ /// This trait abstracts over different SMT backends (e.g., `Smt` and `LargeSmt`) to allow /// the `AccountTree` to work with either implementation transparently. /// +/// This trait contains only read-only methods. For write methods, see +/// [`AccountTreeBackend`]. +/// /// Implementors must provide `Default` for creating empty instances. Users should /// instantiate the backend directly (potentially with entries) and then pass it to /// [`AccountTree::new`]. -pub trait AccountTreeBackend: Sized { +pub trait AccountTreeBackendReader: Sized { type Error: core::error::Error + Send + 'static; /// Returns the number of leaves in the SMT. @@ -29,6 +32,27 @@ pub trait AccountTreeBackend: Sized { /// Opens the leaf at the given key, returning a Merkle proof. fn open(&self, key: &Word) -> SmtProof; + /// Returns the value associated with the given key. + fn get_value(&self, key: &Word) -> Word; + + /// Returns the leaf at the given key. + fn get_leaf(&self, key: &Word) -> SmtLeaf; + + /// Returns the root of the SMT. + fn root(&self) -> Word; +} + +// ACCOUNT TREE BACKEND +// ================================================================================================ + +/// Extension trait for [`AccountTreeBackendReader`] that provides write methods. +pub trait AccountTreeBackend: AccountTreeBackendReader { + /// Computes the mutation set required to apply the given updates to the SMT. + fn compute_mutations( + &self, + updates: Vec<(Word, Word)>, + ) -> Result, Self::Error>; + /// Applies the given mutation set to the SMT. fn apply_mutations( &mut self, @@ -43,29 +67,14 @@ pub trait AccountTreeBackend: Sized { set: MutationSet, ) -> Result, Self::Error>; - /// Computes the mutation set required to apply the given updates to the SMT. - fn compute_mutations( - &self, - updates: Vec<(Word, Word)>, - ) -> Result, Self::Error>; - /// Inserts a key-value pair into the SMT, returning the previous value at that key. fn insert(&mut self, key: Word, value: Word) -> Result; - - /// Returns the value associated with the given key. - fn get_value(&self, key: &Word) -> Word; - - /// Returns the leaf at the given key. - fn get_leaf(&self, key: &Word) -> SmtLeaf; - - /// Returns the root of the SMT. - fn root(&self) -> Word; } -// BACKEND IMPLEMENTATION FOR SMT +// BACKEND READER IMPLEMENTATION FOR SMT // ================================================================================================ -impl AccountTreeBackend for Smt { +impl AccountTreeBackendReader for Smt { type Error = MerkleError; fn num_leaves(&self) -> usize { @@ -80,6 +89,30 @@ impl AccountTreeBackend for Smt { Smt::open(self, key) } + fn get_value(&self, key: &Word) -> Word { + Smt::get_value(self, key) + } + + fn get_leaf(&self, key: &Word) -> SmtLeaf { + Smt::get_leaf(self, key) + } + + fn root(&self) -> Word { + Smt::root(self) + } +} + +// BACKEND IMPLEMENTATION FOR SMT +// ================================================================================================ + +impl AccountTreeBackend for Smt { + fn compute_mutations( + &self, + updates: Vec<(Word, Word)>, + ) -> Result, Self::Error> { + Smt::compute_mutations(self, updates) + } + fn apply_mutations( &mut self, set: MutationSet, @@ -94,37 +127,18 @@ impl AccountTreeBackend for Smt { Smt::apply_mutations_with_reversion(self, set) } - fn compute_mutations( - &self, - updates: Vec<(Word, Word)>, - ) -> Result, Self::Error> { - Smt::compute_mutations(self, updates) - } - fn insert(&mut self, key: Word, value: Word) -> Result { Smt::insert(self, key, value) } - - fn get_value(&self, key: &Word) -> Word { - Smt::get_value(self, key) - } - - fn get_leaf(&self, key: &Word) -> SmtLeaf { - Smt::get_leaf(self, key) - } - - fn root(&self) -> Word { - Smt::root(self) - } } -// BACKEND IMPLEMENTATION FOR LARGE SMT +// BACKEND READER IMPLEMENTATION FOR LARGE SMT // ================================================================================================ #[cfg(feature = "std")] -impl AccountTreeBackend for LargeSmt +impl AccountTreeBackendReader for LargeSmt where - Backend: SmtStorage, + Backend: SmtStorageReader, { type Error = MerkleError; @@ -140,6 +154,34 @@ where LargeSmt::open(self, key) } + fn get_value(&self, key: &Word) -> Word { + LargeSmt::get_value(self, key) + } + + fn get_leaf(&self, key: &Word) -> SmtLeaf { + LargeSmt::get_leaf(self, key) + } + + fn root(&self) -> Word { + LargeSmt::root(self) + } +} + +// BACKEND IMPLEMENTATION FOR LARGE SMT +// ================================================================================================ + +#[cfg(feature = "std")] +impl AccountTreeBackend for LargeSmt +where + Backend: SmtStorage, +{ + fn compute_mutations( + &self, + updates: Vec<(Word, Word)>, + ) -> Result, Self::Error> { + LargeSmt::compute_mutations(self, updates).map_err(large_smt_error_to_merkle_error) + } + fn apply_mutations( &mut self, set: MutationSet, @@ -154,28 +196,9 @@ where LargeSmt::apply_mutations_with_reversion(self, set).map_err(large_smt_error_to_merkle_error) } - fn compute_mutations( - &self, - updates: Vec<(Word, Word)>, - ) -> Result, Self::Error> { - LargeSmt::compute_mutations(self, updates).map_err(large_smt_error_to_merkle_error) - } - fn insert(&mut self, key: Word, value: Word) -> Result { LargeSmt::insert(self, key, value) } - - fn get_value(&self, key: &Word) -> Word { - LargeSmt::get_value(self, key) - } - - fn get_leaf(&self, key: &Word) -> SmtLeaf { - LargeSmt::get_leaf(self, key) - } - - fn root(&self) -> Word { - LargeSmt::root(self) - } } // CONVENIENCE METHODS @@ -223,6 +246,17 @@ impl AccountTree { } } +#[cfg(feature = "std")] +impl AccountTree> +where + Backend: SmtStorage, +{ + /// Returns a read-only account tree backed by a reader view of this tree's storage. + pub fn reader(&self) -> AccountTree> { + AccountTree::new_unchecked(self.smt.reader()) + } +} + // HELPER FUNCTIONS // ================================================================================================ diff --git a/crates/miden-protocol/src/block/account_tree/mod.rs b/crates/miden-protocol/src/block/account_tree/mod.rs index e594684be1..d9130bfd02 100644 --- a/crates/miden-protocol/src/block/account_tree/mod.rs +++ b/crates/miden-protocol/src/block/account_tree/mod.rs @@ -21,7 +21,7 @@ mod witness; pub use witness::AccountWitness; mod backend; -pub use backend::AccountTreeBackend; +pub use backend::{AccountTreeBackend, AccountTreeBackendReader}; mod account_id_key; pub use account_id_key::AccountIdKey; @@ -53,7 +53,7 @@ where impl AccountTree where - S: AccountTreeBackend, + S: AccountTreeBackendReader, { // CONSTANTS // -------------------------------------------------------------------------------------------- @@ -188,6 +188,27 @@ where }) } + // HELPERS + // -------------------------------------------------------------------------------------------- + + /// Returns the SMT key of the given account ID prefix. + fn id_prefix_to_smt_key(account_id: AccountIdPrefix) -> Word { + // We construct this in such a way that we're forced to use the constants, so that when + // they're updated, the other usages of the constants are also updated. + let mut key = Word::empty(); + key[Self::KEY_PREFIX_IDX] = account_id.as_felt(); + + key + } +} + +impl AccountTree +where + S: AccountTreeBackend, +{ + // PUBLIC MUTATORS + // -------------------------------------------------------------------------------------------- + /// Computes the necessary changes to insert the specified (account ID, state commitment) pairs /// into this tree, allowing for validation before applying those changes. /// @@ -245,9 +266,6 @@ where Ok(AccountMutationSet::new(mutation_set)) } - // PUBLIC MUTATORS - // -------------------------------------------------------------------------------------------- - /// Inserts the state commitment for the given account ID, returning the previous state /// commitment associated with that ID. /// @@ -314,19 +332,6 @@ where .map_err(AccountTreeError::ApplyMutations)?; Ok(AccountMutationSet::new(reversion)) } - - // HELPERS - // -------------------------------------------------------------------------------------------- - - /// Returns the SMT key of the given account ID prefix. - fn id_prefix_to_smt_key(account_id: AccountIdPrefix) -> Word { - // We construct this in such a way that we're forced to use the constants, so that when - // they're updated, the other usages of the constants are also updated. - let mut key = Word::empty(); - key[Self::KEY_PREFIX_IDX] = account_id.as_felt(); - - key - } } // SERIALIZATION diff --git a/crates/miden-protocol/src/block/nullifier_tree/backend.rs b/crates/miden-protocol/src/block/nullifier_tree/backend.rs index 90f0955046..77e01fba55 100644 --- a/crates/miden-protocol/src/block/nullifier_tree/backend.rs +++ b/crates/miden-protocol/src/block/nullifier_tree/backend.rs @@ -4,15 +4,18 @@ use super::{BlockNumber, Nullifier, NullifierBlock, NullifierTree, NullifierTree use crate::Word; use crate::crypto::merkle::MerkleError; #[cfg(feature = "std")] -use crate::crypto::merkle::smt::{LargeSmt, LargeSmtError, SmtStorage}; +use crate::crypto::merkle::smt::{LargeSmt, LargeSmtError, SmtStorage, SmtStorageReader}; use crate::crypto::merkle::smt::{MutationSet, SMT_DEPTH, Smt, SmtProof}; -// NULLIFIER TREE BACKEND +// NULLIFIER TREE BACKEND READER // ================================================================================================ /// This trait abstracts over different SMT backends (e.g., `Smt` and `LargeSmt`) to allow /// the `NullifierTree` to work with either implementation transparently. /// +/// This trait contains only read-only methods. For write methods, see +/// [`NullifierTreeBackend`]. +/// /// Users should instantiate the backend directly (potentially with entries) and then /// pass it to [`NullifierTree::new_unchecked`]. /// @@ -21,7 +24,7 @@ use crate::crypto::merkle::smt::{MutationSet, SMT_DEPTH, Smt, SmtProof}; /// Assumes the provided SMT upholds the guarantees of the [`NullifierTree`]. Specifically: /// - Nullifiers are only spent once and their block numbers do not change. /// - Nullifier leaf values must be valid according to [`NullifierBlock`]. -pub trait NullifierTreeBackend: Sized { +pub trait NullifierTreeBackendReader: Sized { type Error: core::error::Error + Send + 'static; /// Returns the number of entries in the SMT. @@ -33,32 +36,38 @@ pub trait NullifierTreeBackend: Sized { /// Opens the leaf at the given key, returning a Merkle proof. fn open(&self, key: &Word) -> SmtProof; - /// Applies the given mutation set to the SMT. - fn apply_mutations( - &mut self, - set: MutationSet, - ) -> Result<(), Self::Error>; + /// Returns the value associated with the given key. + fn get_value(&self, key: &Word) -> NullifierBlock; + /// Returns the root of the SMT. + fn root(&self) -> Word; +} + +// NULLIFIER TREE BACKEND +// ================================================================================================ + +/// Extension trait for [`NullifierTreeBackendReader`] that provides write methods. +pub trait NullifierTreeBackend: NullifierTreeBackendReader { /// Computes the mutation set required to apply the given updates to the SMT. fn compute_mutations( &self, updates: impl IntoIterator, ) -> Result, Self::Error>; + /// Applies the given mutation set to the SMT. + fn apply_mutations( + &mut self, + set: MutationSet, + ) -> Result<(), Self::Error>; + /// Inserts a key-value pair into the SMT, returning the previous value at that key. fn insert(&mut self, key: Word, value: NullifierBlock) -> Result; - - /// Returns the value associated with the given key. - fn get_value(&self, key: &Word) -> NullifierBlock; - - /// Returns the root of the SMT. - fn root(&self) -> Word; } -// BACKEND IMPLEMENTATION FOR SMT +// BACKEND READER IMPLEMENTATION FOR SMT // ================================================================================================ -impl NullifierTreeBackend for Smt { +impl NullifierTreeBackendReader for Smt { type Error = MerkleError; fn num_entries(&self) -> usize { @@ -73,13 +82,20 @@ impl NullifierTreeBackend for Smt { Smt::open(self, key) } - fn apply_mutations( - &mut self, - set: MutationSet, - ) -> Result<(), Self::Error> { - Smt::apply_mutations(self, set) + fn get_value(&self, key: &Word) -> NullifierBlock { + NullifierBlock::new(Smt::get_value(self, key)) + .expect("SMT should only store valid NullifierBlocks") + } + + fn root(&self) -> Word { + Smt::root(self) } +} +// BACKEND IMPLEMENTATION FOR SMT +// ================================================================================================ + +impl NullifierTreeBackend for Smt { fn compute_mutations( &self, updates: impl IntoIterator, @@ -87,29 +103,27 @@ impl NullifierTreeBackend for Smt { Smt::compute_mutations(self, updates) } + fn apply_mutations( + &mut self, + set: MutationSet, + ) -> Result<(), Self::Error> { + Smt::apply_mutations(self, set) + } + fn insert(&mut self, key: Word, value: NullifierBlock) -> Result { Smt::insert(self, key, value.into()).map(|word| { NullifierBlock::try_from(word).expect("SMT should only store valid NullifierBlocks") }) } - - fn get_value(&self, key: &Word) -> NullifierBlock { - NullifierBlock::new(Smt::get_value(self, key)) - .expect("SMT should only store valid NullifierBlocks") - } - - fn root(&self) -> Word { - Smt::root(self) - } } -// NULLIFIER TREE BACKEND FOR LARGE SMT +// BACKEND READER IMPLEMENTATION FOR LARGE SMT // ================================================================================================ #[cfg(feature = "std")] -impl NullifierTreeBackend for LargeSmt +impl NullifierTreeBackendReader for LargeSmt where - Backend: SmtStorage, + Backend: SmtStorageReader, { type Error = MerkleError; @@ -127,13 +141,25 @@ where LargeSmt::open(self, key) } - fn apply_mutations( - &mut self, - set: MutationSet, - ) -> Result<(), Self::Error> { - LargeSmt::apply_mutations(self, set).map_err(large_smt_error_to_merkle_error) + fn get_value(&self, key: &Word) -> NullifierBlock { + LargeSmt::get_value(self, key) + .try_into() + .expect("unable to create NullifierBlock") } + fn root(&self) -> Word { + LargeSmt::root(self) + } +} + +// BACKEND IMPLEMENTATION FOR LARGE SMT +// ================================================================================================ + +#[cfg(feature = "std")] +impl NullifierTreeBackend for LargeSmt +where + Backend: SmtStorage, +{ fn compute_mutations( &self, updates: impl IntoIterator, @@ -141,21 +167,18 @@ where LargeSmt::compute_mutations(self, updates).map_err(large_smt_error_to_merkle_error) } + fn apply_mutations( + &mut self, + set: MutationSet, + ) -> Result<(), Self::Error> { + LargeSmt::apply_mutations(self, set).map_err(large_smt_error_to_merkle_error) + } + fn insert(&mut self, key: Word, value: NullifierBlock) -> Result { LargeSmt::insert(self, key, value.into()).map(|word| { NullifierBlock::try_from(word).expect("SMT should only store valid NullifierBlocks") }) } - - fn get_value(&self, key: &Word) -> NullifierBlock { - LargeSmt::get_value(self, key) - .try_into() - .expect("unable to create NullifierBlock") - } - - fn root(&self) -> Word { - LargeSmt::root(self) - } } // CONVENIENCE METHODS @@ -214,6 +237,15 @@ where Ok(Self::new_unchecked(smt)) } + + /// Returns a read-only nullifier tree backed by a reader view of this tree's storage. + /// + /// The returned tree shares the same root and entries as `self`, but its storage is a + /// read-only snapshot produced by [`SmtStorage::reader`]. The returned tree cannot be + /// mutated. + pub fn reader(&self) -> NullifierTree> { + NullifierTree::new_unchecked(self.smt.reader()) + } } // HELPER FUNCTIONS diff --git a/crates/miden-protocol/src/block/nullifier_tree/mod.rs b/crates/miden-protocol/src/block/nullifier_tree/mod.rs index b85a4aebd7..c7a960998a 100644 --- a/crates/miden-protocol/src/block/nullifier_tree/mod.rs +++ b/crates/miden-protocol/src/block/nullifier_tree/mod.rs @@ -16,7 +16,7 @@ use crate::utils::serde::{ use crate::{Felt, Word}; mod backend; -pub use backend::NullifierTreeBackend; +pub use backend::{NullifierTreeBackend, NullifierTreeBackendReader}; mod witness; pub use witness::NullifierWitness; @@ -51,7 +51,7 @@ where impl NullifierTree where - Backend: NullifierTreeBackend, + Backend: NullifierTreeBackendReader, { // CONSTANTS // -------------------------------------------------------------------------------------------- @@ -116,6 +116,14 @@ where Some(nullifier_block.into()) } +} + +impl NullifierTree +where + Backend: NullifierTreeBackend, +{ + // PUBLIC MUTATORS + // -------------------------------------------------------------------------------------------- /// Computes a mutation set resulting from inserting the provided nullifiers into this nullifier /// tree. @@ -153,9 +161,6 @@ where Ok(NullifierMutationSet::new(mutation_set)) } - // PUBLIC MUTATORS - // -------------------------------------------------------------------------------------------- - /// Marks the given nullifier as spent at the given block number. /// /// # Errors