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}