Skip to main content

miden_crypto/merkle/mmr/
partial.rs

1use alloc::{
2    collections::{BTreeMap, BTreeSet},
3    string::ToString,
4    vec::Vec,
5};
6
7use super::{MmrDelta, MmrPath, MmrProof};
8use crate::{
9    Word,
10    hash::poseidon2::Poseidon2,
11    merkle::{
12        InnerNodeInfo, MerklePath,
13        mmr::{InOrderIndex, MmrError, MmrPeaks, forest::Forest},
14    },
15    utils::{ByteReader, ByteWriter, Deserializable, Serializable},
16};
17
18// TYPE ALIASES
19// ================================================================================================
20
21type NodeMap = BTreeMap<InOrderIndex, Word>;
22
23// PARTIAL MERKLE MOUNTAIN RANGE
24// ================================================================================================
25/// Partially materialized Merkle Mountain Range (MMR), used to efficiently store and update the
26/// authentication paths for a subset of the elements in a full MMR.
27///
28/// This structure stores both the authentication paths and the leaf values for tracked leaves.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct PartialMmr {
31    /// The version of the MMR.
32    ///
33    /// This value serves the following purposes:
34    ///
35    /// - The forest is a counter for the total number of elements in the MMR.
36    /// - Since the MMR is an append-only structure, every change to it causes a change to the
37    ///   `forest`, so this value has a dual purpose as a version tag.
38    /// - The bits in the forest also corresponds to the count and size of every perfect binary
39    ///   tree that composes the MMR structure, which server to compute indexes and perform
40    ///   validation.
41    pub(crate) forest: Forest,
42
43    /// The MMR peaks.
44    ///
45    /// The peaks are used for two reasons:
46    ///
47    /// 1. It authenticates the addition of an element to the [PartialMmr], ensuring only valid
48    ///    elements are tracked.
49    /// 2. During a MMR update peaks can be merged by hashing the left and right hand sides. The
50    ///    peaks are used as the left hand.
51    ///
52    /// All the peaks of every tree in the MMR forest. The peaks are always ordered by number of
53    /// leaves, starting from the peak with most children, to the one with least.
54    pub(crate) peaks: Vec<Word>,
55
56    /// Nodes used to construct merkle paths for a subset of the MMR's leaves.
57    ///
58    /// This includes both:
59    /// - Tracked leaf values at their own in-order index
60    /// - Authentication nodes needed for the merkle paths
61    ///
62    /// The elements in the MMR are referenced using a in-order tree index. This indexing scheme
63    /// permits for easy computation of the relative nodes (left/right children, sibling, parent),
64    /// which is useful for traversal. The indexing is also stable, meaning that merges to the
65    /// trees in the MMR can be represented without rewrites of the indexes.
66    pub(crate) nodes: NodeMap,
67
68    /// Set of leaf positions that are being tracked.
69    pub(crate) tracked_leaves: BTreeSet<usize>,
70}
71
72impl Default for PartialMmr {
73    /// Creates a new [PartialMmr] with default values.
74    fn default() -> Self {
75        let forest = Forest::empty();
76        let peaks = Vec::new();
77        let nodes = BTreeMap::new();
78        let tracked_leaves = BTreeSet::new();
79
80        Self { forest, peaks, nodes, tracked_leaves }
81    }
82}
83
84impl PartialMmr {
85    /// Marker byte separating `nodes` from the tracked leaf vector. This makes format corruption
86    /// detectable and prevents ambiguity between adjacent variable-length fields.
87    const TRACKED_LEAVES_MARKER: u8 = 0xff;
88
89    // CONSTRUCTORS
90    // --------------------------------------------------------------------------------------------
91
92    /// Returns a new [PartialMmr] instantiated from the specified peaks.
93    pub fn from_peaks(peaks: MmrPeaks) -> Self {
94        let forest = peaks.forest();
95        let peaks = peaks.into();
96        let nodes = BTreeMap::new();
97        let tracked_leaves = BTreeSet::new();
98
99        Self { forest, peaks, nodes, tracked_leaves }
100    }
101
102    /// Returns a new [PartialMmr] instantiated from the specified components.
103    ///
104    /// This constructor validates the consistency between peaks, nodes, and tracked_leaves:
105    /// - All tracked leaf positions must be within forest bounds.
106    /// - All tracked leaves must have their values in the nodes map.
107    /// - All tracked leaves must have complete authentication paths.
108    /// - All node indices must be valid leaf or internal node positions within the forest.
109    ///
110    /// Note: This performs structural validation only. It does not verify that authentication
111    /// paths for tracked leaves are cryptographically consistent with the peaks.
112    ///
113    /// # Errors
114    /// Returns an error if the components are inconsistent.
115    pub fn from_parts(
116        peaks: MmrPeaks,
117        nodes: NodeMap,
118        tracked_leaves: BTreeSet<usize>,
119    ) -> Result<Self, MmrError> {
120        let forest = peaks.forest();
121        let num_leaves = forest.num_leaves();
122
123        // Validate that all tracked leaf positions are within forest bounds and have values
124        for &pos in &tracked_leaves {
125            if pos >= num_leaves {
126                return Err(MmrError::InconsistentPartialMmr(format!(
127                    "tracked leaf position {pos} is out of bounds (forest has {num_leaves} leaves)"
128                )));
129            }
130            let leaf_idx = InOrderIndex::from_leaf_pos(pos);
131            if !nodes.contains_key(&leaf_idx) {
132                return Err(MmrError::InconsistentPartialMmr(format!(
133                    "tracked leaf at position {pos} has no value in nodes"
134                )));
135            }
136        }
137
138        // Validate that all node indices correspond to actual nodes in the forest
139        // This catches: empty forest with nodes, index 0, out of bounds, and separator indices
140        for idx in nodes.keys() {
141            if !forest.is_valid_in_order_index(idx) {
142                return Err(MmrError::InconsistentPartialMmr(format!(
143                    "node index {} is not a valid index in the forest",
144                    idx.inner()
145                )));
146            }
147        }
148
149        let peaks = peaks.into();
150        let partial_mmr = Self { forest, peaks, nodes, tracked_leaves };
151
152        // Validate that every tracked leaf has a complete authentication path.
153        for &pos in &partial_mmr.tracked_leaves {
154            match partial_mmr.open(pos) {
155                Ok(Some(_)) => {},
156                Ok(None) => {
157                    return Err(MmrError::InconsistentPartialMmr(format!(
158                        "tracked leaf at position {pos} has no authentication path"
159                    )));
160                },
161                Err(err) => return Err(err),
162            }
163        }
164
165        Ok(partial_mmr)
166    }
167
168    /// Returns a new [PartialMmr] instantiated from the specified components without validation.
169    ///
170    /// # Preconditions
171    /// This constructor does not check the consistency between peaks, nodes, and tracked_leaves.
172    /// If the specified components are inconsistent, the returned partial MMR may exhibit
173    /// undefined behavior.
174    ///
175    /// Use this method only when you are certain the components are valid, for example when
176    /// constructing from trusted sources or for performance-critical code paths.
177    pub fn from_parts_unchecked(
178        peaks: MmrPeaks,
179        nodes: NodeMap,
180        tracked_leaves: BTreeSet<usize>,
181    ) -> Self {
182        let forest = peaks.forest();
183        let peaks = peaks.into();
184
185        Self { forest, peaks, nodes, tracked_leaves }
186    }
187
188    // PUBLIC ACCESSORS
189    // --------------------------------------------------------------------------------------------
190
191    /// Returns the current `forest` of this [PartialMmr].
192    ///
193    /// This value corresponds to the version of the [PartialMmr] and the number of leaves in the
194    /// underlying MMR.
195    pub fn forest(&self) -> Forest {
196        self.forest
197    }
198
199    /// Returns the number of leaves in the underlying MMR for this [PartialMmr].
200    pub fn num_leaves(&self) -> usize {
201        self.forest.num_leaves()
202    }
203
204    /// Returns the peaks of the MMR for this [PartialMmr].
205    pub fn peaks(&self) -> MmrPeaks {
206        // expect() is OK here because the constructor ensures that MMR peaks can be constructed
207        // correctly
208        MmrPeaks::new(self.forest, self.peaks.clone()).expect("invalid MMR peaks")
209    }
210
211    /// Returns true if this partial MMR tracks an authentication path for the leaf at the
212    /// specified position.
213    pub fn is_tracked(&self, pos: usize) -> bool {
214        self.tracked_leaves.contains(&pos)
215    }
216
217    /// Returns the leaf value at the specified position, or `None` if the leaf is not tracked.
218    pub fn get(&self, pos: usize) -> Option<Word> {
219        if !self.tracked_leaves.contains(&pos) {
220            return None;
221        }
222        let leaf_idx = InOrderIndex::from_leaf_pos(pos);
223        self.nodes.get(&leaf_idx).copied()
224    }
225
226    /// Returns an iterator over the tracked leaves as (position, value) pairs.
227    pub fn leaves(&self) -> impl Iterator<Item = (usize, Word)> + '_ {
228        self.tracked_leaves.iter().map(|&pos| {
229            let leaf_idx = InOrderIndex::from_leaf_pos(pos);
230            let leaf = *self.nodes.get(&leaf_idx).expect("tracked leaf must have value in nodes");
231            (pos, leaf)
232        })
233    }
234
235    /// Returns an [MmrProof] for the leaf at the specified position, or `None` if not tracked.
236    ///
237    /// Note: The leaf position is the 0-indexed number corresponding to the order the leaves were
238    /// added, this corresponds to the MMR size _prior_ to adding the element. So the 1st element
239    /// has position 0, the second position 1, and so on.
240    ///
241    /// # Errors
242    /// Returns an error if the specified position is greater-or-equal than the number of leaves
243    /// in the underlying MMR.
244    pub fn open(&self, pos: usize) -> Result<Option<MmrProof>, MmrError> {
245        let tree_bit = self
246            .forest
247            .leaf_to_corresponding_tree(pos)
248            .ok_or(MmrError::PositionNotFound(pos))?;
249
250        // Check if the leaf is tracked
251        if !self.tracked_leaves.contains(&pos) {
252            return Ok(None);
253        }
254
255        // Get the leaf value from nodes
256        let leaf_idx = InOrderIndex::from_leaf_pos(pos);
257        let leaf = *self.nodes.get(&leaf_idx).expect("tracked leaf must have value in nodes");
258
259        // Collect authentication path nodes
260        let depth = tree_bit as usize;
261        let mut nodes = Vec::with_capacity(depth);
262        let mut idx = leaf_idx;
263
264        for _ in 0..depth {
265            let Some(node) = self.nodes.get(&idx.sibling()) else {
266                return Err(MmrError::InconsistentPartialMmr(format!(
267                    "missing sibling for tracked leaf at position {pos}"
268                )));
269            };
270            nodes.push(*node);
271            idx = idx.parent();
272        }
273
274        let path = MmrPath::new(self.forest, pos, MerklePath::new(nodes));
275        Ok(Some(MmrProof::new(path, leaf)))
276    }
277
278    // ITERATORS
279    // --------------------------------------------------------------------------------------------
280
281    /// Returns an iterator nodes of all authentication paths of this [PartialMmr].
282    pub fn nodes(&self) -> impl Iterator<Item = (&InOrderIndex, &Word)> {
283        self.nodes.iter()
284    }
285
286    /// Returns an iterator over inner nodes of this [PartialMmr] for the specified leaves.
287    ///
288    /// The order of iteration is not defined. If a leaf is not presented in this partial MMR it
289    /// is silently ignored.
290    pub fn inner_nodes<'a, I: Iterator<Item = (usize, Word)> + 'a>(
291        &'a self,
292        mut leaves: I,
293    ) -> impl Iterator<Item = InnerNodeInfo> + 'a {
294        let stack = if let Some((pos, leaf)) = leaves.next() {
295            let idx = InOrderIndex::from_leaf_pos(pos);
296            vec![(idx, leaf)]
297        } else {
298            Vec::new()
299        };
300
301        InnerNodeIterator {
302            nodes: &self.nodes,
303            leaves,
304            stack,
305            seen_nodes: BTreeSet::new(),
306        }
307    }
308
309    // STATE MUTATORS
310    // --------------------------------------------------------------------------------------------
311
312    /// Adds a new peak and optionally track it. Returns a vector of the authentication nodes
313    /// inserted into this [PartialMmr] as a result of this operation.
314    ///
315    /// When `track` is `true` the new leaf is tracked and its value is stored.
316    ///
317    /// # Errors
318    /// Returns an error if the MMR exceeds the maximum supported forest size.
319    pub fn add(&mut self, leaf: Word, track: bool) -> Result<Vec<(InOrderIndex, Word)>, MmrError> {
320        // Fail early before mutating nodes.
321        self.forest.append_leaf()?;
322        // The smallest tree height equals the number of merges because adding a leaf is like
323        // adding 1 in binary: each carry corresponds to a merge. For example, forest 3 (0b11)
324        // + 1 = 4 (0b100) requires 2 carries/merges to form a tree of height 2.
325        let num_merges = self.forest.smallest_tree_height_unchecked();
326        let mut new_nodes = Vec::with_capacity(num_merges + 1);
327
328        // Store the leaf value at its own index if tracking
329        let leaf_pos = self.forest.num_leaves() - 1;
330        let leaf_idx = InOrderIndex::from_leaf_pos(leaf_pos);
331        if track {
332            self.tracked_leaves.insert(leaf_pos);
333            self.nodes.insert(leaf_idx, leaf);
334            new_nodes.push((leaf_idx, leaf));
335        }
336
337        let peak = if num_merges == 0 {
338            leaf
339        } else {
340            let mut track_right = track;
341            // Check if the previous dangling leaf was tracked.
342            // If num_merges > 0, there was a single-leaf tree that is now being merged.
343            let prev_last_pos = self.forest.num_leaves() - 2;
344            let mut track_left = self.tracked_leaves.contains(&prev_last_pos);
345
346            let mut right = leaf;
347            let mut right_idx = self.forest.rightmost_in_order_index_unchecked();
348
349            for _ in 0..num_merges {
350                let left = self.peaks.pop().expect("Missing peak");
351                let left_idx = right_idx.sibling();
352
353                if track_right {
354                    let old = self.nodes.insert(left_idx, left);
355                    // It's valid to insert if: nothing was there, or same value was there
356                    // (tracked leaf value can match auth node for its sibling)
357                    debug_assert!(
358                        old.is_none() || old == Some(left),
359                        "Idx {left_idx:?} already contained a different element {old:?}",
360                    );
361                    if old.is_none() {
362                        new_nodes.push((left_idx, left));
363                    }
364                };
365                if track_left {
366                    let old = self.nodes.insert(right_idx, right);
367                    debug_assert!(
368                        old.is_none() || old == Some(right),
369                        "Idx {right_idx:?} already contained a different element {old:?}",
370                    );
371                    if old.is_none() {
372                        new_nodes.push((right_idx, right));
373                    }
374                };
375
376                // Update state for the next iteration.
377                // --------------------------------------------------------------------------------
378
379                // This layer is merged, go up one layer.
380                right_idx = right_idx.parent();
381
382                // Merge the current layer. The result is either the right element of the next
383                // merge, or a new peak.
384                right = Poseidon2::merge(&[left, right]);
385
386                // This iteration merged the left and right nodes, the new value is always used as
387                // the next iteration's right node. Therefore the tracking flags of this iteration
388                // have to be merged into the right side only.
389                track_right = track_right || track_left;
390
391                // On the next iteration, a peak will be merged. If any of its children are tracked,
392                // then we have to track the left side
393                track_left = self.is_tracked_node(right_idx.sibling());
394            }
395            right
396        };
397
398        self.peaks.push(peak);
399
400        Ok(new_nodes)
401    }
402
403    /// Adds the authentication path represented by [MerklePath] if it is valid.
404    ///
405    /// The `leaf_pos` refers to the global position of the leaf in the MMR, these are 0-indexed
406    /// values assigned in a strictly monotonic fashion as elements are inserted into the MMR,
407    /// this value corresponds to the values used in the MMR structure.
408    ///
409    /// The `leaf` corresponds to the value at `leaf_pos`, and `path` is the authentication path for
410    /// that element up to its corresponding Mmr peak. Both the authentication path and the leaf
411    /// value are stored.
412    pub fn track(
413        &mut self,
414        leaf_pos: usize,
415        leaf: Word,
416        path: &MerklePath,
417    ) -> Result<(), MmrError> {
418        // Checks there is a tree with same depth as the authentication path, if not the path is
419        // invalid.
420        let path_depth = path.depth();
421        let tree_leaves =
422            1usize.checked_shl(path_depth as u32).ok_or(MmrError::UnknownPeak(path_depth))?;
423        let tree = Forest::new(tree_leaves).map_err(|_| MmrError::UnknownPeak(path_depth))?;
424        if (tree & self.forest).is_empty() {
425            return Err(MmrError::UnknownPeak(path_depth));
426        };
427
428        // Ensure the position belongs to the tree selected by the authentication path. Besides
429        // rejecting positions outside the forest, this makes the subtraction below safe: a leaf
430        // in the target tree always follows every larger tree in the forest.
431        let owning_tree = self
432            .forest
433            .leaf_to_corresponding_tree(leaf_pos)
434            .ok_or(MmrError::PositionNotFound(leaf_pos))?;
435        if owning_tree != u32::from(path_depth) {
436            return Err(MmrError::PositionNotFound(leaf_pos));
437        }
438
439        // ignore the trees smaller than the target (these elements are position after the current
440        // target and don't affect the target leaf_pos)
441        let target_forest = self.forest ^ (self.forest & tree.all_smaller_trees_unchecked());
442        let peak_pos = target_forest.num_trees() - 1;
443
444        // translate from mmr leaf_pos to merkle path
445        let path_idx = leaf_pos - (target_forest ^ tree).num_leaves();
446
447        // Compute the root of the authentication path, and check it matches the current version of
448        // the PartialMmr.
449        let computed = path
450            .compute_root(path_idx as u64, leaf)
451            .map_err(MmrError::MerkleRootComputationFailed)?;
452        if self.peaks[peak_pos] != computed {
453            return Err(MmrError::PeakPathMismatch);
454        }
455
456        // Mark the leaf as tracked
457        self.tracked_leaves.insert(leaf_pos);
458
459        // Store the leaf value at its own index
460        let leaf_idx = InOrderIndex::from_leaf_pos(leaf_pos);
461        self.nodes.insert(leaf_idx, leaf);
462
463        // Store the authentication path nodes
464        let mut idx = leaf_idx;
465        for node in path.nodes() {
466            self.nodes.insert(idx.sibling(), *node);
467            idx = idx.parent();
468        }
469
470        Ok(())
471    }
472
473    /// Removes a leaf of the [PartialMmr] and the unused nodes from the authentication path.
474    ///
475    /// Returns a vector of the authentication nodes removed from this [PartialMmr] as a result
476    /// of this operation. This is useful for client-side pruning, where the caller needs to know
477    /// which nodes can be deleted from storage.
478    ///
479    /// Note: `leaf_pos` corresponds to the position in the MMR and not on an individual tree.
480    /// If `leaf_pos` is invalid for the current forest, this method returns an empty vector.
481    pub fn untrack(&mut self, leaf_pos: usize) -> Vec<(InOrderIndex, Word)> {
482        // Remove from tracked leaves set
483        self.tracked_leaves.remove(&leaf_pos);
484
485        let mut idx = InOrderIndex::from_leaf_pos(leaf_pos);
486        let mut removed = Vec::new();
487
488        // Check if the sibling leaf is still tracked. If so, we need to keep our leaf value
489        // as an auth node for the sibling, and keep all auth nodes above.
490        let sibling_idx = idx.sibling();
491        let sibling_pos = sibling_idx.to_leaf_pos().expect("sibling of a leaf is always a leaf");
492        if self.tracked_leaves.contains(&sibling_pos) {
493            // Sibling is tracked, so don't remove anything - our leaf value and all auth
494            // nodes above are still needed for the sibling's proof.
495            return removed;
496        }
497
498        let Some(rel_pos) = self.forest.leaf_relative_position(leaf_pos) else {
499            // Invalid MMR leaf position - treat as a no-op.
500            return removed;
501        };
502        let tree_start = leaf_pos - rel_pos;
503
504        // Remove the leaf value itself
505        if let Some(word) = self.nodes.remove(&idx) {
506            removed.push((idx, word));
507        }
508
509        // Remove authentication path nodes that are no longer needed.
510        loop {
511            // At each level, identify the subtree covered by `idx`. If any tracked leaf still
512            // exists in this range, nodes above this point are still required.
513            let level = idx.level() as usize;
514            let subtree_size = 1usize << level;
515            let subtree_start_rel = (rel_pos >> level) << level;
516            let subtree_start = tree_start + subtree_start_rel;
517            let subtree_end = subtree_start + subtree_size;
518            if self.tracked_leaves.range(subtree_start..subtree_end).next().is_some() {
519                break;
520            }
521
522            let sibling_idx = idx.sibling();
523
524            // Try to remove the sibling auth node
525            let Some(word) = self.nodes.remove(&sibling_idx) else {
526                break;
527            };
528            removed.push((sibling_idx, word));
529
530            // If `idx` is present, it was added for another element's authentication.
531            if self.nodes.contains_key(&idx) {
532                break;
533            }
534            idx = idx.parent();
535        }
536
537        removed
538    }
539
540    /// Applies updates to this [PartialMmr] and returns a vector of new authentication nodes
541    /// inserted into the partial MMR.
542    pub fn apply(&mut self, delta: MmrDelta) -> Result<Vec<(InOrderIndex, Word)>, MmrError> {
543        if delta.forest < self.forest {
544            return Err(MmrError::InvalidPeaks(format!(
545                "forest of mmr delta {} is less than current forest {}",
546                delta.forest, self.forest
547            )));
548        }
549
550        let mut inserted_nodes = Vec::new();
551
552        if delta.forest == self.forest {
553            if !delta.data.is_empty() {
554                return Err(MmrError::InvalidUpdate);
555            }
556
557            return Ok(inserted_nodes);
558        }
559
560        // find the trees to merge (bitmask of existing trees that will be combined)
561        let changes = self.forest.num_leaves() ^ delta.forest.num_leaves();
562        // `largest_tree_unchecked()` panics if `changes` is empty. `changes` cannot be empty
563        // unless `self.forest == delta.forest`, which is guarded against above.
564        let largest = super::forest::largest_tree_from_mask(changes);
565        // The largest tree itself also cannot be an empty forest, so this cannot panic either.
566        let trees_to_merge = self.forest & largest.all_smaller_trees_unchecked();
567
568        // count the number elements needed to produce largest from the current state
569        let (merge_count, new_peaks) = if !trees_to_merge.is_empty() {
570            let depth = largest.smallest_tree_height_unchecked();
571            // `trees_to_merge` also cannot be an empty forest, so this cannot panic either.
572            let skipped = trees_to_merge.smallest_tree_height_unchecked();
573            let computed = trees_to_merge.num_trees() - 1;
574            let merge_count = depth - skipped - computed;
575
576            let new_peaks = delta.forest & largest.all_smaller_trees_unchecked();
577
578            (merge_count, new_peaks)
579        } else {
580            let new_peaks = Forest::new(changes)
581                .expect("changes must be a valid forest under apply invariants");
582            (0, new_peaks)
583        };
584
585        // verify the delta size
586        if delta.data.len() != merge_count + new_peaks.num_trees() {
587            return Err(MmrError::InvalidUpdate);
588        }
589
590        // keeps track of how many data elements from the update have been consumed
591        let mut update_count = 0;
592
593        if !trees_to_merge.is_empty() {
594            // starts at the smallest peak and follows the merged peaks
595            let mut peak_idx = self.forest.root_in_order_index_unchecked();
596
597            // match order of the update data while applying it
598            self.peaks.reverse();
599
600            let mut track = false;
601
602            let mut peak_count = 0;
603            let mut target = trees_to_merge.smallest_tree_unchecked();
604            let mut new = delta.data[0];
605            update_count += 1;
606
607            while target < largest {
608                // Check if either the left or right subtrees have nodes saved for authentication
609                // paths. If so, turn tracking on to update those paths.
610                if !track {
611                    track = self.is_tracked_node(peak_idx);
612                }
613
614                // update data only contains the nodes from the right subtrees, left nodes are
615                // either previously known peaks or computed values
616                let (left, right) = if !(target & trees_to_merge).is_empty() {
617                    let peak = self.peaks[peak_count];
618                    let sibling_idx = peak_idx.sibling();
619
620                    // if the sibling peak is tracked, add this peaks to the set of
621                    // authentication nodes
622                    if self.is_tracked_node(sibling_idx) {
623                        self.nodes.insert(peak_idx, new);
624                        inserted_nodes.push((peak_idx, new));
625                    }
626                    peak_count += 1;
627                    (peak, new)
628                } else {
629                    let update = delta.data[update_count];
630                    update_count += 1;
631                    (new, update)
632                };
633
634                if track {
635                    let sibling_idx = peak_idx.sibling();
636                    if peak_idx.is_left_child() {
637                        self.nodes.insert(sibling_idx, right);
638                        inserted_nodes.push((sibling_idx, right));
639                    } else {
640                        self.nodes.insert(sibling_idx, left);
641                        inserted_nodes.push((sibling_idx, left));
642                    }
643                }
644
645                peak_idx = peak_idx.parent();
646                new = Poseidon2::merge(&[left, right]);
647                target = target.next_larger_tree()?;
648            }
649
650            debug_assert!(peak_count == trees_to_merge.num_trees());
651
652            // restore the peaks order
653            self.peaks.reverse();
654            // remove the merged peaks
655            self.peaks.truncate(self.peaks.len() - peak_count);
656            // add the newly computed peak, the result of the tree merges
657            self.peaks.push(new);
658        }
659
660        // The rest of the update data is composed of peaks. None of these elements can contain
661        // tracked elements because the peaks were unknown, and it is not possible to add elements
662        // for tacking without authenticating it to a peak.
663        self.peaks.extend_from_slice(&delta.data[update_count..]);
664        self.forest = delta.forest;
665
666        debug_assert!(self.peaks.len() == self.forest.num_trees());
667
668        Ok(inserted_nodes)
669    }
670
671    // HELPER METHODS
672    // --------------------------------------------------------------------------------------------
673
674    /// Returns true if this [PartialMmr] tracks authentication path for the node at the specified
675    /// index.
676    fn is_tracked_node(&self, node_index: InOrderIndex) -> bool {
677        if let Some(leaf_pos) = node_index.to_leaf_pos() {
678            // For leaf nodes, check if the leaf is in the tracked set.
679            self.tracked_leaves.contains(&leaf_pos)
680        } else {
681            let left_child = node_index.left_child();
682            let right_child = node_index.right_child();
683            self.nodes.contains_key(&left_child) | self.nodes.contains_key(&right_child)
684        }
685    }
686}
687
688// CONVERSIONS
689// ================================================================================================
690
691impl From<MmrPeaks> for PartialMmr {
692    fn from(peaks: MmrPeaks) -> Self {
693        Self::from_peaks(peaks)
694    }
695}
696
697impl From<PartialMmr> for MmrPeaks {
698    fn from(partial_mmr: PartialMmr) -> Self {
699        // Safety: the [PartialMmr] maintains the constraints the number of true bits in the forest
700        // matches the number of peaks, as required by the [MmrPeaks]
701        MmrPeaks::new(partial_mmr.forest, partial_mmr.peaks).unwrap()
702    }
703}
704
705impl From<&MmrPeaks> for PartialMmr {
706    fn from(peaks: &MmrPeaks) -> Self {
707        Self::from_peaks(peaks.clone())
708    }
709}
710
711impl From<&PartialMmr> for MmrPeaks {
712    fn from(partial_mmr: &PartialMmr) -> Self {
713        // Safety: the [PartialMmr] maintains the constraints the number of true bits in the forest
714        // matches the number of peaks, as required by the [MmrPeaks]
715        MmrPeaks::new(partial_mmr.forest, partial_mmr.peaks.clone()).unwrap()
716    }
717}
718
719// ITERATORS
720// ================================================================================================
721
722/// An iterator over every inner node of the [PartialMmr].
723pub struct InnerNodeIterator<'a, I: Iterator<Item = (usize, Word)>> {
724    nodes: &'a NodeMap,
725    leaves: I,
726    stack: Vec<(InOrderIndex, Word)>,
727    seen_nodes: BTreeSet<InOrderIndex>,
728}
729
730impl<I: Iterator<Item = (usize, Word)>> Iterator for InnerNodeIterator<'_, I> {
731    type Item = InnerNodeInfo;
732
733    fn next(&mut self) -> Option<Self::Item> {
734        while let Some((idx, node)) = self.stack.pop() {
735            let parent_idx = idx.parent();
736            let new_node = self.seen_nodes.insert(parent_idx);
737
738            // if we haven't seen this node's parent before, and the node has a sibling, return
739            // the inner node defined by the parent of this node, and move up the branch
740            if new_node && let Some(sibling) = self.nodes.get(&idx.sibling()) {
741                let (left, right) = if parent_idx.left_child() == idx {
742                    (node, *sibling)
743                } else {
744                    (*sibling, node)
745                };
746                let parent = Poseidon2::merge(&[left, right]);
747                let inner_node = InnerNodeInfo { value: parent, left, right };
748
749                self.stack.push((parent_idx, parent));
750                return Some(inner_node);
751            }
752
753            // the previous leaf has been processed, try to process the next leaf
754            if let Some((pos, leaf)) = self.leaves.next() {
755                let idx = InOrderIndex::from_leaf_pos(pos);
756                self.stack.push((idx, leaf));
757            }
758        }
759
760        None
761    }
762}
763
764impl Serializable for PartialMmr {
765    fn write_into<W: ByteWriter>(&self, target: &mut W) {
766        self.forest.num_leaves().write_into(target);
767        self.peaks.write_into(target);
768        self.nodes.write_into(target);
769        // Write a marker before tracked leaves to guard against malformed/truncated payloads.
770        target.write_u8(Self::TRACKED_LEAVES_MARKER);
771        let tracked: Vec<usize> = self.tracked_leaves.iter().copied().collect();
772        tracked.write_into(target);
773    }
774}
775
776impl Deserializable for PartialMmr {
777    fn read_from<R: ByteReader>(
778        source: &mut R,
779    ) -> Result<Self, crate::utils::DeserializationError> {
780        use crate::utils::DeserializationError;
781
782        let forest = Forest::new(usize::read_from(source)?)?;
783        let peaks_vec = Vec::<Word>::read_from(source)?;
784        let nodes = NodeMap::read_from(source)?;
785        if !source.has_more_bytes() {
786            return Err(DeserializationError::UnexpectedEOF);
787        }
788        let marker = source.read_u8()?;
789        if marker != Self::TRACKED_LEAVES_MARKER {
790            return Err(DeserializationError::InvalidValue(
791                "unknown partial mmr serialization format".to_string(),
792            ));
793        }
794        let tracked: Vec<usize> = Vec::read_from(source)?;
795        let mut tracked_leaves = BTreeSet::new();
796        for leaf_pos in tracked {
797            if !tracked_leaves.insert(leaf_pos) {
798                return Err(DeserializationError::InvalidValue(
799                    "duplicate tracked leaf in partial mmr encoding".to_string(),
800                ));
801            }
802        }
803
804        // Construct MmrPeaks to validate forest/peaks consistency
805        let peaks = MmrPeaks::new(forest, peaks_vec).map_err(|e| {
806            DeserializationError::InvalidValue(format!("invalid partial mmr peaks: {e}"))
807        })?;
808
809        // Use validating constructor
810        Self::from_parts(peaks, nodes, tracked_leaves)
811            .map_err(|e| DeserializationError::InvalidValue(format!("invalid partial mmr: {e}")))
812    }
813}
814
815// TESTS
816// ================================================================================================
817
818#[cfg(test)]
819mod tests {
820    use alloc::{
821        collections::{BTreeMap, BTreeSet},
822        vec::Vec,
823    };
824
825    use super::{MerklePath, MmrError, MmrPeaks, PartialMmr};
826    use crate::{
827        Word,
828        merkle::{
829            NodeIndex, int_to_node,
830            mmr::{InOrderIndex, Mmr, forest::Forest},
831            store::MerkleStore,
832        },
833        utils::{ByteWriter, Deserializable, DeserializationError, Serializable},
834    };
835
836    const LEAVES: [Word; 7] = [
837        int_to_node(0),
838        int_to_node(1),
839        int_to_node(2),
840        int_to_node(3),
841        int_to_node(4),
842        int_to_node(5),
843        int_to_node(6),
844    ];
845
846    #[test]
847    fn test_partial_mmr_apply_delta() {
848        // build an MMR with 10 nodes (2 peaks) and a partial MMR based on it
849        let mut mmr = Mmr::default();
850        (0..10).for_each(|i| mmr.add(int_to_node(i)).unwrap());
851        let mut partial_mmr: PartialMmr = mmr.peaks().into();
852
853        // add authentication path for position 1 and 8
854        {
855            let node = mmr.get(1).unwrap();
856            let proof = mmr.open(1).unwrap();
857            partial_mmr.track(1, node, proof.path().merkle_path()).unwrap();
858        }
859
860        {
861            let node = mmr.get(8).unwrap();
862            let proof = mmr.open(8).unwrap();
863            partial_mmr.track(8, node, proof.path().merkle_path()).unwrap();
864        }
865
866        // add 2 more nodes into the MMR and validate apply_delta()
867        (10..12).for_each(|i| mmr.add(int_to_node(i)).unwrap());
868        validate_apply_delta(&mmr, &mut partial_mmr);
869
870        // add 1 more node to the MMR, validate apply_delta() and start tracking the node
871        mmr.add(int_to_node(12)).unwrap();
872        validate_apply_delta(&mmr, &mut partial_mmr);
873        {
874            let node = mmr.get(12).unwrap();
875            let proof = mmr.open(12).unwrap();
876            partial_mmr.track(12, node, proof.path().merkle_path()).unwrap();
877            // Position 12 is the last leaf (dangling) and should now be tracked
878            assert!(partial_mmr.is_tracked(12));
879        }
880
881        // by this point we are tracking authentication paths for positions: 1, 8, and 12
882
883        // add 3 more nodes to the MMR (collapses to 1 peak) and validate apply_delta()
884        (13..16).for_each(|i| mmr.add(int_to_node(i)).unwrap());
885        validate_apply_delta(&mmr, &mut partial_mmr);
886    }
887
888    fn validate_apply_delta(mmr: &Mmr, partial: &mut PartialMmr) {
889        // Get tracked leaf positions
890        let tracked_positions: Vec<_> = partial.tracked_leaves.iter().copied().collect();
891        let nodes_before = partial.nodes.clone();
892
893        // compute and apply delta
894        let delta = mmr.get_delta(partial.forest(), mmr.forest()).unwrap();
895        let nodes_delta = partial.apply(delta).unwrap();
896
897        // new peaks were computed correctly
898        assert_eq!(mmr.peaks(), partial.peaks());
899
900        let mut expected_nodes = nodes_before;
901        for (key, value) in nodes_delta {
902            // nodes should not be duplicated
903            assert!(expected_nodes.insert(key, value).is_none());
904        }
905
906        // new nodes should be a combination of original nodes and delta
907        assert_eq!(expected_nodes, partial.nodes);
908
909        // make sure tracked leaves open to the same proofs as in the underlying MMR
910        for pos in tracked_positions {
911            let proof1 = partial.open(pos).unwrap().unwrap();
912            let proof2 = mmr.open(pos).unwrap();
913            assert_eq!(proof1, proof2);
914        }
915    }
916
917    #[test]
918    fn test_partial_mmr_inner_nodes_iterator() {
919        // build the MMR
920        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
921        let first_peak = mmr.peaks().peaks()[0];
922
923        // -- test single tree ----------------------------
924
925        // get path and node for position 1
926        let node1 = mmr.get(1).unwrap();
927        let proof1 = mmr.open(1).unwrap();
928
929        // create partial MMR and add authentication path to node at position 1
930        let mut partial_mmr: PartialMmr = mmr.peaks().into();
931        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
932
933        // empty iterator should have no nodes
934        assert_eq!(partial_mmr.inner_nodes([].iter().cloned()).next(), None);
935
936        // build Merkle store from authentication paths in partial MMR
937        let mut store: MerkleStore = MerkleStore::new();
938        store.extend(partial_mmr.inner_nodes([(1, node1)].iter().cloned()));
939
940        let index1 = NodeIndex::new(2, 1).unwrap();
941        let path1 = store.get_path(first_peak, index1).unwrap().path;
942
943        assert_eq!(path1, *proof1.path().merkle_path());
944
945        // -- test no duplicates --------------------------
946
947        // build the partial MMR
948        let mut partial_mmr: PartialMmr = mmr.peaks().into();
949
950        let node0 = mmr.get(0).unwrap();
951        let proof0 = mmr.open(0).unwrap();
952
953        let node2 = mmr.get(2).unwrap();
954        let proof2 = mmr.open(2).unwrap();
955
956        partial_mmr.track(0, node0, proof0.path().merkle_path()).unwrap();
957        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
958        partial_mmr.track(2, node2, proof2.path().merkle_path()).unwrap();
959
960        // make sure there are no duplicates
961        let leaves = [(0, node0), (1, node1), (2, node2)];
962        let mut nodes = BTreeSet::new();
963        for node in partial_mmr.inner_nodes(leaves.iter().cloned()) {
964            assert!(nodes.insert(node.value));
965        }
966
967        // and also that the store is still be built correctly
968        store.extend(partial_mmr.inner_nodes(leaves.iter().cloned()));
969
970        let index0 = NodeIndex::new(2, 0).unwrap();
971        let index1 = NodeIndex::new(2, 1).unwrap();
972        let index2 = NodeIndex::new(2, 2).unwrap();
973
974        let path0 = store.get_path(first_peak, index0).unwrap().path;
975        let path1 = store.get_path(first_peak, index1).unwrap().path;
976        let path2 = store.get_path(first_peak, index2).unwrap().path;
977
978        assert_eq!(path0, *proof0.path().merkle_path());
979        assert_eq!(path1, *proof1.path().merkle_path());
980        assert_eq!(path2, *proof2.path().merkle_path());
981
982        // -- test multiple trees -------------------------
983
984        // build the partial MMR
985        let mut partial_mmr: PartialMmr = mmr.peaks().into();
986
987        let node5 = mmr.get(5).unwrap();
988        let proof5 = mmr.open(5).unwrap();
989
990        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
991        partial_mmr.track(5, node5, proof5.path().merkle_path()).unwrap();
992
993        // build Merkle store from authentication paths in partial MMR
994        let mut store: MerkleStore = MerkleStore::new();
995        store.extend(partial_mmr.inner_nodes([(1, node1), (5, node5)].iter().cloned()));
996
997        let index1 = NodeIndex::new(2, 1).unwrap();
998        let index5 = NodeIndex::new(1, 1).unwrap();
999
1000        let second_peak = mmr.peaks().peaks()[1];
1001
1002        let path1 = store.get_path(first_peak, index1).unwrap().path;
1003        let path5 = store.get_path(second_peak, index5).unwrap().path;
1004
1005        assert_eq!(path1, *proof1.path().merkle_path());
1006        assert_eq!(path5, *proof5.path().merkle_path());
1007    }
1008
1009    #[test]
1010    fn test_partial_mmr_add_without_track() {
1011        let mut mmr = Mmr::default();
1012        let empty_peaks = MmrPeaks::new(Forest::empty(), vec![]).unwrap();
1013        let mut partial_mmr = PartialMmr::from_peaks(empty_peaks);
1014
1015        for el in (0..256).map(int_to_node) {
1016            mmr.add(el).unwrap();
1017            partial_mmr.add(el, false).unwrap();
1018
1019            assert_eq!(mmr.peaks(), partial_mmr.peaks());
1020            assert_eq!(mmr.forest(), partial_mmr.forest());
1021        }
1022    }
1023
1024    #[test]
1025    fn test_partial_mmr_add_with_track() {
1026        let mut mmr = Mmr::default();
1027        let empty_peaks = MmrPeaks::new(Forest::empty(), vec![]).unwrap();
1028        let mut partial_mmr = PartialMmr::from_peaks(empty_peaks);
1029
1030        for i in 0..256 {
1031            let el = int_to_node(i as u64);
1032            mmr.add(el).unwrap();
1033            partial_mmr.add(el, true).unwrap();
1034
1035            assert_eq!(mmr.peaks(), partial_mmr.peaks());
1036            assert_eq!(mmr.forest(), partial_mmr.forest());
1037
1038            for pos in 0..i {
1039                let mmr_proof = mmr.open(pos).unwrap();
1040                let partialmmr_proof = partial_mmr.open(pos).unwrap().unwrap();
1041                assert_eq!(mmr_proof, partialmmr_proof);
1042            }
1043        }
1044    }
1045
1046    #[test]
1047    fn test_partial_mmr_add_existing_track() {
1048        let mut mmr = Mmr::try_from_iter((0..7).map(int_to_node)).unwrap();
1049
1050        // derive a partial Mmr from it which tracks authentication path to leaf 5
1051        let mut partial_mmr = PartialMmr::from_peaks(mmr.peaks());
1052        let path_to_5 = mmr.open(5).unwrap().path().merkle_path().clone();
1053        let leaf_at_5 = mmr.get(5).unwrap();
1054        partial_mmr.track(5, leaf_at_5, &path_to_5).unwrap();
1055
1056        // add a new leaf to both Mmr and partial Mmr
1057        let leaf_at_7 = int_to_node(7);
1058        mmr.add(leaf_at_7).unwrap();
1059        partial_mmr.add(leaf_at_7, false).unwrap();
1060
1061        // the openings should be the same
1062        assert_eq!(mmr.open(5).unwrap(), partial_mmr.open(5).unwrap().unwrap());
1063    }
1064
1065    #[test]
1066    fn test_partial_mmr_add_updates_tracked_dangling_leaf() {
1067        // Track a dangling leaf, then add a new untracked leaf.
1068        // The previously dangling leaf's proof should still work.
1069        let mut mmr = Mmr::default();
1070        let mut partial_mmr = PartialMmr::default();
1071
1072        // Add leaf 0 with tracking - it's a dangling leaf (forest=1)
1073        let leaf0 = int_to_node(0);
1074        mmr.add(leaf0).unwrap();
1075        partial_mmr.add(leaf0, true).unwrap();
1076
1077        // Both should produce the same proof (empty path, leaf is a peak)
1078        assert_eq!(mmr.open(0).unwrap(), partial_mmr.open(0).unwrap().unwrap());
1079
1080        // Add leaf 1 WITHOUT tracking - triggers merge, leaf 0 gets a sibling
1081        let leaf1 = int_to_node(1);
1082        mmr.add(leaf1).unwrap();
1083        partial_mmr.add(leaf1, false).unwrap();
1084
1085        // Leaf 0 should still be tracked with correct proof after merge
1086        assert!(partial_mmr.is_tracked(0));
1087        assert!(!partial_mmr.is_tracked(1));
1088        assert_eq!(mmr.open(0).unwrap(), partial_mmr.open(0).unwrap().unwrap());
1089    }
1090
1091    #[test]
1092    fn test_partial_mmr_serialization() {
1093        let mmr = Mmr::try_from_iter((0..7).map(int_to_node)).unwrap();
1094        let partial_mmr = PartialMmr::from_peaks(mmr.peaks());
1095
1096        let bytes = partial_mmr.to_bytes();
1097        let decoded = PartialMmr::read_from_bytes(&bytes).unwrap();
1098
1099        assert_eq!(partial_mmr, decoded);
1100    }
1101
1102    #[test]
1103    fn test_partial_mmr_deserialization_rejects_duplicate_tracked_leaves() {
1104        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1105        let mut partial_mmr = PartialMmr::from_peaks(mmr.peaks());
1106        let leaf_pos = 1usize;
1107        let node = mmr.get(leaf_pos).unwrap();
1108        let proof = mmr.open(leaf_pos).unwrap();
1109        partial_mmr.track(leaf_pos, node, proof.path().merkle_path()).unwrap();
1110
1111        let mut bytes = Vec::new();
1112        partial_mmr.forest.num_leaves().write_into(&mut bytes);
1113        partial_mmr.peaks.write_into(&mut bytes);
1114        partial_mmr.nodes.write_into(&mut bytes);
1115        bytes.write_u8(PartialMmr::TRACKED_LEAVES_MARKER);
1116        vec![leaf_pos, leaf_pos].write_into(&mut bytes);
1117
1118        let result = PartialMmr::read_from_bytes(&bytes);
1119
1120        assert!(matches!(result, Err(DeserializationError::InvalidValue(_))));
1121    }
1122
1123    #[test]
1124    fn test_partial_mmr_deserialization_rejects_large_forest() {
1125        let mut bytes = (Forest::MAX_LEAVES + 1).to_bytes();
1126        bytes.extend_from_slice(&0usize.to_bytes()); // empty peaks vec
1127        bytes.extend_from_slice(&0usize.to_bytes()); // empty nodes map
1128        bytes.extend_from_slice(&0usize.to_bytes()); // empty tracked vec
1129
1130        let result = PartialMmr::read_from_bytes(&bytes);
1131        assert!(matches!(result, Err(DeserializationError::InvalidValue(_))));
1132    }
1133
1134    #[test]
1135    fn test_partial_mmr_untrack() {
1136        // build the MMR
1137        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1138
1139        // get path and node for position 1
1140        let node1 = mmr.get(1).unwrap();
1141        let proof1 = mmr.open(1).unwrap();
1142
1143        // get path and node for position 2
1144        let node2 = mmr.get(2).unwrap();
1145        let proof2 = mmr.open(2).unwrap();
1146
1147        // create partial MMR and add authentication path to nodes at position 1 and 2
1148        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1149        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
1150        partial_mmr.track(2, node2, proof2.path().merkle_path()).unwrap();
1151
1152        // untrack nodes at positions 1 and 2
1153        partial_mmr.untrack(1);
1154        partial_mmr.untrack(2);
1155
1156        // nodes should not longer be tracked
1157        assert!(!partial_mmr.is_tracked(1));
1158        assert!(!partial_mmr.is_tracked(2));
1159        assert_eq!(partial_mmr.nodes().count(), 0);
1160    }
1161
1162    #[test]
1163    fn test_partial_mmr_untrack_returns_removed_nodes() {
1164        // build the MMR
1165        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1166
1167        // get path and node for position 1
1168        let node1 = mmr.get(1).unwrap();
1169        let proof1 = mmr.open(1).unwrap();
1170
1171        // create partial MMR
1172        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1173
1174        // add authentication path for position 1
1175        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
1176
1177        // collect nodes before untracking
1178        let nodes_before: BTreeSet<_> =
1179            partial_mmr.nodes().map(|(&idx, &word)| (idx, word)).collect();
1180
1181        // untrack and capture removed nodes
1182        let removed: BTreeSet<_> = partial_mmr.untrack(1).into_iter().collect();
1183
1184        // verify that all nodes that were in the partial MMR were returned
1185        assert_eq!(removed, nodes_before);
1186
1187        // verify that partial MMR is now empty
1188        assert!(!partial_mmr.is_tracked(1));
1189        assert_eq!(partial_mmr.nodes().count(), 0);
1190    }
1191
1192    #[test]
1193    fn test_partial_mmr_untrack_shared_nodes() {
1194        // build the MMR
1195        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1196
1197        // track two sibling leaves (positions 0 and 1)
1198        let node0 = mmr.get(0).unwrap();
1199        let proof0 = mmr.open(0).unwrap();
1200
1201        let node1 = mmr.get(1).unwrap();
1202        let proof1 = mmr.open(1).unwrap();
1203
1204        // create partial MMR
1205        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1206
1207        // add authentication paths for position 0 and 1
1208        partial_mmr.track(0, node0, proof0.path().merkle_path()).unwrap();
1209        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
1210
1211        // There are 3 unique nodes stored in `nodes`:
1212        // - nodes[idx0] = leaf0 (tracked leaf value, also serves as auth sibling for leaf1)
1213        // - nodes[idx1] = leaf1 (tracked leaf value, also serves as auth sibling for leaf0)
1214        // - nodes[parent_sibling] = shared higher-level auth node
1215        //
1216        // Note: Each tracked leaf's value is stored at its own InOrderIndex so that `open()`
1217        // can return an MmrProof containing the leaf value. These values also double as the
1218        // authentication siblings for their neighboring leaves.
1219        assert_eq!(partial_mmr.nodes().count(), 3);
1220
1221        // untrack position 0:
1222        // Even though pos 0 is no longer tracked, we cannot remove any nodes because:
1223        // - leaf0's value (at idx0) is still needed as the auth sibling for leaf1's path
1224        // - leaf1's value (at idx1) is needed for open(1) to return MmrProof
1225        // - parent_sibling is still needed for leaf1's path
1226        let removed0 = partial_mmr.untrack(0);
1227        assert_eq!(removed0.len(), 0);
1228        assert_eq!(partial_mmr.nodes().count(), 3);
1229        assert!(partial_mmr.is_tracked(1));
1230        assert!(!partial_mmr.is_tracked(0));
1231
1232        // untrack position 1:
1233        // Now sibling (pos 0) is NOT tracked, so all nodes can be removed:
1234        // - leaf1's value at idx1 (no longer needed for open())
1235        // - leaf0's value at idx0 (no longer needed as auth sibling)
1236        // - parent_sibling (no longer needed for any path)
1237        let removed1 = partial_mmr.untrack(1);
1238        assert_eq!(removed1.len(), 3);
1239        assert_eq!(partial_mmr.nodes().count(), 0);
1240        assert!(!partial_mmr.is_tracked(1));
1241    }
1242
1243    #[test]
1244    fn test_partial_mmr_untrack_preserves_upper_siblings() {
1245        let mut mmr = Mmr::default();
1246        (0..8).for_each(|i| mmr.add(int_to_node(i)).unwrap());
1247
1248        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1249        for pos in [0, 2] {
1250            let node = mmr.get(pos).unwrap();
1251            let proof = mmr.open(pos).unwrap();
1252            partial_mmr.track(pos, node, proof.path().merkle_path()).unwrap();
1253        }
1254
1255        partial_mmr.untrack(0);
1256
1257        let proof_partial = partial_mmr.open(2).unwrap().unwrap();
1258        let proof_full = mmr.open(2).unwrap();
1259        assert_eq!(proof_partial, proof_full);
1260    }
1261
1262    #[test]
1263    fn test_partial_mmr_deserialize_missing_marker_fails() {
1264        let mut mmr = Mmr::default();
1265        (0..3).for_each(|i| mmr.add(int_to_node(i)).unwrap());
1266        let peaks = mmr.peaks();
1267
1268        let mut bytes = Vec::new();
1269        peaks.num_leaves().write_into(&mut bytes);
1270        peaks.peaks().to_vec().write_into(&mut bytes);
1271        BTreeMap::<InOrderIndex, Word>::new().write_into(&mut bytes);
1272        assert!(PartialMmr::read_from_bytes(&bytes).is_err());
1273    }
1274
1275    #[test]
1276    fn test_partial_mmr_deserialize_invalid_marker_fails() {
1277        let mut mmr = Mmr::default();
1278        (0..3).for_each(|i| mmr.add(int_to_node(i)).unwrap());
1279        let peaks = mmr.peaks();
1280
1281        let mut bytes = Vec::new();
1282        peaks.num_leaves().write_into(&mut bytes);
1283        peaks.peaks().to_vec().write_into(&mut bytes);
1284        BTreeMap::<InOrderIndex, Word>::new().write_into(&mut bytes);
1285        bytes.write_u8(0x7f);
1286        Vec::<usize>::new().write_into(&mut bytes);
1287
1288        assert!(PartialMmr::read_from_bytes(&bytes).is_err());
1289    }
1290
1291    #[test]
1292    fn test_partial_mmr_open_returns_proof_with_leaf() {
1293        // build the MMR
1294        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1295
1296        // get leaf and proof for position 1
1297        let leaf1 = mmr.get(1).unwrap();
1298        let mmr_proof = mmr.open(1).unwrap();
1299
1300        // create partial MMR and track position 1
1301        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1302        partial_mmr.track(1, leaf1, mmr_proof.path().merkle_path()).unwrap();
1303
1304        // open should return MmrProof with the correct leaf value
1305        let partial_proof = partial_mmr.open(1).unwrap().unwrap();
1306        assert_eq!(partial_proof.leaf(), leaf1);
1307        assert_eq!(partial_proof, mmr_proof);
1308
1309        // untrack and verify open returns None
1310        partial_mmr.untrack(1);
1311        assert!(partial_mmr.open(1).unwrap().is_none());
1312    }
1313
1314    #[test]
1315    fn test_partial_mmr_add_tracks_leaf() {
1316        // create empty partial MMR
1317        let mut partial_mmr = PartialMmr::default();
1318
1319        // add leaves, tracking some
1320        let leaf0 = int_to_node(0);
1321        let leaf1 = int_to_node(1);
1322        let leaf2 = int_to_node(2);
1323
1324        partial_mmr.add(leaf0, true).unwrap(); // track
1325        partial_mmr.add(leaf1, false).unwrap(); // don't track
1326        partial_mmr.add(leaf2, true).unwrap(); // track
1327
1328        // verify tracked leaves can be opened
1329        let proof0 = partial_mmr.open(0).unwrap();
1330        assert!(proof0.is_some());
1331        assert_eq!(proof0.unwrap().leaf(), leaf0);
1332
1333        // verify untracked leaf returns None
1334        let proof1 = partial_mmr.open(1).unwrap();
1335        assert!(proof1.is_none());
1336
1337        // verify tracked leaf can be opened
1338        let proof2 = partial_mmr.open(2).unwrap();
1339        assert!(proof2.is_some());
1340        assert_eq!(proof2.unwrap().leaf(), leaf2);
1341
1342        // verify get() returns correct values
1343        assert_eq!(partial_mmr.get(0), Some(leaf0));
1344        assert_eq!(partial_mmr.get(1), None);
1345        assert_eq!(partial_mmr.get(2), Some(leaf2));
1346
1347        // verify leaves() iterator returns only tracked leaves
1348        let tracked: Vec<_> = partial_mmr.leaves().collect();
1349        assert_eq!(tracked, vec![(0, leaf0), (2, leaf2)]);
1350    }
1351
1352    #[test]
1353    fn test_partial_mmr_track_dangling_leaf() {
1354        // Single-leaf MMR: forest = 1, leaf 0 is a peak with an empty path.
1355        let mut mmr = Mmr::default();
1356        mmr.add(int_to_node(0)).unwrap();
1357        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1358
1359        let leaf0 = mmr.get(0).unwrap();
1360        // depth-0 MerklePath
1361        let proof0 = mmr.open(0).unwrap();
1362
1363        // Track the dangling leaf via `track` using the empty path.
1364        partial_mmr.track(0, leaf0, proof0.path().merkle_path()).unwrap();
1365
1366        // It should now be tracked and open to the same proof as the full MMR.
1367        assert!(partial_mmr.is_tracked(0));
1368        assert_eq!(partial_mmr.open(0).unwrap().unwrap(), proof0);
1369    }
1370
1371    #[test]
1372    fn test_partial_mmr_track_rejects_position_path_tree_mismatches() {
1373        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1374        let path_for_large_tree = mmr.open(0).unwrap();
1375        let path_for_middle_tree = mmr.open(4).unwrap();
1376
1377        // Position 0 belongs to the depth-2 tree, so using the depth-1 path would underflow when
1378        // translating the global position to the tree-relative path index.
1379        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1380        let result =
1381            partial_mmr.track(0, mmr.get(0).unwrap(), path_for_middle_tree.path().merkle_path());
1382        assert!(matches!(result, Err(MmrError::PositionNotFound(0))));
1383        assert!(!partial_mmr.is_tracked(0));
1384
1385        // Position 4 belongs to the depth-1 tree. The reverse mismatch must be rejected too,
1386        // rather than relying on the computed root to happen not to match a peak.
1387        let result =
1388            partial_mmr.track(4, mmr.get(4).unwrap(), path_for_large_tree.path().merkle_path());
1389        assert!(matches!(result, Err(MmrError::PositionNotFound(4))));
1390        assert!(!partial_mmr.is_tracked(4));
1391    }
1392
1393    #[test]
1394    fn test_partial_mmr_track_rejects_position_outside_forest() {
1395        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1396        let proof = mmr.open(0).unwrap();
1397        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1398
1399        let result = partial_mmr.track(LEAVES.len(), int_to_node(7), proof.path().merkle_path());
1400
1401        assert!(matches!(result, Err(MmrError::PositionNotFound(7))));
1402        assert!(!partial_mmr.is_tracked(LEAVES.len()));
1403    }
1404
1405    #[test]
1406    fn test_partial_mmr_track_preserves_unknown_peak_error() {
1407        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1408        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1409        let path = MerklePath::new(vec![Word::empty(); 3]);
1410
1411        let result = partial_mmr.track(0, mmr.get(0).unwrap(), &path);
1412
1413        assert!(matches!(result, Err(MmrError::UnknownPeak(3))));
1414        assert!(!partial_mmr.is_tracked(0));
1415    }
1416
1417    #[test]
1418    fn test_partial_mmr_track_valid_proofs_round_trip_across_all_peaks() {
1419        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1420
1421        for leaf_pos in 0..LEAVES.len() {
1422            let leaf = mmr.get(leaf_pos).unwrap();
1423            let proof = mmr.open(leaf_pos).unwrap();
1424            let mut partial_mmr: PartialMmr = mmr.peaks().into();
1425
1426            partial_mmr.track(leaf_pos, leaf, proof.path().merkle_path()).unwrap();
1427
1428            assert_eq!(partial_mmr.open(leaf_pos).unwrap(), Some(proof));
1429        }
1430    }
1431
1432    #[test]
1433    fn test_from_parts_validation() {
1434        use alloc::collections::BTreeMap;
1435
1436        use super::InOrderIndex;
1437
1438        // Build a valid MMR with 7 leaves
1439        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1440        let peaks = mmr.peaks();
1441
1442        // Valid case: empty nodes and empty tracked_leaves
1443        let result = PartialMmr::from_parts(peaks.clone(), BTreeMap::new(), BTreeSet::new());
1444        assert!(result.is_ok());
1445
1446        // Invalid case: tracked leaf position out of bounds
1447        let mut out_of_bounds = BTreeSet::new();
1448        out_of_bounds.insert(100);
1449        let result = PartialMmr::from_parts(peaks.clone(), BTreeMap::new(), out_of_bounds);
1450        assert!(result.is_err());
1451
1452        // Invalid case: tracked leaf has no value in nodes
1453        let mut tracked_no_value = BTreeSet::new();
1454        tracked_no_value.insert(0);
1455        let result = PartialMmr::from_parts(peaks.clone(), BTreeMap::new(), tracked_no_value);
1456        assert!(result.is_err());
1457
1458        // Valid case: tracked leaf with its complete authentication path in nodes
1459        let tracked_pos = 0;
1460        let mut complete_partial = PartialMmr::from_peaks(peaks.clone());
1461        complete_partial
1462            .track(
1463                tracked_pos,
1464                mmr.get(tracked_pos).unwrap(),
1465                mmr.open(tracked_pos).unwrap().path().merkle_path(),
1466            )
1467            .unwrap();
1468        let mut tracked_valid = BTreeSet::new();
1469        tracked_valid.insert(tracked_pos);
1470        let result = PartialMmr::from_parts(peaks.clone(), complete_partial.nodes, tracked_valid);
1471        assert!(result.is_ok());
1472
1473        // Invalid case: node index out of bounds (leaf index)
1474        let mut invalid_nodes = BTreeMap::new();
1475        let invalid_idx = InOrderIndex::from_leaf_pos(100); // way out of bounds
1476        invalid_nodes.insert(invalid_idx, int_to_node(0));
1477        let result = PartialMmr::from_parts(peaks.clone(), invalid_nodes, BTreeSet::new());
1478        assert!(result.is_err());
1479
1480        // Invalid case: index 0 (which is never valid for InOrderIndex). Deserialization used to
1481        // be the only way to construct it and is now rejected at the source, so the invalid value
1482        // can no longer reach `from_parts` at all.
1483        assert!(InOrderIndex::read_from_bytes(&0usize.to_bytes()).is_err());
1484
1485        // Invalid case: large even index (internal node) beyond forest bounds
1486        let mut nodes_with_large_even = BTreeMap::new();
1487        let large_even_idx = InOrderIndex::read_from_bytes(&1000usize.to_bytes()).unwrap();
1488        nodes_with_large_even.insert(large_even_idx, int_to_node(0));
1489        let result = PartialMmr::from_parts(peaks.clone(), nodes_with_large_even, BTreeSet::new());
1490        assert!(result.is_err());
1491
1492        // Invalid case: separator index between trees
1493        // For 7 leaves (0b111 = 4+2+1), index 8 is a separator between the first tree (1-7)
1494        // and the second tree (9-11). Similarly, index 12 is a separator between the second
1495        // tree and the third tree (13).
1496        let mut nodes_with_separator = BTreeMap::new();
1497        let separator_idx = InOrderIndex::read_from_bytes(&8usize.to_bytes()).unwrap();
1498        nodes_with_separator.insert(separator_idx, int_to_node(0));
1499        let result = PartialMmr::from_parts(peaks.clone(), nodes_with_separator, BTreeSet::new());
1500        assert!(result.is_err(), "separator index 8 should be rejected");
1501
1502        let mut nodes_with_separator_12 = BTreeMap::new();
1503        let separator_idx_12 = InOrderIndex::read_from_bytes(&12usize.to_bytes()).unwrap();
1504        nodes_with_separator_12.insert(separator_idx_12, int_to_node(0));
1505        let result = PartialMmr::from_parts(peaks, nodes_with_separator_12, BTreeSet::new());
1506        assert!(result.is_err(), "separator index 12 should be rejected");
1507
1508        // Invalid case: nodes with empty forest
1509        let empty_peaks = MmrPeaks::new(Forest::empty(), vec![]).unwrap();
1510        let mut nodes_with_empty_forest = BTreeMap::new();
1511        nodes_with_empty_forest.insert(InOrderIndex::from_leaf_pos(0), int_to_node(0));
1512        let result = PartialMmr::from_parts(empty_peaks, nodes_with_empty_forest, BTreeSet::new());
1513        assert!(result.is_err());
1514    }
1515
1516    #[test]
1517    fn test_from_parts_rejects_missing_ancestor_sibling() {
1518        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1519        let peaks = mmr.peaks();
1520        let tracked_pos = 0;
1521        let mut partial_mmr = PartialMmr::from_peaks(peaks.clone());
1522        partial_mmr
1523            .track(
1524                tracked_pos,
1525                mmr.get(tracked_pos).unwrap(),
1526                mmr.open(tracked_pos).unwrap().path().merkle_path(),
1527            )
1528            .unwrap();
1529
1530        let missing_sibling = InOrderIndex::from_leaf_pos(tracked_pos).parent().sibling();
1531        assert!(partial_mmr.nodes.remove(&missing_sibling).is_some());
1532
1533        let result = PartialMmr::from_parts(peaks, partial_mmr.nodes, partial_mmr.tracked_leaves);
1534        assert!(matches!(result, Err(MmrError::InconsistentPartialMmr(_))));
1535    }
1536
1537    #[test]
1538    fn test_from_parts_validation_deserialization() {
1539        // Build an MMR with 7 leaves
1540        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1541        let partial_mmr = PartialMmr::from_peaks(mmr.peaks());
1542
1543        // Valid serialization/deserialization
1544        let bytes = partial_mmr.to_bytes();
1545        let decoded = PartialMmr::read_from_bytes(&bytes);
1546        assert!(decoded.is_ok());
1547
1548        // Test that deserialization rejects bad data:
1549        // We'll construct invalid bytes that would create an invalid PartialMmr
1550
1551        // Create a PartialMmr with a valid node, serialize it, then manually corrupt the node index
1552        let mut partial_with_node = PartialMmr::from_peaks(mmr.peaks());
1553        let node = mmr.get(1).unwrap();
1554        let proof = mmr.open(1).unwrap();
1555        partial_with_node.track(1, node, proof.path().merkle_path()).unwrap();
1556
1557        // Serialize and verify it deserializes correctly first
1558        let valid_bytes = partial_with_node.to_bytes();
1559        let valid_decoded = PartialMmr::read_from_bytes(&valid_bytes);
1560        assert!(valid_decoded.is_ok());
1561
1562        // Now create malformed data with index 0 via manual byte construction
1563        // This tests that deserialization properly validates inputs
1564        let mut bad_bytes = Vec::new();
1565        // forest (7 leaves)
1566        bad_bytes.extend_from_slice(&7usize.to_bytes());
1567        // peaks (3 peaks for forest 0b111)
1568        bad_bytes.extend_from_slice(&3usize.to_bytes()); // vec length
1569        for i in 0..3 {
1570            bad_bytes.extend_from_slice(&int_to_node(i as u64).to_bytes());
1571        }
1572        // nodes: 1 entry with index 0
1573        bad_bytes.extend_from_slice(&1usize.to_bytes()); // BTreeMap length
1574        bad_bytes.extend_from_slice(&0usize.to_bytes()); // invalid index 0
1575        bad_bytes.extend_from_slice(&int_to_node(0).to_bytes()); // value
1576        bad_bytes.push(PartialMmr::TRACKED_LEAVES_MARKER);
1577        // tracked_leaves: empty vec
1578        bad_bytes.extend_from_slice(&0usize.to_bytes());
1579
1580        let result = PartialMmr::read_from_bytes(&bad_bytes);
1581        assert!(result.is_err());
1582    }
1583
1584    #[test]
1585    fn test_deserialization_rejects_missing_ancestor_sibling() {
1586        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1587        let tracked_pos = 0;
1588        let mut partial_mmr = PartialMmr::from_peaks(mmr.peaks());
1589        partial_mmr
1590            .track(
1591                tracked_pos,
1592                mmr.get(tracked_pos).unwrap(),
1593                mmr.open(tracked_pos).unwrap().path().merkle_path(),
1594            )
1595            .unwrap();
1596
1597        let missing_sibling = InOrderIndex::from_leaf_pos(tracked_pos).parent().sibling();
1598        assert!(partial_mmr.nodes.remove(&missing_sibling).is_some());
1599
1600        let result = PartialMmr::read_from_bytes(&partial_mmr.to_bytes());
1601        assert!(matches!(result, Err(DeserializationError::InvalidValue(_))));
1602    }
1603
1604    #[test]
1605    fn test_from_parts_unchecked() {
1606        use alloc::collections::BTreeMap;
1607
1608        // Build a valid MMR
1609        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1610        let peaks = mmr.peaks();
1611
1612        // from_parts_unchecked should not validate and always succeed
1613        let partial =
1614            PartialMmr::from_parts_unchecked(peaks.clone(), BTreeMap::new(), BTreeSet::new());
1615        assert_eq!(partial.forest(), peaks.forest());
1616
1617        // Even invalid combinations should work (no validation)
1618        let mut invalid_tracked = BTreeSet::new();
1619        invalid_tracked.insert(999);
1620        let partial = PartialMmr::from_parts_unchecked(peaks, BTreeMap::new(), invalid_tracked);
1621        assert!(partial.tracked_leaves.contains(&999));
1622    }
1623}