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