Skip to main content

miden_crypto/merkle/smt/partial/
mod.rs

1use alloc::{
2    collections::{BTreeMap, BTreeSet, VecDeque},
3    string::ToString,
4    vec::Vec,
5};
6
7use super::{EmptySubtreeRoots, LeafIndex, SMT_DEPTH};
8use crate::{
9    EMPTY_WORD, Map, Word,
10    merkle::{
11        InnerNodeInfo, MerkleError, NodeIndex, SparseMerklePath,
12        smt::{InnerNode, InnerNodes, Leaves, SmtLeaf, SmtLeafError, SmtProof},
13    },
14    utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
15};
16
17mod serialization;
18#[cfg(test)]
19mod tests;
20
21pub use serialization::UniqueNodes;
22
23/// A partial version of an [`super::Smt`].
24///
25/// This type can track a subset of the key-value pairs of a full [`super::Smt`] and allows for
26/// updating those pairs to compute the new root of the tree, as if the updates had been done on the
27/// full tree. This is useful so that not all leaves have to be present and loaded into memory to
28/// compute an update.
29///
30/// A key is considered "tracked" if either:
31/// 1. Its merkle path was explicitly added to the tree (via [`PartialSmt::add_path`] or
32///    [`PartialSmt::add_proof`]), or
33/// 2. The path from the leaf to the root goes through empty subtrees that are consistent with the
34///    stored inner nodes (provably empty with zero hash computations).
35///
36/// The second condition allows updating keys in empty subtrees without explicitly adding their
37/// merkle paths. This is verified by walking up from the leaf and checking that any stored
38/// inner node has an empty subtree root as the child on our path.
39///
40/// An important caveat is that only tracked keys can be updated. Attempting to update an
41/// untracked key will result in an error. See [`PartialSmt::insert`] for more details.
42///
43/// Once a partial SMT has been constructed, its root is set in stone. All subsequently added proofs
44/// or merkle paths must match that root, otherwise an error is returned.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct PartialSmt {
47    root: Word,
48    num_entries: usize,
49    leaves: Leaves<SmtLeaf>,
50    inner_nodes: InnerNodes,
51}
52
53impl PartialSmt {
54    // CONSTANTS
55    // --------------------------------------------------------------------------------------------
56
57    /// The default value used to compute the hash of empty leaves.
58    pub const EMPTY_VALUE: Word = EMPTY_WORD;
59
60    /// The root of an empty tree.
61    pub const EMPTY_ROOT: Word = *EmptySubtreeRoots::entry(SMT_DEPTH, 0);
62
63    // CONSTRUCTORS
64    // --------------------------------------------------------------------------------------------
65
66    /// Constructs a [`PartialSmt`] from a root.
67    ///
68    /// All subsequently added proofs or paths must have the same root.
69    pub fn new(root: Word) -> Self {
70        Self {
71            root,
72            num_entries: 0,
73            leaves: Leaves::<SmtLeaf>::default(),
74            inner_nodes: InnerNodes::default(),
75        }
76    }
77
78    /// Instantiates a new [`PartialSmt`] by calling [`PartialSmt::add_proof`] for all [`SmtProof`]s
79    /// in the provided iterator.
80    ///
81    /// If the provided iterator is empty, an empty [`PartialSmt`] is returned.
82    ///
83    /// # Errors
84    ///
85    /// Returns an error if:
86    /// - the roots of the provided proofs are not the same.
87    pub fn from_proofs<I>(proofs: I) -> Result<Self, MerkleError>
88    where
89        I: IntoIterator<Item = SmtProof>,
90    {
91        let mut proofs = proofs.into_iter();
92
93        let Some(first_proof) = proofs.next() else {
94            return Ok(Self::default());
95        };
96
97        // Add the first path to an empty partial SMT without checking that the existing root
98        // matches the new one. This sets the expected root to the root of the first proof and all
99        // subsequently added proofs must match it.
100        let mut partial_smt = Self::default();
101        let (path, leaf) = first_proof.into_parts();
102        let path_root = partial_smt.add_path_unchecked(leaf, path);
103        partial_smt.root = path_root;
104
105        for proof in proofs {
106            partial_smt.add_proof(proof)?;
107        }
108
109        Ok(partial_smt)
110    }
111
112    // PUBLIC ACCESSORS
113    // --------------------------------------------------------------------------------------------
114
115    /// Returns the root of the tree.
116    pub fn root(&self) -> Word {
117        self.root
118    }
119
120    /// Returns an opening of the leaf associated with `key`. Conceptually, an opening is a Merkle
121    /// path to the leaf, as well as the leaf itself.
122    ///
123    /// # Errors
124    ///
125    /// Returns an error if:
126    /// - the key is not tracked by this partial SMT.
127    pub fn open(&self, key: &Word) -> Result<SmtProof, MerkleError> {
128        let leaf = self.get_leaf(key)?;
129        let merkle_path = self.get_path(key);
130        Ok(SmtProof::new_unchecked(merkle_path, leaf))
131    }
132
133    /// Returns the leaf to which `key` maps.
134    ///
135    /// # Errors
136    ///
137    /// Returns an error if:
138    /// - the key is not tracked by this partial SMT.
139    pub fn get_leaf(&self, key: &Word) -> Result<SmtLeaf, MerkleError> {
140        self.get_tracked_leaf(key).ok_or(MerkleError::UntrackedKey(*key))
141    }
142
143    /// Returns the value associated with `key`.
144    ///
145    /// # Errors
146    ///
147    /// Returns an error if:
148    /// - the key is not tracked by this partial SMT.
149    pub fn get_value(&self, key: &Word) -> Result<Word, MerkleError> {
150        self.get_tracked_leaf(key)
151            .map(|leaf| leaf.get_value(key).unwrap_or_default())
152            .ok_or(MerkleError::UntrackedKey(*key))
153    }
154
155    /// Returns an iterator over the inner nodes of the [`PartialSmt`].
156    pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
157        self.inner_nodes.values().map(|e| InnerNodeInfo {
158            value: e.hash(),
159            left: e.left,
160            right: e.right,
161        })
162    }
163
164    /// Returns an iterator over the [`InnerNode`] and the respective [`NodeIndex`] of the
165    /// [`PartialSmt`].
166    pub fn inner_node_indices(&self) -> impl Iterator<Item = (NodeIndex, InnerNode)> + '_ {
167        self.inner_nodes.iter().map(|(idx, inner)| (*idx, inner.clone()))
168    }
169
170    /// Returns an iterator over the explicitly stored leaves of the [`PartialSmt`] in arbitrary
171    /// order.
172    ///
173    /// Note: This only returns leaves that were explicitly added via [`Self::add_path`] or
174    /// [`Self::add_proof`], or created through [`Self::insert`]. It does not include implicitly
175    /// trackable leaves in empty subtrees.
176    pub fn leaves(&self) -> impl Iterator<Item = (LeafIndex<SMT_DEPTH>, &SmtLeaf)> {
177        self.leaves
178            .iter()
179            .map(|(leaf_index, leaf)| (LeafIndex::new_max_depth(*leaf_index), leaf))
180    }
181
182    /// Returns an iterator over the tracked, non-empty key-value pairs of the [`PartialSmt`] in
183    /// arbitrary order.
184    pub fn entries(&self) -> impl Iterator<Item = &(Word, Word)> {
185        self.leaves().flat_map(|(_, leaf)| leaf.entries())
186    }
187
188    /// Returns the number of non-empty leaves in this tree.
189    ///
190    /// Note that this may return a different value from [Self::num_entries()] as a single leaf may
191    /// contain more than one key-value pair.
192    pub fn num_leaves(&self) -> usize {
193        self.leaves.len()
194    }
195
196    /// Returns the number of tracked, non-empty key-value pairs in this tree.
197    ///
198    /// Note that this may return a different value from [Self::num_leaves()] as a single leaf may
199    /// contain more than one key-value pair.
200    pub fn num_entries(&self) -> usize {
201        self.num_entries
202    }
203
204    /// Returns a boolean value indicating whether the [`PartialSmt`] tracks any leaves.
205    ///
206    /// Note that if a partial SMT does not track leaves, its root is not necessarily the empty SMT
207    /// root, since it could have been constructed from a different root but without tracking any
208    /// leaves.
209    pub fn tracks_leaves(&self) -> bool {
210        !self.leaves.is_empty()
211    }
212
213    // STATE MUTATORS
214    // --------------------------------------------------------------------------------------------
215
216    /// Inserts a value at the specified key, returning the previous value associated with that key.
217    /// Recall that by definition, any key that hasn't been updated is associated with
218    /// [`Self::EMPTY_VALUE`].
219    ///
220    /// This also recomputes all hashes between the leaf (associated with the key) and the root,
221    /// updating the root itself.
222    ///
223    /// # Errors
224    ///
225    /// Returns an error if:
226    /// - the key is not tracked (see the type documentation for the definition of "tracked"). If an
227    ///   error is returned the tree is in the same state as before.
228    /// - inserting the key-value pair would exceed [`super::MAX_LEAF_ENTRIES`] (1024 entries) in
229    ///   the leaf.
230    pub fn insert(&mut self, key: Word, value: Word) -> Result<Word, MerkleError> {
231        let current_leaf = self.get_tracked_leaf(&key).ok_or(MerkleError::UntrackedKey(key))?;
232        let leaf_index = current_leaf.index();
233        let previous_value = current_leaf.get_value(&key).unwrap_or(EMPTY_WORD);
234        let prev_entries = current_leaf.num_entries();
235
236        let leaf = self
237            .leaves
238            .entry(leaf_index.position())
239            .or_insert_with(|| SmtLeaf::new_empty(leaf_index));
240
241        if value != EMPTY_WORD {
242            leaf.insert(key, value).map_err(|e| match e {
243                SmtLeafError::TooManyLeafEntries { actual } => {
244                    MerkleError::TooManyLeafEntries { actual }
245                },
246                other => panic!("unexpected SmtLeaf::insert error: {other:?}"),
247            })?;
248        } else {
249            leaf.remove(key);
250        }
251        let current_entries = leaf.num_entries();
252        let new_leaf_hash = leaf.hash();
253        self.num_entries = self.num_entries + current_entries - prev_entries;
254
255        // Remove empty leaf
256        if current_entries == 0 {
257            self.leaves.remove(&leaf_index.position());
258        }
259
260        // Recompute the path from leaf to root
261        self.recompute_nodes_from_leaf_to_root(leaf_index, new_leaf_hash);
262
263        Ok(previous_value)
264    }
265
266    /// Adds an [`SmtProof`] to this [`PartialSmt`].
267    ///
268    /// This is a convenience method which calls [`Self::add_path`] on the proof. See its
269    /// documentation for details on errors.
270    pub fn add_proof(&mut self, proof: SmtProof) -> Result<(), MerkleError> {
271        let (path, leaf) = proof.into_parts();
272        self.add_path(leaf, path)
273    }
274
275    /// Adds a leaf and its sparse merkle path to this [`PartialSmt`].
276    ///
277    /// If this function was called, any key that is part of the `leaf` can subsequently be updated
278    /// to a new value and produce a correct new tree root.
279    ///
280    /// # Errors
281    ///
282    /// Returns an error if:
283    /// - the new root after the insertion of the leaf and the path does not match the existing
284    ///   root. If an error is returned, the tree is left in an inconsistent state.
285    pub fn add_path(&mut self, leaf: SmtLeaf, path: SparseMerklePath) -> Result<(), MerkleError> {
286        let path_root = self.add_path_unchecked(leaf, path);
287
288        // Check if the newly added merkle path is consistent with the existing tree. If not, the
289        // merkle path was invalid or computed against another tree.
290        if self.root() != path_root {
291            return Err(MerkleError::ConflictingRoots {
292                expected_root: self.root(),
293                actual_root: path_root,
294            });
295        }
296
297        Ok(())
298    }
299
300    // UNIQUE NODES
301    // --------------------------------------------------------------------------------------------
302
303    /// Converts `self` into the [`UniqueNodes`] serialization representation for compact
304    /// serialization.
305    ///
306    /// This method assumes that the `PartialSmt` is in a valid state.
307    ///
308    /// # Reconstructable Sets
309    ///
310    /// We define the notion of a reconstructable set as one which stores the minimum amount of
311    /// information necessary in order to reconstruct the full state of the tree. We build this set
312    /// as follows:
313    ///
314    /// 1. Start at the leaves and traverse toward the root.
315    /// 2. Wherever a node's value is determined solely by children already implicitly contained
316    ///    within the set, store no new information. If additional information is required (e.g. a
317    ///    sibling node) store that.
318    /// 3. Repeat until the root is reached.
319    ///
320    /// To reconstruct the tree, we just start at the leaves and compute all intermediary nodes from
321    /// the data stored in the reconstructible set.
322    pub fn to_unique_nodes(&self) -> UniqueNodes {
323        // We start by getting all the known leaves, as these give us the starting point for the
324        // reconstruction.
325        let leaf_nodes = self
326            .leaves()
327            .map(|(k, v)| (k, v.clone()))
328            .collect::<Map<LeafIndex<SMT_DEPTH>, SmtLeaf>>();
329
330        // We also create storage for the nodes necessary for reconstruction of the tree...
331        let mut needed_nodes = BTreeMap::new();
332
333        // ... and grab the full set of inner nodes to work from as a queue for easy use. We sort
334        // them from the bottom of the tree to the top, but retain the standard left-to-right
335        // ordering.
336        let mut inner_nodes = self.inner_node_indices().collect::<Vec<(NodeIndex, InnerNode)>>();
337        inner_nodes.sort_by(|(il, _), (ir, _)| {
338            ir.depth().cmp(&il.depth()).then(il.position().cmp(&ir.position()))
339        });
340        let mut inner_nodes = inner_nodes.into_iter().collect::<VecDeque<(NodeIndex, InnerNode)>>();
341
342        // We also need to store the values for leaves where we ONLY have the hash value, rather
343        // than the proper leaf value.
344        let mut value_only_leaves = BTreeMap::new();
345
346        // We then need to iterate over all the nodes to work out which ones are reconstructible,
347        // and which need us to store additional data to be reconstructible.
348        while let Some((ix, v)) = inner_nodes.pop_front() {
349            // There must be data available for both of the node's children for it to be
350            // reconstructible.
351            for (child, val) in [(ix.left_child(), v.left), (ix.right_child(), v.right)] {
352                if child.depth() != SMT_DEPTH {
353                    // A child of the node `v` can be in one of three states:
354                    //
355                    // 1. The child does not exist as a physical node in `self`, but its value as
356                    //    stored in `v` is real.
357                    // 2. The child does not exist as a physical node in `self`, but its value is
358                    //    the default empty subtree root.
359                    // 3. The child does exist as a physical node in `self`. By induction, as this
360                    //    algorithm runs bottom-up, the data to reconstruct the node already exists.
361                    if self.get_inner_node(child).is_none() {
362                        // In this case, the node does not exist physically, so we have to work out
363                        // which of the other cases it is.
364                        if val != *EmptySubtreeRoots::entry(SMT_DEPTH, child.depth())
365                            && let Some(previous) = needed_nodes.insert(child, val)
366                        {
367                            assert_eq!(
368                                previous, val,
369                                "node was overwritten with a different value"
370                            );
371                        }
372                    } else {
373                        // Here, the node exists physically, so by induction, it is reconstructible.
374                    }
375                } else {
376                    // Here the child is a leaf node. Leaf nodes can be in one of three states:
377                    //
378                    // 1. A node that has the default empty value, in which case we encode it using
379                    //    absence in the compact representation.
380                    // 2. A node that has a hash value, but that does not exist in the physical
381                    //    leaves in the PartialSmt. These are encoded using an auxiliary buffer to
382                    //    aid in reconstruction.
383                    // 3. A node that exists in fully-materialized form. These are encoded with
384                    //    their full content.
385                    //
386                    // Cases 1 and 3 require no special handling here, as they are encoded with the
387                    // leaves below. Case 2 needs us to take action here.
388                    let empty_leaf_hash =
389                        SmtLeaf::new_empty(LeafIndex::new_max_depth(child.position())).hash();
390
391                    if val != empty_leaf_hash && !self.leaves.contains_key(&child.position()) {
392                        // We are in case 2 here, as the value is not that of the empty leaf, nor is
393                        // there a physical leaf stored in the tree for this. We store this leaf
394                        // value in the auxiliary buffer so we can reconstruct correctly in this
395                        // scenario.
396                        value_only_leaves.insert(child.position(), val);
397                    }
398                }
399            }
400        }
401
402        // With all the data gathered, we can convert our types as necessary to create our output.
403        let leaves = leaf_nodes.into_iter().map(|(i, l)| (i.position(), l)).collect();
404
405        UniqueNodes {
406            root: self.root(),
407            leaves,
408            nodes: needed_nodes,
409            value_only_leaves,
410        }
411    }
412
413    /// Constructs a new `PartialSmt` from the provided `unique_nodes`, reconstituting the full data
414    /// from the compact representation.
415    ///
416    /// This method assumes that the `unique_nodes` represent a valid `PartialSmt` instance.
417    ///
418    /// See the documentation of [`Self::to_unique_nodes`] for the reconstruction algorithm.
419    ///
420    /// # Errors
421    ///
422    /// - [`DeserializationError::InvalidValue`] if a leaf's map key does not match its embedded
423    ///   index, or the reconstructed tree fails validation.
424    pub fn from_unique_nodes(unique_nodes: UniqueNodes) -> Result<Self, DeserializationError> {
425        unique_nodes.validate()?;
426
427        let mut smt = Self::new(unique_nodes.root);
428
429        // Reconstruction starts from every known leaf position. Stored internal nodes are also
430        // starting points because an exclusion proof may contain no leaf below them. Group these
431        // positions by depth so each layer can be rebuilt before its parent layer.
432        let mut active_by_depth = BTreeMap::<u8, BTreeSet<NodeIndex>>::new();
433
434        for &position in unique_nodes.leaves.keys().chain(unique_nodes.value_only_leaves.keys()) {
435            let index = NodeIndex::new(SMT_DEPTH, position)
436                .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
437            active_by_depth.entry(SMT_DEPTH).or_default().insert(index);
438        }
439        for &index in unique_nodes.nodes.keys() {
440            active_by_depth.entry(index.depth()).or_default().insert(index);
441        }
442
443        // Rebuild the tree one layer at a time, from the deepest starting positions to the root.
444        for child_depth in (1..=SMT_DEPTH).rev() {
445            let Some(active_nodes) = active_by_depth.remove(&child_depth) else {
446                continue;
447            };
448
449            // Every active node requires its parent. The set removes shared parents before the
450            // next layer is built.
451            let parents = active_nodes.into_iter().map(NodeIndex::parent).collect::<BTreeSet<_>>();
452
453            for parent in &parents {
454                // A child is either a leaf, a node rebuilt on the prior pass, a stored sparse node,
455                // or an omitted empty subtree root. The lookup helpers handle the last two cases.
456                let [left, right] = [parent.left_child(), parent.right_child()].map(|child| {
457                    if child.depth() == SMT_DEPTH {
458                        unique_nodes.get_leaf_hash(child.position())
459                    } else {
460                        smt.get_inner_node(child)
461                            .map(|node| node.hash())
462                            .unwrap_or_else(|| unique_nodes.get_node_hash(child))
463                    }
464                });
465                smt.insert_inner_node(*parent, InnerNode { left, right });
466            }
467
468            if child_depth > 1 {
469                // The parents become the active nodes for the next layer toward the root.
470                active_by_depth.entry(child_depth - 1).or_default().extend(parents);
471            }
472        }
473
474        for (position, leaf) in unique_nodes.leaves {
475            smt.num_entries += leaf.num_entries();
476            smt.leaves.insert(position, leaf);
477        }
478
479        smt.validate()?;
480
481        Ok(smt)
482    }
483
484    // PRIVATE HELPERS
485    // --------------------------------------------------------------------------------------------
486
487    /// Adds a leaf and its sparse merkle path to this [`PartialSmt`] and returns the root of the
488    /// inserted path.
489    ///
490    /// This does not check that the path root matches the existing root of the tree and if so, the
491    /// tree is left in an inconsistent state. This state can be made consistent again by setting
492    /// the root of the SMT to the path root.
493    fn add_path_unchecked(&mut self, leaf: SmtLeaf, path: SparseMerklePath) -> Word {
494        let mut current_index = leaf.index().index;
495
496        let mut node_hash_at_current_index = leaf.hash();
497
498        let prev_entries = self
499            .leaves
500            .get(&current_index.position())
501            .map(SmtLeaf::num_entries)
502            .unwrap_or(0);
503        let current_entries = leaf.num_entries();
504        // Only store non-empty leaves
505        if current_entries > 0 {
506            self.leaves.insert(current_index.position(), leaf);
507        } else {
508            self.leaves.remove(&current_index.position());
509        }
510
511        // Guaranteed not to over/underflow. All variables are <= MAX_LEAF_ENTRIES and result > 0.
512        self.num_entries = self.num_entries + current_entries - prev_entries;
513
514        for sibling_hash in path {
515            // Find the index of the sibling node and compute whether it is a left or right child.
516            let is_sibling_right = current_index.sibling().is_position_odd();
517
518            // Move the index up so it points to the parent of the current index and the sibling.
519            current_index.move_up();
520
521            // Construct the new parent node from the child that was updated and the sibling from
522            // the merkle path.
523            let new_parent_node = if is_sibling_right {
524                InnerNode {
525                    left: node_hash_at_current_index,
526                    right: sibling_hash,
527                }
528            } else {
529                InnerNode {
530                    left: sibling_hash,
531                    right: node_hash_at_current_index,
532                }
533            };
534
535            node_hash_at_current_index = new_parent_node.hash();
536
537            self.insert_inner_node(current_index, new_parent_node);
538        }
539
540        node_hash_at_current_index
541    }
542
543    /// Returns the leaf for a key if it can be tracked.
544    ///
545    /// A key is trackable if:
546    /// 1. It was explicitly added via `add_path`/`add_proof`, OR
547    /// 2. The path to the leaf goes through empty subtrees (provably empty)
548    ///
549    /// Returns `None` if the key cannot be tracked (path goes through non-empty
550    /// subtrees we don't have data for).
551    fn get_tracked_leaf(&self, key: &Word) -> Option<SmtLeaf> {
552        let leaf_index = Self::key_to_leaf_index(key);
553
554        // Explicitly stored leaves are always trackable
555        if let Some(leaf) = self.leaves.get(&leaf_index.position()) {
556            return Some(leaf.clone());
557        }
558
559        // Empty tree - all leaves implicitly trackable
560        if self.root == Self::EMPTY_ROOT {
561            return Some(SmtLeaf::new_empty(leaf_index));
562        }
563
564        // Walk from root down towards the leaf
565        let target: NodeIndex = leaf_index.into();
566        let mut index = NodeIndex::root();
567
568        for i in (0..SMT_DEPTH).rev() {
569            let inner_node = self.get_inner_node(index)?;
570
571            let is_right = target.is_nth_bit_odd(i);
572            let child_hash = if is_right { inner_node.right } else { inner_node.left };
573
574            // If child is empty subtree root, leaf is implicitly trackable
575            if child_hash == *EmptySubtreeRoots::entry(SMT_DEPTH, SMT_DEPTH - i) {
576                return Some(SmtLeaf::new_empty(leaf_index));
577            }
578
579            index = if is_right {
580                index.right_child()
581            } else {
582                index.left_child()
583            };
584        }
585
586        // Reached leaf level without finding empty subtree - can't track
587        None
588    }
589
590    /// Converts a key to a leaf index.
591    fn key_to_leaf_index(key: &Word) -> LeafIndex<SMT_DEPTH> {
592        let most_significant_felt = key[3];
593        LeafIndex::new_max_depth(most_significant_felt.as_canonical_u64())
594    }
595
596    /// Returns the inner node at the specified index, or `None` if not stored.
597    fn get_inner_node(&self, index: NodeIndex) -> Option<InnerNode> {
598        self.inner_nodes.get(&index).cloned()
599    }
600
601    /// Returns the inner node at the specified index, falling back to the empty subtree root
602    /// if not stored.
603    fn get_inner_node_or_empty(&self, index: NodeIndex) -> InnerNode {
604        self.get_inner_node(index)
605            .unwrap_or_else(|| EmptySubtreeRoots::get_inner_node(SMT_DEPTH, index.depth()))
606    }
607
608    /// Inserts an inner node at the specified index, or removes it if it equals the empty
609    /// subtree root.
610    fn insert_inner_node(&mut self, index: NodeIndex, inner_node: InnerNode) {
611        if inner_node == EmptySubtreeRoots::get_inner_node(SMT_DEPTH, index.depth()) {
612            self.inner_nodes.remove(&index);
613        } else {
614            self.inner_nodes.insert(index, inner_node);
615        }
616    }
617
618    /// Returns the merkle path for a key by walking up the tree from the leaf.
619    fn get_path(&self, key: &Word) -> SparseMerklePath {
620        let index = NodeIndex::from(Self::key_to_leaf_index(key));
621
622        // Use proof_indices to get sibling indices from leaf to root,
623        // and get each sibling's hash
624        SparseMerklePath::from_sized_iter(index.proof_indices().map(|idx| self.get_node_hash(idx)))
625            .expect("path should be valid since it's from a valid SMT")
626    }
627
628    /// Get the hash of a node at an arbitrary index, including the root or leaf hashes.
629    ///
630    /// The root index simply returns the root. Other hashes are retrieved by looking at
631    /// the parent inner node and returning the respective child hash.
632    fn get_node_hash(&self, index: NodeIndex) -> Word {
633        if index.is_root() {
634            return self.root;
635        }
636
637        let InnerNode { left, right } = self.get_inner_node_or_empty(index.parent());
638
639        if index.is_position_odd() { right } else { left }
640    }
641
642    /// Recomputes all inner nodes from a leaf up to the root after a leaf value change.
643    fn recompute_nodes_from_leaf_to_root(
644        &mut self,
645        leaf_index: LeafIndex<SMT_DEPTH>,
646        leaf_hash: Word,
647    ) {
648        use crate::hash::poseidon2::Poseidon2;
649
650        let mut index: NodeIndex = leaf_index.into();
651        let mut node_hash = leaf_hash;
652
653        for _ in (0..index.depth()).rev() {
654            let is_right = index.is_position_odd();
655            index.move_up();
656            let InnerNode { left, right } = self.get_inner_node_or_empty(index);
657            let (left, right) = if is_right {
658                (left, node_hash)
659            } else {
660                (node_hash, right)
661            };
662            node_hash = Poseidon2::merge(&[left, right]);
663
664            // insert_inner_node handles removing empty subtree roots
665            self.insert_inner_node(index, InnerNode { left, right });
666        }
667        self.root = node_hash;
668    }
669
670    /// Validates the internal structure during deserialization.
671    ///
672    /// Checks that:
673    /// - Each inner node's hash is consistent with its parent.
674    /// - Each leaf's hash is consistent with its parent inner node's left/right child.
675    fn validate(&self) -> Result<(), DeserializationError> {
676        // Validate each inner node is consistent with its parent
677        for (&idx, node) in &self.inner_nodes {
678            let node_hash = node.hash();
679            let expected_hash = self.get_node_hash(idx);
680
681            if node_hash != expected_hash {
682                return Err(DeserializationError::InvalidValue(
683                    "inner node hash is inconsistent with parent".into(),
684                ));
685            }
686        }
687
688        // Validate each leaf's hash is consistent with its parent inner node
689        for (&leaf_pos, leaf) in &self.leaves {
690            let leaf_index = LeafIndex::<SMT_DEPTH>::new_max_depth(leaf_pos);
691            let node_index: NodeIndex = leaf_index.into();
692            let leaf_hash = leaf.hash();
693            let expected_hash = self.get_node_hash(node_index);
694
695            if leaf_hash != expected_hash {
696                return Err(DeserializationError::InvalidValue(
697                    "leaf hash is inconsistent with parent inner node".into(),
698                ));
699            }
700        }
701
702        Ok(())
703    }
704}
705
706impl Default for PartialSmt {
707    /// Returns a new, empty [`PartialSmt`].
708    ///
709    /// All leaves in the returned tree are set to [`Self::EMPTY_VALUE`].
710    fn default() -> Self {
711        Self::new(Self::EMPTY_ROOT)
712    }
713}
714
715// CONVERSIONS
716// ================================================================================================
717
718impl From<super::Smt> for PartialSmt {
719    fn from(smt: super::Smt) -> Self {
720        Self {
721            root: smt.root(),
722            num_entries: smt.num_entries(),
723            leaves: smt.leaves().map(|(idx, leaf)| (idx.position(), leaf.clone())).collect(),
724            inner_nodes: smt.inner_node_indices().collect(),
725        }
726    }
727}
728
729// SERIALIZATION
730// ================================================================================================
731
732impl Serializable for PartialSmt {
733    fn write_into<W: ByteWriter>(&self, target: &mut W) {
734        let unique_rep = self.to_unique_nodes();
735        unique_rep.write_into(target);
736    }
737}
738
739impl Deserializable for PartialSmt {
740    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
741        let unique_rep = UniqueNodes::read_from(source)?;
742        PartialSmt::from_unique_nodes(unique_rep)
743            .map_err(|e| DeserializationError::InvalidValue(format!("{e}")))
744    }
745}