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}