miden_crypto/merkle/smt/
partial.rs

1use crate::{
2    EMPTY_WORD, Word,
3    hash::rpo::RpoDigest,
4    merkle::{InnerNode, MerkleError, MerklePath, Smt, SmtLeaf, SmtProof, smt::SparseMerkleTree},
5};
6
7/// A partial version of an [`Smt`].
8///
9/// This type can track a subset of the key-value pairs of a full [`Smt`] and allows for updating
10/// those pairs to compute the new root of the tree, as if the updates had been done on the full
11/// tree. This is useful so that not all leaves have to be present and loaded into memory to compute
12/// an update.
13///
14/// To facilitate this, a partial SMT requires that the merkle paths of every key-value pair are
15/// added to the tree. This means this pair is considered "tracked" and can be updated.
16///
17/// An important caveat is that only pairs whose merkle paths were added can be updated. Attempting
18/// to update an untracked value will result in an error. See [`PartialSmt::insert`] for more
19/// details.
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
22pub struct PartialSmt(Smt);
23
24impl PartialSmt {
25    // CONSTRUCTORS
26    // --------------------------------------------------------------------------------------------
27
28    /// Returns a new [`PartialSmt`].
29    ///
30    /// All leaves in the returned tree are set to [`Smt::EMPTY_VALUE`].
31    pub fn new() -> Self {
32        Self(Smt::new())
33    }
34
35    /// Instantiates a new [`PartialSmt`] by calling [`PartialSmt::add_path`] for all [`SmtProof`]s
36    /// in the provided iterator.
37    ///
38    /// # Errors
39    ///
40    /// Returns an error if:
41    /// - the new root after the insertion of a (leaf, path) tuple does not match the existing root
42    ///   (except if the tree was previously empty).
43    pub fn from_proofs<I>(paths: I) -> Result<Self, MerkleError>
44    where
45        I: IntoIterator<Item = SmtProof>,
46    {
47        let mut partial_smt = Self::new();
48
49        for (leaf, path) in paths.into_iter().map(SmtProof::into_parts) {
50            partial_smt.add_path(path, leaf)?;
51        }
52
53        Ok(partial_smt)
54    }
55
56    // PUBLIC ACCESSORS
57    // --------------------------------------------------------------------------------------------
58
59    /// Returns the root of the tree.
60    pub fn root(&self) -> RpoDigest {
61        self.0.root()
62    }
63
64    /// Returns an opening of the leaf associated with `key`. Conceptually, an opening is a Merkle
65    /// path to the leaf, as well as the leaf itself.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if:
70    /// - the key is not tracked by this partial SMT.
71    pub fn open(&self, key: &RpoDigest) -> Result<SmtProof, MerkleError> {
72        if !self.is_leaf_tracked(key) {
73            return Err(MerkleError::UntrackedKey(*key));
74        }
75
76        Ok(self.0.open(key))
77    }
78
79    /// Returns the leaf to which `key` maps
80    ///
81    /// # Errors
82    ///
83    /// Returns an error if:
84    /// - the key is not tracked by this partial SMT.
85    pub fn get_leaf(&self, key: &RpoDigest) -> Result<SmtLeaf, MerkleError> {
86        if !self.is_leaf_tracked(key) {
87            return Err(MerkleError::UntrackedKey(*key));
88        }
89
90        Ok(self.0.get_leaf(key))
91    }
92
93    /// Returns the value associated with `key`.
94    ///
95    /// # Errors
96    ///
97    /// Returns an error if:
98    /// - the key is not tracked by this partial SMT.
99    pub fn get_value(&self, key: &RpoDigest) -> Result<Word, MerkleError> {
100        if !self.is_leaf_tracked(key) {
101            return Err(MerkleError::UntrackedKey(*key));
102        }
103
104        Ok(self.0.get_value(key))
105    }
106
107    // STATE MUTATORS
108    // --------------------------------------------------------------------------------------------
109
110    /// Inserts a value at the specified key, returning the previous value associated with that key.
111    /// Recall that by definition, any key that hasn't been updated is associated with
112    /// [`Smt::EMPTY_VALUE`].
113    ///
114    /// This also recomputes all hashes between the leaf (associated with the key) and the root,
115    /// updating the root itself.
116    ///
117    /// # Errors
118    ///
119    /// Returns an error if:
120    /// - the key and its merkle path were not previously added (using [`PartialSmt::add_path`]) to
121    ///   this [`PartialSmt`], which means it is almost certainly incorrect to update its value. If
122    ///   an error is returned the tree is in the same state as before.
123    pub fn insert(&mut self, key: RpoDigest, value: Word) -> Result<Word, MerkleError> {
124        if !self.is_leaf_tracked(&key) {
125            return Err(MerkleError::UntrackedKey(key));
126        }
127
128        let previous_value = self.0.insert(key, value);
129
130        // If the value was removed the SmtLeaf was removed as well by the underlying Smt
131        // implementation. However, we still want to consider that leaf tracked so it can be
132        // read and written to, so we reinsert an empty SmtLeaf.
133        if value == EMPTY_WORD {
134            let leaf_index = Smt::key_to_leaf_index(&key);
135            self.0.leaves.insert(leaf_index.value(), SmtLeaf::Empty(leaf_index));
136        }
137
138        Ok(previous_value)
139    }
140
141    /// Adds an [`SmtProof`] to this [`PartialSmt`].
142    ///
143    /// This is a convenience method which calls [`Self::add_path`] on the proof. See its
144    /// documentation for details on errors.
145    pub fn add_proof(&mut self, proof: SmtProof) -> Result<(), MerkleError> {
146        let (path, leaf) = proof.into_parts();
147        self.add_path(leaf, path)
148    }
149
150    /// Adds a leaf and its merkle path to this [`PartialSmt`].
151    ///
152    /// If this function was called, any key that is part of the `leaf` can subsequently be updated
153    /// to a new value and produce a correct new tree root.
154    ///
155    /// # Errors
156    ///
157    /// Returns an error if:
158    /// - the new root after the insertion of the leaf and the path does not match the existing root
159    ///   (except when the first leaf is added). If an error is returned, the tree is left in an
160    ///   inconsistent state.
161    pub fn add_path(&mut self, leaf: SmtLeaf, path: MerklePath) -> Result<(), MerkleError> {
162        let mut current_index = leaf.index().index;
163
164        let mut node_hash_at_current_index = leaf.hash();
165
166        // We insert directly into the leaves for two reasons:
167        // - We can directly insert the leaf as it is without having to loop over its entries to
168        //   call Smt::perform_insert.
169        // - If the leaf is SmtLeaf::Empty, we will also insert it, which means this leaf is
170        //   considered tracked by the partial SMT as it is part of the leaves map. When calling
171        //   PartialSmt::insert, this will not error for such empty leaves whose merkle path was
172        //   added, but will error for otherwise non-existent leaves whose paths were not added,
173        //   which is what we want.
174        self.0.leaves.insert(current_index.value(), leaf);
175
176        for sibling_hash in path {
177            // Find the index of the sibling node and compute whether it is a left or right child.
178            let is_sibling_right = current_index.sibling().is_value_odd();
179
180            // Move the index up so it points to the parent of the current index and the sibling.
181            current_index.move_up();
182
183            // Construct the new parent node from the child that was updated and the sibling from
184            // the merkle path.
185            let new_parent_node = if is_sibling_right {
186                InnerNode {
187                    left: node_hash_at_current_index,
188                    right: sibling_hash,
189                }
190            } else {
191                InnerNode {
192                    left: sibling_hash,
193                    right: node_hash_at_current_index,
194                }
195            };
196
197            self.0.insert_inner_node(current_index, new_parent_node);
198
199            node_hash_at_current_index = self.0.get_inner_node(current_index).hash();
200        }
201
202        // Check the newly added merkle path is consistent with the existing tree. If not, the
203        // merkle path was invalid or computed from another tree.
204        //
205        // We skip this check if we have just inserted the first leaf since we assume that leaf's
206        // root is correct and all subsequent leaves that will be added must have the same root.
207        if self.0.num_leaves() != 1 && self.root() != node_hash_at_current_index {
208            return Err(MerkleError::ConflictingRoots {
209                expected_root: self.root(),
210                actual_root: node_hash_at_current_index,
211            });
212        }
213
214        self.0.set_root(node_hash_at_current_index);
215
216        Ok(())
217    }
218
219    /// Returns true if the key's merkle path was previously added to this partial SMT and can be
220    /// sensibly updated to a new value.
221    /// In particular, this returns true for keys whose value was empty **but** their merkle paths
222    /// were added, while it returns false if the merkle paths were **not** added.
223    fn is_leaf_tracked(&self, key: &RpoDigest) -> bool {
224        self.0.leaves.contains_key(&Smt::key_to_leaf_index(key).value())
225    }
226}
227
228impl Default for PartialSmt {
229    fn default() -> Self {
230        Self::new()
231    }
232}
233
234// TESTS
235// ================================================================================================
236
237#[cfg(test)]
238mod tests {
239
240    use assert_matches::assert_matches;
241    use rand_utils::rand_array;
242
243    use super::*;
244    use crate::{EMPTY_WORD, ONE, ZERO};
245
246    /// Tests that a basic PartialSmt can be built from a full one and that inserting or removing
247    /// values whose merkle path were added to the partial SMT results in the same root as the
248    /// equivalent update in the full tree.
249    #[test]
250    fn partial_smt_insert_and_remove() {
251        let key0 = RpoDigest::from(Word::from(rand_array()));
252        let key1 = RpoDigest::from(Word::from(rand_array()));
253        let key2 = RpoDigest::from(Word::from(rand_array()));
254        // A key for which we won't add a value so it will be empty.
255        let key_empty = RpoDigest::from(Word::from(rand_array()));
256
257        let value0 = Word::from(rand_array());
258        let value1 = Word::from(rand_array());
259        let value2 = Word::from(rand_array());
260
261        let mut kv_pairs = vec![(key0, value0), (key1, value1), (key2, value2)];
262
263        // Add more random leaves.
264        kv_pairs.reserve(1000);
265        for _ in 0..1000 {
266            let key = RpoDigest::from(Word::from(rand_array()));
267            let value = Word::from(rand_array());
268            kv_pairs.push((key, value));
269        }
270
271        let mut full = Smt::with_entries(kv_pairs).unwrap();
272
273        // Constructing a partial SMT from proofs succeeds.
274        // ----------------------------------------------------------------------------------------
275
276        let proof0 = full.open(&key0);
277        let proof2 = full.open(&key2);
278        let proof_empty = full.open(&key_empty);
279
280        assert!(proof_empty.leaf().is_empty());
281
282        let mut partial = PartialSmt::from_proofs([proof0, proof2, proof_empty]).unwrap();
283
284        assert_eq!(full.root(), partial.root());
285        assert_eq!(partial.get_value(&key0).unwrap(), value0);
286        let error = partial.get_value(&key1).unwrap_err();
287        assert_matches!(error, MerkleError::UntrackedKey(_));
288        assert_eq!(partial.get_value(&key2).unwrap(), value2);
289
290        // Insert new values for added keys with empty and non-empty values.
291        // ----------------------------------------------------------------------------------------
292
293        let new_value0 = Word::from(rand_array());
294        let new_value2 = Word::from(rand_array());
295        // A non-empty value for the key that was previously empty.
296        let new_value_empty_key = Word::from(rand_array());
297
298        full.insert(key0, new_value0);
299        full.insert(key2, new_value2);
300        full.insert(key_empty, new_value_empty_key);
301
302        partial.insert(key0, new_value0).unwrap();
303        partial.insert(key2, new_value2).unwrap();
304        // This updates a key whose value was previously empty.
305        partial.insert(key_empty, new_value_empty_key).unwrap();
306
307        assert_eq!(full.root(), partial.root());
308        assert_eq!(partial.get_value(&key0).unwrap(), new_value0);
309        assert_eq!(partial.get_value(&key2).unwrap(), new_value2);
310        assert_eq!(partial.get_value(&key_empty).unwrap(), new_value_empty_key);
311
312        // Remove an added key.
313        // ----------------------------------------------------------------------------------------
314
315        full.insert(key0, EMPTY_WORD);
316        partial.insert(key0, EMPTY_WORD).unwrap();
317
318        assert_eq!(full.root(), partial.root());
319        assert_eq!(partial.get_value(&key0).unwrap(), EMPTY_WORD);
320
321        // Check if returned openings are the same in partial and full SMT.
322        // ----------------------------------------------------------------------------------------
323
324        // This is a key whose value is empty since it was removed.
325        assert_eq!(full.open(&key0), partial.open(&key0).unwrap());
326        // This is a key whose value is non-empty.
327        assert_eq!(full.open(&key2), partial.open(&key2).unwrap());
328
329        // Attempting to update a key whose merkle path was not added is an error.
330        // ----------------------------------------------------------------------------------------
331
332        let error = partial.clone().insert(key1, Word::from(rand_array())).unwrap_err();
333        assert_matches!(error, MerkleError::UntrackedKey(_));
334
335        let error = partial.insert(key1, EMPTY_WORD).unwrap_err();
336        assert_matches!(error, MerkleError::UntrackedKey(_));
337    }
338
339    /// Test that we can add an SmtLeaf::Multiple variant to a partial SMT.
340    #[test]
341    fn partial_smt_multiple_leaf_success() {
342        // key0 and key1 have the same felt at index 3 so they will be placed in the same leaf.
343        let key0 = RpoDigest::from(Word::from([ZERO, ZERO, ZERO, ONE]));
344        let key1 = RpoDigest::from(Word::from([ONE, ONE, ONE, ONE]));
345        let key2 = RpoDigest::from(Word::from(rand_array()));
346
347        let value0 = Word::from(rand_array());
348        let value1 = Word::from(rand_array());
349        let value2 = Word::from(rand_array());
350
351        let full = Smt::with_entries([(key0, value0), (key1, value1), (key2, value2)]).unwrap();
352
353        // Make sure our assumption about the leaf being a multiple is correct.
354        let SmtLeaf::Multiple(_) = full.get_leaf(&key0) else {
355            panic!("expected full tree to produce multiple leaf")
356        };
357
358        let proof0 = full.open(&key0);
359        let proof2 = full.open(&key2);
360
361        let partial = PartialSmt::from_proofs([proof0, proof2]).unwrap();
362
363        assert_eq!(partial.root(), full.root());
364
365        assert_eq!(partial.get_leaf(&key0).unwrap(), full.get_leaf(&key0));
366        // key1 is present in the partial tree because it is part of the proof of key0.
367        assert_eq!(partial.get_leaf(&key1).unwrap(), full.get_leaf(&key1));
368        assert_eq!(partial.get_leaf(&key2).unwrap(), full.get_leaf(&key2));
369    }
370
371    /// Tests that adding proofs to a partial SMT whose roots are not the same will result in an
372    /// error.
373    ///
374    /// This test uses only empty values in the partial SMT.
375    #[test]
376    fn partial_smt_root_mismatch_on_empty_values() {
377        let key0 = RpoDigest::from(Word::from(rand_array()));
378        let key1 = RpoDigest::from(Word::from(rand_array()));
379        let key2 = RpoDigest::from(Word::from(rand_array()));
380
381        let value0 = EMPTY_WORD;
382        let value1 = Word::from(rand_array());
383        let value2 = EMPTY_WORD;
384
385        let kv_pairs = vec![(key0, value0)];
386
387        let mut full = Smt::with_entries(kv_pairs).unwrap();
388        // This proof will be stale after we insert another value.
389        let stale_proof0 = full.open(&key0);
390
391        // Insert a non-empty value so the root actually changes.
392        full.insert(key1, value1);
393        full.insert(key2, value2);
394
395        let proof2 = full.open(&key2);
396
397        let mut partial = PartialSmt::new();
398
399        partial.add_proof(stale_proof0).unwrap();
400        let err = partial.add_proof(proof2).unwrap_err();
401        assert_matches!(err, MerkleError::ConflictingRoots { .. });
402    }
403
404    /// Tests that adding proofs to a partial SMT whose roots are not the same will result in an
405    /// error.
406    ///
407    /// This test uses only non-empty values in the partial SMT.
408    #[test]
409    fn partial_smt_root_mismatch_on_non_empty_values() {
410        let key0 = RpoDigest::from(Word::from(rand_array()));
411        let key1 = RpoDigest::from(Word::from(rand_array()));
412        let key2 = RpoDigest::from(Word::from(rand_array()));
413
414        let value0 = Word::from(rand_array());
415        let value1 = Word::from(rand_array());
416        let value2 = Word::from(rand_array());
417
418        let kv_pairs = vec![(key0, value0), (key1, value1)];
419
420        let mut full = Smt::with_entries(kv_pairs).unwrap();
421        // This proof will be stale after we insert another value.
422        let stale_proof0 = full.open(&key0);
423
424        full.insert(key2, value2);
425
426        let proof2 = full.open(&key2);
427
428        let mut partial = PartialSmt::new();
429
430        partial.add_proof(stale_proof0).unwrap();
431        let err = partial.add_proof(proof2).unwrap_err();
432        assert_matches!(err, MerkleError::ConflictingRoots { .. });
433    }
434}