miden_crypto/merkle/smt/
partial.rs

1use super::{LeafIndex, SMT_DEPTH};
2use crate::{
3    EMPTY_WORD, Word,
4    hash::rpo::RpoDigest,
5    merkle::{
6        InnerNode, InnerNodeInfo, MerkleError, MerklePath, Smt, SmtLeaf, SmtProof,
7        smt::SparseMerkleTree,
8    },
9};
10
11/// A partial version of an [`Smt`].
12///
13/// This type can track a subset of the key-value pairs of a full [`Smt`] and allows for updating
14/// those pairs to compute the new root of the tree, as if the updates had been done on the full
15/// tree. This is useful so that not all leaves have to be present and loaded into memory to compute
16/// an update.
17///
18/// To facilitate this, a partial SMT requires that the merkle paths of every key-value pair are
19/// added to the tree. This means this pair is considered "tracked" and can be updated.
20///
21/// An important caveat is that only pairs whose merkle paths were added can be updated. Attempting
22/// to update an untracked value will result in an error. See [`PartialSmt::insert`] for more
23/// details.
24#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
26pub struct PartialSmt(Smt);
27
28impl PartialSmt {
29    // CONSTRUCTORS
30    // --------------------------------------------------------------------------------------------
31
32    /// Returns a new [`PartialSmt`].
33    ///
34    /// All leaves in the returned tree are set to [`Smt::EMPTY_VALUE`].
35    pub fn new() -> Self {
36        Self(Smt::new())
37    }
38
39    /// Instantiates a new [`PartialSmt`] by calling [`PartialSmt::add_path`] for all [`SmtProof`]s
40    /// in the provided iterator.
41    ///
42    /// # Errors
43    ///
44    /// Returns an error if:
45    /// - the new root after the insertion of a (leaf, path) tuple does not match the existing root
46    ///   (except if the tree was previously empty).
47    pub fn from_proofs<I>(proofs: I) -> Result<Self, MerkleError>
48    where
49        I: IntoIterator<Item = SmtProof>,
50    {
51        let mut partial_smt = Self::new();
52
53        for (proof, leaf) in proofs.into_iter().map(SmtProof::into_parts) {
54            partial_smt.add_path(leaf, proof)?;
55        }
56
57        Ok(partial_smt)
58    }
59
60    // PUBLIC ACCESSORS
61    // --------------------------------------------------------------------------------------------
62
63    /// Returns the root of the tree.
64    pub fn root(&self) -> RpoDigest {
65        self.0.root()
66    }
67
68    /// Returns an opening of the leaf associated with `key`. Conceptually, an opening is a Merkle
69    /// path to the leaf, as well as the leaf itself.
70    ///
71    /// # Errors
72    ///
73    /// Returns an error if:
74    /// - the key is not tracked by this partial SMT.
75    pub fn open(&self, key: &RpoDigest) -> Result<SmtProof, MerkleError> {
76        if !self.is_leaf_tracked(key) {
77            return Err(MerkleError::UntrackedKey(*key));
78        }
79
80        Ok(self.0.open(key))
81    }
82
83    /// Returns the leaf to which `key` maps
84    ///
85    /// # Errors
86    ///
87    /// Returns an error if:
88    /// - the key is not tracked by this partial SMT.
89    pub fn get_leaf(&self, key: &RpoDigest) -> Result<SmtLeaf, MerkleError> {
90        if !self.is_leaf_tracked(key) {
91            return Err(MerkleError::UntrackedKey(*key));
92        }
93
94        Ok(self.0.get_leaf(key))
95    }
96
97    /// Returns the value associated with `key`.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error if:
102    /// - the key is not tracked by this partial SMT.
103    pub fn get_value(&self, key: &RpoDigest) -> Result<Word, MerkleError> {
104        if !self.is_leaf_tracked(key) {
105            return Err(MerkleError::UntrackedKey(*key));
106        }
107
108        Ok(self.0.get_value(key))
109    }
110
111    // STATE MUTATORS
112    // --------------------------------------------------------------------------------------------
113
114    /// Inserts a value at the specified key, returning the previous value associated with that key.
115    /// Recall that by definition, any key that hasn't been updated is associated with
116    /// [`Smt::EMPTY_VALUE`].
117    ///
118    /// This also recomputes all hashes between the leaf (associated with the key) and the root,
119    /// updating the root itself.
120    ///
121    /// # Errors
122    ///
123    /// Returns an error if:
124    /// - the key and its merkle path were not previously added (using [`PartialSmt::add_path`]) to
125    ///   this [`PartialSmt`], which means it is almost certainly incorrect to update its value. If
126    ///   an error is returned the tree is in the same state as before.
127    pub fn insert(&mut self, key: RpoDigest, value: Word) -> Result<Word, MerkleError> {
128        if !self.is_leaf_tracked(&key) {
129            return Err(MerkleError::UntrackedKey(key));
130        }
131
132        let previous_value = self.0.insert(key, value);
133
134        // If the value was removed the SmtLeaf was removed as well by the underlying Smt
135        // implementation. However, we still want to consider that leaf tracked so it can be
136        // read and written to, so we reinsert an empty SmtLeaf.
137        if value == EMPTY_WORD {
138            let leaf_index = Smt::key_to_leaf_index(&key);
139            self.0.leaves.insert(leaf_index.value(), SmtLeaf::Empty(leaf_index));
140        }
141
142        Ok(previous_value)
143    }
144
145    /// Adds an [`SmtProof`] to this [`PartialSmt`].
146    ///
147    /// This is a convenience method which calls [`Self::add_path`] on the proof. See its
148    /// documentation for details on errors.
149    pub fn add_proof(&mut self, proof: SmtProof) -> Result<(), MerkleError> {
150        let (path, leaf) = proof.into_parts();
151        self.add_path(leaf, path)
152    }
153
154    /// Adds a leaf and its merkle path to this [`PartialSmt`].
155    ///
156    /// If this function was called, any key that is part of the `leaf` can subsequently be updated
157    /// to a new value and produce a correct new tree root.
158    ///
159    /// # Errors
160    ///
161    /// Returns an error if:
162    /// - the new root after the insertion of the leaf and the path does not match the existing root
163    ///   (except when the first leaf is added). If an error is returned, the tree is left in an
164    ///   inconsistent state.
165    pub fn add_path(&mut self, leaf: SmtLeaf, path: MerklePath) -> Result<(), MerkleError> {
166        let mut current_index = leaf.index().index;
167
168        let mut node_hash_at_current_index = leaf.hash();
169
170        // We insert directly into the leaves for two reasons:
171        // - We can directly insert the leaf as it is without having to loop over its entries to
172        //   call Smt::perform_insert.
173        // - If the leaf is SmtLeaf::Empty, we will also insert it, which means this leaf is
174        //   considered tracked by the partial SMT as it is part of the leaves map. When calling
175        //   PartialSmt::insert, this will not error for such empty leaves whose merkle path was
176        //   added, but will error for otherwise non-existent leaves whose paths were not added,
177        //   which is what we want.
178        self.0.leaves.insert(current_index.value(), leaf);
179
180        for sibling_hash in path {
181            // Find the index of the sibling node and compute whether it is a left or right child.
182            let is_sibling_right = current_index.sibling().is_value_odd();
183
184            // Move the index up so it points to the parent of the current index and the sibling.
185            current_index.move_up();
186
187            // Construct the new parent node from the child that was updated and the sibling from
188            // the merkle path.
189            let new_parent_node = if is_sibling_right {
190                InnerNode {
191                    left: node_hash_at_current_index,
192                    right: sibling_hash,
193                }
194            } else {
195                InnerNode {
196                    left: sibling_hash,
197                    right: node_hash_at_current_index,
198                }
199            };
200
201            self.0.insert_inner_node(current_index, new_parent_node);
202
203            node_hash_at_current_index = self.0.get_inner_node(current_index).hash();
204        }
205
206        // Check the newly added merkle path is consistent with the existing tree. If not, the
207        // merkle path was invalid or computed from another tree.
208        //
209        // We skip this check if we have just inserted the first leaf since we assume that leaf's
210        // root is correct and all subsequent leaves that will be added must have the same root.
211        if self.0.num_leaves() != 1 && self.root() != node_hash_at_current_index {
212            return Err(MerkleError::ConflictingRoots {
213                expected_root: self.root(),
214                actual_root: node_hash_at_current_index,
215            });
216        }
217
218        self.0.set_root(node_hash_at_current_index);
219
220        Ok(())
221    }
222
223    /// Returns true if the key's merkle path was previously added to this partial SMT and can be
224    /// sensibly updated to a new value.
225    /// In particular, this returns true for keys whose value was empty **but** their merkle paths
226    /// were added, while it returns false if the merkle paths were **not** added.
227    fn is_leaf_tracked(&self, key: &RpoDigest) -> bool {
228        self.0.leaves.contains_key(&Smt::key_to_leaf_index(key).value())
229    }
230
231    /// Returns an iterator over the inner nodes of the [`PartialSmt`].
232    pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
233        self.0.inner_nodes()
234    }
235
236    /// Returns an iterator over the tracked, non-empty leaves of the [`PartialSmt`] in arbitrary
237    /// order.
238    pub fn leaves(&self) -> impl Iterator<Item = (LeafIndex<SMT_DEPTH>, &SmtLeaf)> {
239        // The partial SMT also contains empty leaves, so we have to filter them out.
240        self.0.leaves().filter_map(
241            |(leaf_idx, leaf)| {
242                if leaf.is_empty() { None } else { Some((leaf_idx, leaf)) }
243            },
244        )
245    }
246
247    /// Returns an iterator over the tracked leaves of the [`PartialSmt`] in arbitrary order.
248    ///
249    /// Note that this includes empty leaves.
250    pub fn tracked_leaves(&self) -> impl Iterator<Item = (LeafIndex<SMT_DEPTH>, &SmtLeaf)> {
251        self.0.leaves()
252    }
253
254    /// Returns an iterator over the tracked, non-empty key-value pairs of the [`PartialSmt`] in
255    /// arbitrary order.
256    pub fn entries(&self) -> impl Iterator<Item = &(RpoDigest, Word)> {
257        self.0.entries()
258    }
259
260    /// Returns the number of tracked leaves in this tree, which includes empty ones.
261    ///
262    /// Note that this may return a different value from [Self::num_entries()] as a single leaf may
263    /// contain more than one key-value pair.
264    pub fn num_leaves(&self) -> usize {
265        self.0.num_leaves()
266    }
267
268    /// Returns the number of tracked, non-empty key-value pairs in this tree.
269    ///
270    /// Note that this may return a different value from [Self::num_leaves()] as a single leaf may
271    /// contain more than one key-value pair.
272    ///
273    /// Also note that this is currently an expensive operation as counting the number of
274    /// entries requires iterating over all leaves of the tree.
275    pub fn num_entries(&self) -> usize {
276        self.0.num_entries()
277    }
278
279    /// Returns a boolean value indicating whether the [`PartialSmt`] is empty.
280    pub fn is_empty(&self) -> bool {
281        self.0.is_empty()
282    }
283}
284
285impl Default for PartialSmt {
286    fn default() -> Self {
287        Self::new()
288    }
289}
290
291// TESTS
292// ================================================================================================
293
294#[cfg(test)]
295mod tests {
296
297    use alloc::collections::{BTreeMap, BTreeSet};
298
299    use assert_matches::assert_matches;
300    use rand_utils::rand_array;
301
302    use super::*;
303    use crate::{EMPTY_WORD, ONE, ZERO};
304
305    /// Tests that a basic PartialSmt can be built from a full one and that inserting or removing
306    /// values whose merkle path were added to the partial SMT results in the same root as the
307    /// equivalent update in the full tree.
308    #[test]
309    fn partial_smt_insert_and_remove() {
310        let key0 = RpoDigest::from(Word::from(rand_array()));
311        let key1 = RpoDigest::from(Word::from(rand_array()));
312        let key2 = RpoDigest::from(Word::from(rand_array()));
313        // A key for which we won't add a value so it will be empty.
314        let key_empty = RpoDigest::from(Word::from(rand_array()));
315
316        let value0 = Word::from(rand_array());
317        let value1 = Word::from(rand_array());
318        let value2 = Word::from(rand_array());
319
320        let mut kv_pairs = vec![(key0, value0), (key1, value1), (key2, value2)];
321
322        // Add more random leaves.
323        kv_pairs.reserve(1000);
324        for _ in 0..1000 {
325            let key = RpoDigest::from(Word::from(rand_array()));
326            let value = Word::from(rand_array());
327            kv_pairs.push((key, value));
328        }
329
330        let mut full = Smt::with_entries(kv_pairs).unwrap();
331
332        // Constructing a partial SMT from proofs succeeds.
333        // ----------------------------------------------------------------------------------------
334
335        let proof0 = full.open(&key0);
336        let proof2 = full.open(&key2);
337        let proof_empty = full.open(&key_empty);
338
339        assert!(proof_empty.leaf().is_empty());
340
341        let mut partial = PartialSmt::from_proofs([proof0, proof2, proof_empty]).unwrap();
342
343        assert_eq!(full.root(), partial.root());
344        assert_eq!(partial.get_value(&key0).unwrap(), value0);
345        let error = partial.get_value(&key1).unwrap_err();
346        assert_matches!(error, MerkleError::UntrackedKey(_));
347        assert_eq!(partial.get_value(&key2).unwrap(), value2);
348
349        // Insert new values for added keys with empty and non-empty values.
350        // ----------------------------------------------------------------------------------------
351
352        let new_value0 = Word::from(rand_array());
353        let new_value2 = Word::from(rand_array());
354        // A non-empty value for the key that was previously empty.
355        let new_value_empty_key = Word::from(rand_array());
356
357        full.insert(key0, new_value0);
358        full.insert(key2, new_value2);
359        full.insert(key_empty, new_value_empty_key);
360
361        partial.insert(key0, new_value0).unwrap();
362        partial.insert(key2, new_value2).unwrap();
363        // This updates a key whose value was previously empty.
364        partial.insert(key_empty, new_value_empty_key).unwrap();
365
366        assert_eq!(full.root(), partial.root());
367        assert_eq!(partial.get_value(&key0).unwrap(), new_value0);
368        assert_eq!(partial.get_value(&key2).unwrap(), new_value2);
369        assert_eq!(partial.get_value(&key_empty).unwrap(), new_value_empty_key);
370
371        // Remove an added key.
372        // ----------------------------------------------------------------------------------------
373
374        full.insert(key0, EMPTY_WORD);
375        partial.insert(key0, EMPTY_WORD).unwrap();
376
377        assert_eq!(full.root(), partial.root());
378        assert_eq!(partial.get_value(&key0).unwrap(), EMPTY_WORD);
379
380        // Check if returned openings are the same in partial and full SMT.
381        // ----------------------------------------------------------------------------------------
382
383        // This is a key whose value is empty since it was removed.
384        assert_eq!(full.open(&key0), partial.open(&key0).unwrap());
385        // This is a key whose value is non-empty.
386        assert_eq!(full.open(&key2), partial.open(&key2).unwrap());
387
388        // Attempting to update a key whose merkle path was not added is an error.
389        // ----------------------------------------------------------------------------------------
390
391        let error = partial.clone().insert(key1, Word::from(rand_array())).unwrap_err();
392        assert_matches!(error, MerkleError::UntrackedKey(_));
393
394        let error = partial.insert(key1, EMPTY_WORD).unwrap_err();
395        assert_matches!(error, MerkleError::UntrackedKey(_));
396    }
397
398    /// Test that we can add an SmtLeaf::Multiple variant to a partial SMT.
399    #[test]
400    fn partial_smt_multiple_leaf_success() {
401        // key0 and key1 have the same felt at index 3 so they will be placed in the same leaf.
402        let key0 = RpoDigest::from(Word::from([ZERO, ZERO, ZERO, ONE]));
403        let key1 = RpoDigest::from(Word::from([ONE, ONE, ONE, ONE]));
404        let key2 = RpoDigest::from(Word::from(rand_array()));
405
406        let value0 = Word::from(rand_array());
407        let value1 = Word::from(rand_array());
408        let value2 = Word::from(rand_array());
409
410        let full = Smt::with_entries([(key0, value0), (key1, value1), (key2, value2)]).unwrap();
411
412        // Make sure our assumption about the leaf being a multiple is correct.
413        let SmtLeaf::Multiple(_) = full.get_leaf(&key0) else {
414            panic!("expected full tree to produce multiple leaf")
415        };
416
417        let proof0 = full.open(&key0);
418        let proof2 = full.open(&key2);
419
420        let partial = PartialSmt::from_proofs([proof0, proof2]).unwrap();
421
422        assert_eq!(partial.root(), full.root());
423
424        assert_eq!(partial.get_leaf(&key0).unwrap(), full.get_leaf(&key0));
425        // key1 is present in the partial tree because it is part of the proof of key0.
426        assert_eq!(partial.get_leaf(&key1).unwrap(), full.get_leaf(&key1));
427        assert_eq!(partial.get_leaf(&key2).unwrap(), full.get_leaf(&key2));
428    }
429
430    /// Tests that adding proofs to a partial SMT whose roots are not the same will result in an
431    /// error.
432    ///
433    /// This test uses only empty values in the partial SMT.
434    #[test]
435    fn partial_smt_root_mismatch_on_empty_values() {
436        let key0 = RpoDigest::from(Word::from(rand_array()));
437        let key1 = RpoDigest::from(Word::from(rand_array()));
438        let key2 = RpoDigest::from(Word::from(rand_array()));
439
440        let value0 = EMPTY_WORD;
441        let value1 = Word::from(rand_array());
442        let value2 = EMPTY_WORD;
443
444        let kv_pairs = vec![(key0, value0)];
445
446        let mut full = Smt::with_entries(kv_pairs).unwrap();
447        // This proof will be stale after we insert another value.
448        let stale_proof0 = full.open(&key0);
449
450        // Insert a non-empty value so the root actually changes.
451        full.insert(key1, value1);
452        full.insert(key2, value2);
453
454        let proof2 = full.open(&key2);
455
456        let mut partial = PartialSmt::new();
457
458        partial.add_proof(stale_proof0).unwrap();
459        let err = partial.add_proof(proof2).unwrap_err();
460        assert_matches!(err, MerkleError::ConflictingRoots { .. });
461    }
462
463    /// Tests that adding proofs to a partial SMT whose roots are not the same will result in an
464    /// error.
465    ///
466    /// This test uses only non-empty values in the partial SMT.
467    #[test]
468    fn partial_smt_root_mismatch_on_non_empty_values() {
469        let key0 = RpoDigest::from(Word::from(rand_array()));
470        let key1 = RpoDigest::from(Word::from(rand_array()));
471        let key2 = RpoDigest::from(Word::from(rand_array()));
472
473        let value0 = Word::from(rand_array());
474        let value1 = Word::from(rand_array());
475        let value2 = Word::from(rand_array());
476
477        let kv_pairs = vec![(key0, value0), (key1, value1)];
478
479        let mut full = Smt::with_entries(kv_pairs).unwrap();
480        // This proof will be stale after we insert another value.
481        let stale_proof0 = full.open(&key0);
482
483        full.insert(key2, value2);
484
485        let proof2 = full.open(&key2);
486
487        let mut partial = PartialSmt::new();
488
489        partial.add_proof(stale_proof0).unwrap();
490        let err = partial.add_proof(proof2).unwrap_err();
491        assert_matches!(err, MerkleError::ConflictingRoots { .. });
492    }
493
494    /// Tests that a basic PartialSmt's iterator APIs return the expected values.
495    #[test]
496    fn partial_smt_iterator_apis() {
497        let key0 = RpoDigest::from(Word::from(rand_array()));
498        let key1 = RpoDigest::from(Word::from(rand_array()));
499        let key2 = RpoDigest::from(Word::from(rand_array()));
500        // A key for which we won't add a value so it will be empty.
501        let key_empty = RpoDigest::from(Word::from(rand_array()));
502
503        let value0 = Word::from(rand_array());
504        let value1 = Word::from(rand_array());
505        let value2 = Word::from(rand_array());
506
507        let mut kv_pairs = vec![(key0, value0), (key1, value1), (key2, value2)];
508
509        // Add more random leaves.
510        kv_pairs.reserve(1000);
511        for _ in 0..1000 {
512            let key = RpoDigest::from(Word::from(rand_array()));
513            let value = Word::from(rand_array());
514            kv_pairs.push((key, value));
515        }
516
517        let full = Smt::with_entries(kv_pairs).unwrap();
518
519        // Construct a partial SMT from proofs.
520        // ----------------------------------------------------------------------------------------
521
522        let proof0 = full.open(&key0);
523        let proof2 = full.open(&key2);
524        let proof_empty = full.open(&key_empty);
525
526        assert!(proof_empty.leaf().is_empty());
527
528        let proofs = [proof0, proof2, proof_empty];
529        let partial = PartialSmt::from_proofs(proofs.clone()).unwrap();
530
531        assert!(!partial.is_empty());
532        assert_eq!(full.root(), partial.root());
533        // There should be 2 non-empty entries.
534        assert_eq!(partial.num_entries(), 2);
535        // There should be 3 leaves, including the empty one.
536        assert_eq!(partial.num_leaves(), 3);
537
538        // The leaves API should only return tracked but non-empty leaves.
539        // ----------------------------------------------------------------------------------------
540
541        // Construct the sorted vector of leaves that should be yielded by the partial SMT.
542        let expected_leaves: BTreeMap<_, _> =
543            [SmtLeaf::new_single(key0, value0), SmtLeaf::new_single(key2, value2)]
544                .into_iter()
545                .map(|leaf| (leaf.index(), leaf))
546                .collect();
547
548        let actual_leaves = partial
549            .leaves()
550            .map(|(idx, leaf)| (idx, leaf.clone()))
551            .collect::<BTreeMap<_, _>>();
552
553        assert_eq!(actual_leaves.len(), expected_leaves.len());
554        assert_eq!(actual_leaves, expected_leaves);
555
556        // The tracked_leaves API should return all tracked leaves, including empty ones.
557        // ----------------------------------------------------------------------------------------
558
559        let mut expected_tracked_leaves = expected_leaves;
560        let empty_leaf = SmtLeaf::new_empty(LeafIndex::from(key_empty));
561        expected_tracked_leaves.insert(empty_leaf.index(), empty_leaf);
562
563        let actual_tracked_leaves = partial
564            .tracked_leaves()
565            .map(|(idx, leaf)| (idx, leaf.clone()))
566            .collect::<BTreeMap<_, _>>();
567
568        assert_eq!(actual_tracked_leaves.len(), expected_tracked_leaves.len());
569        assert_eq!(actual_tracked_leaves, expected_tracked_leaves);
570
571        // The entries of the merkle paths from the proofs should exist as children of inner nodes
572        // in the partial SMT.
573        // ----------------------------------------------------------------------------------------
574
575        let partial_inner_nodes: BTreeSet<_> =
576            partial.inner_nodes().map(|node| [node.left, node.right]).flatten().collect();
577
578        for merkle_path in proofs.into_iter().map(|proof| proof.into_parts().0) {
579            for (idx, digest) in merkle_path.into_iter().enumerate() {
580                assert!(partial_inner_nodes.contains(&digest), "failed at idx {idx}");
581            }
582        }
583    }
584
585    /// Test that an empty partial SMT's is_empty method returns `true`.
586    #[test]
587    fn partial_smt_is_empty() {
588        assert!(PartialSmt::new().is_empty());
589    }
590}