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