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 tracked_leaves: BTreeSet<usize> = tracked.into_iter().collect();
770
771        // Construct MmrPeaks to validate forest/peaks consistency
772        let peaks = MmrPeaks::new(forest, peaks_vec).map_err(|e| {
773            DeserializationError::InvalidValue(format!("invalid partial mmr peaks: {e}"))
774        })?;
775
776        // Use validating constructor
777        Self::from_parts(peaks, nodes, tracked_leaves)
778            .map_err(|e| DeserializationError::InvalidValue(format!("invalid partial mmr: {e}")))
779    }
780}
781
782// TESTS
783// ================================================================================================
784
785#[cfg(test)]
786mod tests {
787    use alloc::{
788        collections::{BTreeMap, BTreeSet},
789        vec::Vec,
790    };
791
792    use super::{MmrPeaks, PartialMmr};
793    use crate::{
794        Word,
795        merkle::{
796            NodeIndex, int_to_node,
797            mmr::{InOrderIndex, Mmr, forest::Forest},
798            store::MerkleStore,
799        },
800        utils::{ByteWriter, Deserializable, DeserializationError, Serializable},
801    };
802
803    const LEAVES: [Word; 7] = [
804        int_to_node(0),
805        int_to_node(1),
806        int_to_node(2),
807        int_to_node(3),
808        int_to_node(4),
809        int_to_node(5),
810        int_to_node(6),
811    ];
812
813    #[test]
814    fn test_partial_mmr_apply_delta() {
815        // build an MMR with 10 nodes (2 peaks) and a partial MMR based on it
816        let mut mmr = Mmr::default();
817        (0..10).for_each(|i| mmr.add(int_to_node(i)).unwrap());
818        let mut partial_mmr: PartialMmr = mmr.peaks().into();
819
820        // add authentication path for position 1 and 8
821        {
822            let node = mmr.get(1).unwrap();
823            let proof = mmr.open(1).unwrap();
824            partial_mmr.track(1, node, proof.path().merkle_path()).unwrap();
825        }
826
827        {
828            let node = mmr.get(8).unwrap();
829            let proof = mmr.open(8).unwrap();
830            partial_mmr.track(8, node, proof.path().merkle_path()).unwrap();
831        }
832
833        // add 2 more nodes into the MMR and validate apply_delta()
834        (10..12).for_each(|i| mmr.add(int_to_node(i)).unwrap());
835        validate_apply_delta(&mmr, &mut partial_mmr);
836
837        // add 1 more node to the MMR, validate apply_delta() and start tracking the node
838        mmr.add(int_to_node(12)).unwrap();
839        validate_apply_delta(&mmr, &mut partial_mmr);
840        {
841            let node = mmr.get(12).unwrap();
842            let proof = mmr.open(12).unwrap();
843            partial_mmr.track(12, node, proof.path().merkle_path()).unwrap();
844            // Position 12 is the last leaf (dangling) and should now be tracked
845            assert!(partial_mmr.is_tracked(12));
846        }
847
848        // by this point we are tracking authentication paths for positions: 1, 8, and 12
849
850        // add 3 more nodes to the MMR (collapses to 1 peak) and validate apply_delta()
851        (13..16).for_each(|i| mmr.add(int_to_node(i)).unwrap());
852        validate_apply_delta(&mmr, &mut partial_mmr);
853    }
854
855    fn validate_apply_delta(mmr: &Mmr, partial: &mut PartialMmr) {
856        // Get tracked leaf positions
857        let tracked_positions: Vec<_> = partial.tracked_leaves.iter().copied().collect();
858        let nodes_before = partial.nodes.clone();
859
860        // compute and apply delta
861        let delta = mmr.get_delta(partial.forest(), mmr.forest()).unwrap();
862        let nodes_delta = partial.apply(delta).unwrap();
863
864        // new peaks were computed correctly
865        assert_eq!(mmr.peaks(), partial.peaks());
866
867        let mut expected_nodes = nodes_before;
868        for (key, value) in nodes_delta {
869            // nodes should not be duplicated
870            assert!(expected_nodes.insert(key, value).is_none());
871        }
872
873        // new nodes should be a combination of original nodes and delta
874        assert_eq!(expected_nodes, partial.nodes);
875
876        // make sure tracked leaves open to the same proofs as in the underlying MMR
877        for pos in tracked_positions {
878            let proof1 = partial.open(pos).unwrap().unwrap();
879            let proof2 = mmr.open(pos).unwrap();
880            assert_eq!(proof1, proof2);
881        }
882    }
883
884    #[test]
885    fn test_partial_mmr_inner_nodes_iterator() {
886        // build the MMR
887        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
888        let first_peak = mmr.peaks().peaks()[0];
889
890        // -- test single tree ----------------------------
891
892        // get path and node for position 1
893        let node1 = mmr.get(1).unwrap();
894        let proof1 = mmr.open(1).unwrap();
895
896        // create partial MMR and add authentication path to node at position 1
897        let mut partial_mmr: PartialMmr = mmr.peaks().into();
898        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
899
900        // empty iterator should have no nodes
901        assert_eq!(partial_mmr.inner_nodes([].iter().cloned()).next(), None);
902
903        // build Merkle store from authentication paths in partial MMR
904        let mut store: MerkleStore = MerkleStore::new();
905        store.extend(partial_mmr.inner_nodes([(1, node1)].iter().cloned()));
906
907        let index1 = NodeIndex::new(2, 1).unwrap();
908        let path1 = store.get_path(first_peak, index1).unwrap().path;
909
910        assert_eq!(path1, *proof1.path().merkle_path());
911
912        // -- test no duplicates --------------------------
913
914        // build the partial MMR
915        let mut partial_mmr: PartialMmr = mmr.peaks().into();
916
917        let node0 = mmr.get(0).unwrap();
918        let proof0 = mmr.open(0).unwrap();
919
920        let node2 = mmr.get(2).unwrap();
921        let proof2 = mmr.open(2).unwrap();
922
923        partial_mmr.track(0, node0, proof0.path().merkle_path()).unwrap();
924        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
925        partial_mmr.track(2, node2, proof2.path().merkle_path()).unwrap();
926
927        // make sure there are no duplicates
928        let leaves = [(0, node0), (1, node1), (2, node2)];
929        let mut nodes = BTreeSet::new();
930        for node in partial_mmr.inner_nodes(leaves.iter().cloned()) {
931            assert!(nodes.insert(node.value));
932        }
933
934        // and also that the store is still be built correctly
935        store.extend(partial_mmr.inner_nodes(leaves.iter().cloned()));
936
937        let index0 = NodeIndex::new(2, 0).unwrap();
938        let index1 = NodeIndex::new(2, 1).unwrap();
939        let index2 = NodeIndex::new(2, 2).unwrap();
940
941        let path0 = store.get_path(first_peak, index0).unwrap().path;
942        let path1 = store.get_path(first_peak, index1).unwrap().path;
943        let path2 = store.get_path(first_peak, index2).unwrap().path;
944
945        assert_eq!(path0, *proof0.path().merkle_path());
946        assert_eq!(path1, *proof1.path().merkle_path());
947        assert_eq!(path2, *proof2.path().merkle_path());
948
949        // -- test multiple trees -------------------------
950
951        // build the partial MMR
952        let mut partial_mmr: PartialMmr = mmr.peaks().into();
953
954        let node5 = mmr.get(5).unwrap();
955        let proof5 = mmr.open(5).unwrap();
956
957        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
958        partial_mmr.track(5, node5, proof5.path().merkle_path()).unwrap();
959
960        // build Merkle store from authentication paths in partial MMR
961        let mut store: MerkleStore = MerkleStore::new();
962        store.extend(partial_mmr.inner_nodes([(1, node1), (5, node5)].iter().cloned()));
963
964        let index1 = NodeIndex::new(2, 1).unwrap();
965        let index5 = NodeIndex::new(1, 1).unwrap();
966
967        let second_peak = mmr.peaks().peaks()[1];
968
969        let path1 = store.get_path(first_peak, index1).unwrap().path;
970        let path5 = store.get_path(second_peak, index5).unwrap().path;
971
972        assert_eq!(path1, *proof1.path().merkle_path());
973        assert_eq!(path5, *proof5.path().merkle_path());
974    }
975
976    #[test]
977    fn test_partial_mmr_add_without_track() {
978        let mut mmr = Mmr::default();
979        let empty_peaks = MmrPeaks::new(Forest::empty(), vec![]).unwrap();
980        let mut partial_mmr = PartialMmr::from_peaks(empty_peaks);
981
982        for el in (0..256).map(int_to_node) {
983            mmr.add(el).unwrap();
984            partial_mmr.add(el, false).unwrap();
985
986            assert_eq!(mmr.peaks(), partial_mmr.peaks());
987            assert_eq!(mmr.forest(), partial_mmr.forest());
988        }
989    }
990
991    #[test]
992    fn test_partial_mmr_add_with_track() {
993        let mut mmr = Mmr::default();
994        let empty_peaks = MmrPeaks::new(Forest::empty(), vec![]).unwrap();
995        let mut partial_mmr = PartialMmr::from_peaks(empty_peaks);
996
997        for i in 0..256 {
998            let el = int_to_node(i as u64);
999            mmr.add(el).unwrap();
1000            partial_mmr.add(el, true).unwrap();
1001
1002            assert_eq!(mmr.peaks(), partial_mmr.peaks());
1003            assert_eq!(mmr.forest(), partial_mmr.forest());
1004
1005            for pos in 0..i {
1006                let mmr_proof = mmr.open(pos).unwrap();
1007                let partialmmr_proof = partial_mmr.open(pos).unwrap().unwrap();
1008                assert_eq!(mmr_proof, partialmmr_proof);
1009            }
1010        }
1011    }
1012
1013    #[test]
1014    fn test_partial_mmr_add_existing_track() {
1015        let mut mmr = Mmr::try_from_iter((0..7).map(int_to_node)).unwrap();
1016
1017        // derive a partial Mmr from it which tracks authentication path to leaf 5
1018        let mut partial_mmr = PartialMmr::from_peaks(mmr.peaks());
1019        let path_to_5 = mmr.open(5).unwrap().path().merkle_path().clone();
1020        let leaf_at_5 = mmr.get(5).unwrap();
1021        partial_mmr.track(5, leaf_at_5, &path_to_5).unwrap();
1022
1023        // add a new leaf to both Mmr and partial Mmr
1024        let leaf_at_7 = int_to_node(7);
1025        mmr.add(leaf_at_7).unwrap();
1026        partial_mmr.add(leaf_at_7, false).unwrap();
1027
1028        // the openings should be the same
1029        assert_eq!(mmr.open(5).unwrap(), partial_mmr.open(5).unwrap().unwrap());
1030    }
1031
1032    #[test]
1033    fn test_partial_mmr_add_updates_tracked_dangling_leaf() {
1034        // Track a dangling leaf, then add a new untracked leaf.
1035        // The previously dangling leaf's proof should still work.
1036        let mut mmr = Mmr::default();
1037        let mut partial_mmr = PartialMmr::default();
1038
1039        // Add leaf 0 with tracking - it's a dangling leaf (forest=1)
1040        let leaf0 = int_to_node(0);
1041        mmr.add(leaf0).unwrap();
1042        partial_mmr.add(leaf0, true).unwrap();
1043
1044        // Both should produce the same proof (empty path, leaf is a peak)
1045        assert_eq!(mmr.open(0).unwrap(), partial_mmr.open(0).unwrap().unwrap());
1046
1047        // Add leaf 1 WITHOUT tracking - triggers merge, leaf 0 gets a sibling
1048        let leaf1 = int_to_node(1);
1049        mmr.add(leaf1).unwrap();
1050        partial_mmr.add(leaf1, false).unwrap();
1051
1052        // Leaf 0 should still be tracked with correct proof after merge
1053        assert!(partial_mmr.is_tracked(0));
1054        assert!(!partial_mmr.is_tracked(1));
1055        assert_eq!(mmr.open(0).unwrap(), partial_mmr.open(0).unwrap().unwrap());
1056    }
1057
1058    #[test]
1059    fn test_partial_mmr_serialization() {
1060        let mmr = Mmr::try_from_iter((0..7).map(int_to_node)).unwrap();
1061        let partial_mmr = PartialMmr::from_peaks(mmr.peaks());
1062
1063        let bytes = partial_mmr.to_bytes();
1064        let decoded = PartialMmr::read_from_bytes(&bytes).unwrap();
1065
1066        assert_eq!(partial_mmr, decoded);
1067    }
1068
1069    #[test]
1070    fn test_partial_mmr_deserialization_rejects_large_forest() {
1071        let mut bytes = (Forest::MAX_LEAVES + 1).to_bytes();
1072        bytes.extend_from_slice(&0usize.to_bytes()); // empty peaks vec
1073        bytes.extend_from_slice(&0usize.to_bytes()); // empty nodes map
1074        bytes.extend_from_slice(&0usize.to_bytes()); // empty tracked vec
1075
1076        let result = PartialMmr::read_from_bytes(&bytes);
1077        assert!(matches!(result, Err(DeserializationError::InvalidValue(_))));
1078    }
1079
1080    #[test]
1081    fn test_partial_mmr_untrack() {
1082        // build the MMR
1083        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1084
1085        // get path and node for position 1
1086        let node1 = mmr.get(1).unwrap();
1087        let proof1 = mmr.open(1).unwrap();
1088
1089        // get path and node for position 2
1090        let node2 = mmr.get(2).unwrap();
1091        let proof2 = mmr.open(2).unwrap();
1092
1093        // create partial MMR and add authentication path to nodes at position 1 and 2
1094        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1095        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
1096        partial_mmr.track(2, node2, proof2.path().merkle_path()).unwrap();
1097
1098        // untrack nodes at positions 1 and 2
1099        partial_mmr.untrack(1);
1100        partial_mmr.untrack(2);
1101
1102        // nodes should not longer be tracked
1103        assert!(!partial_mmr.is_tracked(1));
1104        assert!(!partial_mmr.is_tracked(2));
1105        assert_eq!(partial_mmr.nodes().count(), 0);
1106    }
1107
1108    #[test]
1109    fn test_partial_mmr_untrack_returns_removed_nodes() {
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        // create partial MMR
1118        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1119
1120        // add authentication path for position 1
1121        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
1122
1123        // collect nodes before untracking
1124        let nodes_before: BTreeSet<_> =
1125            partial_mmr.nodes().map(|(&idx, &word)| (idx, word)).collect();
1126
1127        // untrack and capture removed nodes
1128        let removed: BTreeSet<_> = partial_mmr.untrack(1).into_iter().collect();
1129
1130        // verify that all nodes that were in the partial MMR were returned
1131        assert_eq!(removed, nodes_before);
1132
1133        // verify that partial MMR is now empty
1134        assert!(!partial_mmr.is_tracked(1));
1135        assert_eq!(partial_mmr.nodes().count(), 0);
1136    }
1137
1138    #[test]
1139    fn test_partial_mmr_untrack_shared_nodes() {
1140        // build the MMR
1141        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1142
1143        // track two sibling leaves (positions 0 and 1)
1144        let node0 = mmr.get(0).unwrap();
1145        let proof0 = mmr.open(0).unwrap();
1146
1147        let node1 = mmr.get(1).unwrap();
1148        let proof1 = mmr.open(1).unwrap();
1149
1150        // create partial MMR
1151        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1152
1153        // add authentication paths for position 0 and 1
1154        partial_mmr.track(0, node0, proof0.path().merkle_path()).unwrap();
1155        partial_mmr.track(1, node1, proof1.path().merkle_path()).unwrap();
1156
1157        // There are 3 unique nodes stored in `nodes`:
1158        // - nodes[idx0] = leaf0 (tracked leaf value, also serves as auth sibling for leaf1)
1159        // - nodes[idx1] = leaf1 (tracked leaf value, also serves as auth sibling for leaf0)
1160        // - nodes[parent_sibling] = shared higher-level auth node
1161        //
1162        // Note: Each tracked leaf's value is stored at its own InOrderIndex so that `open()`
1163        // can return an MmrProof containing the leaf value. These values also double as the
1164        // authentication siblings for their neighboring leaves.
1165        assert_eq!(partial_mmr.nodes().count(), 3);
1166
1167        // untrack position 0:
1168        // Even though pos 0 is no longer tracked, we cannot remove any nodes because:
1169        // - leaf0's value (at idx0) is still needed as the auth sibling for leaf1's path
1170        // - leaf1's value (at idx1) is needed for open(1) to return MmrProof
1171        // - parent_sibling is still needed for leaf1's path
1172        let removed0 = partial_mmr.untrack(0);
1173        assert_eq!(removed0.len(), 0);
1174        assert_eq!(partial_mmr.nodes().count(), 3);
1175        assert!(partial_mmr.is_tracked(1));
1176        assert!(!partial_mmr.is_tracked(0));
1177
1178        // untrack position 1:
1179        // Now sibling (pos 0) is NOT tracked, so all nodes can be removed:
1180        // - leaf1's value at idx1 (no longer needed for open())
1181        // - leaf0's value at idx0 (no longer needed as auth sibling)
1182        // - parent_sibling (no longer needed for any path)
1183        let removed1 = partial_mmr.untrack(1);
1184        assert_eq!(removed1.len(), 3);
1185        assert_eq!(partial_mmr.nodes().count(), 0);
1186        assert!(!partial_mmr.is_tracked(1));
1187    }
1188
1189    #[test]
1190    fn test_partial_mmr_untrack_preserves_upper_siblings() {
1191        let mut mmr = Mmr::default();
1192        (0..8).for_each(|i| mmr.add(int_to_node(i)).unwrap());
1193
1194        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1195        for pos in [0, 2] {
1196            let node = mmr.get(pos).unwrap();
1197            let proof = mmr.open(pos).unwrap();
1198            partial_mmr.track(pos, node, proof.path().merkle_path()).unwrap();
1199        }
1200
1201        partial_mmr.untrack(0);
1202
1203        let proof_partial = partial_mmr.open(2).unwrap().unwrap();
1204        let proof_full = mmr.open(2).unwrap();
1205        assert_eq!(proof_partial, proof_full);
1206    }
1207
1208    #[test]
1209    fn test_partial_mmr_deserialize_missing_marker_fails() {
1210        let mut mmr = Mmr::default();
1211        (0..3).for_each(|i| mmr.add(int_to_node(i)).unwrap());
1212        let peaks = mmr.peaks();
1213
1214        let mut bytes = Vec::new();
1215        peaks.num_leaves().write_into(&mut bytes);
1216        peaks.peaks().to_vec().write_into(&mut bytes);
1217        BTreeMap::<InOrderIndex, Word>::new().write_into(&mut bytes);
1218        assert!(PartialMmr::read_from_bytes(&bytes).is_err());
1219    }
1220
1221    #[test]
1222    fn test_partial_mmr_deserialize_invalid_marker_fails() {
1223        let mut mmr = Mmr::default();
1224        (0..3).for_each(|i| mmr.add(int_to_node(i)).unwrap());
1225        let peaks = mmr.peaks();
1226
1227        let mut bytes = Vec::new();
1228        peaks.num_leaves().write_into(&mut bytes);
1229        peaks.peaks().to_vec().write_into(&mut bytes);
1230        BTreeMap::<InOrderIndex, Word>::new().write_into(&mut bytes);
1231        bytes.write_u8(0x7f);
1232        Vec::<usize>::new().write_into(&mut bytes);
1233
1234        assert!(PartialMmr::read_from_bytes(&bytes).is_err());
1235    }
1236
1237    #[test]
1238    fn test_partial_mmr_open_returns_proof_with_leaf() {
1239        // build the MMR
1240        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1241
1242        // get leaf and proof for position 1
1243        let leaf1 = mmr.get(1).unwrap();
1244        let mmr_proof = mmr.open(1).unwrap();
1245
1246        // create partial MMR and track position 1
1247        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1248        partial_mmr.track(1, leaf1, mmr_proof.path().merkle_path()).unwrap();
1249
1250        // open should return MmrProof with the correct leaf value
1251        let partial_proof = partial_mmr.open(1).unwrap().unwrap();
1252        assert_eq!(partial_proof.leaf(), leaf1);
1253        assert_eq!(partial_proof, mmr_proof);
1254
1255        // untrack and verify open returns None
1256        partial_mmr.untrack(1);
1257        assert!(partial_mmr.open(1).unwrap().is_none());
1258    }
1259
1260    #[test]
1261    fn test_partial_mmr_add_tracks_leaf() {
1262        // create empty partial MMR
1263        let mut partial_mmr = PartialMmr::default();
1264
1265        // add leaves, tracking some
1266        let leaf0 = int_to_node(0);
1267        let leaf1 = int_to_node(1);
1268        let leaf2 = int_to_node(2);
1269
1270        partial_mmr.add(leaf0, true).unwrap(); // track
1271        partial_mmr.add(leaf1, false).unwrap(); // don't track
1272        partial_mmr.add(leaf2, true).unwrap(); // track
1273
1274        // verify tracked leaves can be opened
1275        let proof0 = partial_mmr.open(0).unwrap();
1276        assert!(proof0.is_some());
1277        assert_eq!(proof0.unwrap().leaf(), leaf0);
1278
1279        // verify untracked leaf returns None
1280        let proof1 = partial_mmr.open(1).unwrap();
1281        assert!(proof1.is_none());
1282
1283        // verify tracked leaf can be opened
1284        let proof2 = partial_mmr.open(2).unwrap();
1285        assert!(proof2.is_some());
1286        assert_eq!(proof2.unwrap().leaf(), leaf2);
1287
1288        // verify get() returns correct values
1289        assert_eq!(partial_mmr.get(0), Some(leaf0));
1290        assert_eq!(partial_mmr.get(1), None);
1291        assert_eq!(partial_mmr.get(2), Some(leaf2));
1292
1293        // verify leaves() iterator returns only tracked leaves
1294        let tracked: Vec<_> = partial_mmr.leaves().collect();
1295        assert_eq!(tracked, vec![(0, leaf0), (2, leaf2)]);
1296    }
1297
1298    #[test]
1299    fn test_partial_mmr_track_dangling_leaf() {
1300        // Single-leaf MMR: forest = 1, leaf 0 is a peak with an empty path.
1301        let mut mmr = Mmr::default();
1302        mmr.add(int_to_node(0)).unwrap();
1303        let mut partial_mmr: PartialMmr = mmr.peaks().into();
1304
1305        let leaf0 = mmr.get(0).unwrap();
1306        // depth-0 MerklePath
1307        let proof0 = mmr.open(0).unwrap();
1308
1309        // Track the dangling leaf via `track` using the empty path.
1310        partial_mmr.track(0, leaf0, proof0.path().merkle_path()).unwrap();
1311
1312        // It should now be tracked and open to the same proof as the full MMR.
1313        assert!(partial_mmr.is_tracked(0));
1314        assert_eq!(partial_mmr.open(0).unwrap().unwrap(), proof0);
1315    }
1316
1317    #[test]
1318    fn test_from_parts_validation() {
1319        use alloc::collections::BTreeMap;
1320
1321        use super::InOrderIndex;
1322
1323        // Build a valid MMR with 7 leaves
1324        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1325        let peaks = mmr.peaks();
1326
1327        // Valid case: empty nodes and empty tracked_leaves
1328        let result = PartialMmr::from_parts(peaks.clone(), BTreeMap::new(), BTreeSet::new());
1329        assert!(result.is_ok());
1330
1331        // Invalid case: tracked leaf position out of bounds
1332        let mut out_of_bounds = BTreeSet::new();
1333        out_of_bounds.insert(100);
1334        let result = PartialMmr::from_parts(peaks.clone(), BTreeMap::new(), out_of_bounds);
1335        assert!(result.is_err());
1336
1337        // Invalid case: tracked leaf has no value in nodes
1338        let mut tracked_no_value = BTreeSet::new();
1339        tracked_no_value.insert(0);
1340        let result = PartialMmr::from_parts(peaks.clone(), BTreeMap::new(), tracked_no_value);
1341        assert!(result.is_err());
1342
1343        // Valid case: tracked leaf with its value in nodes
1344        let mut nodes_with_leaf = BTreeMap::new();
1345        let leaf_idx = InOrderIndex::from_leaf_pos(0);
1346        nodes_with_leaf.insert(leaf_idx, int_to_node(0));
1347        let mut tracked_valid = BTreeSet::new();
1348        tracked_valid.insert(0);
1349        let result = PartialMmr::from_parts(peaks.clone(), nodes_with_leaf, tracked_valid);
1350        assert!(result.is_ok());
1351
1352        // Invalid case: node index out of bounds (leaf index)
1353        let mut invalid_nodes = BTreeMap::new();
1354        let invalid_idx = InOrderIndex::from_leaf_pos(100); // way out of bounds
1355        invalid_nodes.insert(invalid_idx, int_to_node(0));
1356        let result = PartialMmr::from_parts(peaks.clone(), invalid_nodes, BTreeSet::new());
1357        assert!(result.is_err());
1358
1359        // Invalid case: index 0 (which is never valid for InOrderIndex)
1360        let mut nodes_with_zero = BTreeMap::new();
1361        // Create an InOrderIndex with value 0 via deserialization
1362        let zero_idx = InOrderIndex::read_from_bytes(&0usize.to_bytes()).unwrap();
1363        nodes_with_zero.insert(zero_idx, int_to_node(0));
1364        let result = PartialMmr::from_parts(peaks.clone(), nodes_with_zero, BTreeSet::new());
1365        assert!(result.is_err());
1366
1367        // Invalid case: large even index (internal node) beyond forest bounds
1368        let mut nodes_with_large_even = BTreeMap::new();
1369        let large_even_idx = InOrderIndex::read_from_bytes(&1000usize.to_bytes()).unwrap();
1370        nodes_with_large_even.insert(large_even_idx, int_to_node(0));
1371        let result = PartialMmr::from_parts(peaks.clone(), nodes_with_large_even, BTreeSet::new());
1372        assert!(result.is_err());
1373
1374        // Invalid case: separator index between trees
1375        // For 7 leaves (0b111 = 4+2+1), index 8 is a separator between the first tree (1-7)
1376        // and the second tree (9-11). Similarly, index 12 is a separator between the second
1377        // tree and the third tree (13).
1378        let mut nodes_with_separator = BTreeMap::new();
1379        let separator_idx = InOrderIndex::read_from_bytes(&8usize.to_bytes()).unwrap();
1380        nodes_with_separator.insert(separator_idx, int_to_node(0));
1381        let result = PartialMmr::from_parts(peaks.clone(), nodes_with_separator, BTreeSet::new());
1382        assert!(result.is_err(), "separator index 8 should be rejected");
1383
1384        let mut nodes_with_separator_12 = BTreeMap::new();
1385        let separator_idx_12 = InOrderIndex::read_from_bytes(&12usize.to_bytes()).unwrap();
1386        nodes_with_separator_12.insert(separator_idx_12, int_to_node(0));
1387        let result = PartialMmr::from_parts(peaks, nodes_with_separator_12, BTreeSet::new());
1388        assert!(result.is_err(), "separator index 12 should be rejected");
1389
1390        // Invalid case: nodes with empty forest
1391        let empty_peaks = MmrPeaks::new(Forest::empty(), vec![]).unwrap();
1392        let mut nodes_with_empty_forest = BTreeMap::new();
1393        nodes_with_empty_forest.insert(InOrderIndex::from_leaf_pos(0), int_to_node(0));
1394        let result = PartialMmr::from_parts(empty_peaks, nodes_with_empty_forest, BTreeSet::new());
1395        assert!(result.is_err());
1396    }
1397
1398    #[test]
1399    fn test_from_parts_validation_deserialization() {
1400        // Build an MMR with 7 leaves
1401        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1402        let partial_mmr = PartialMmr::from_peaks(mmr.peaks());
1403
1404        // Valid serialization/deserialization
1405        let bytes = partial_mmr.to_bytes();
1406        let decoded = PartialMmr::read_from_bytes(&bytes);
1407        assert!(decoded.is_ok());
1408
1409        // Test that deserialization rejects bad data:
1410        // We'll construct invalid bytes that would create an invalid PartialMmr
1411
1412        // Create a PartialMmr with a valid node, serialize it, then manually corrupt the node index
1413        let mut partial_with_node = PartialMmr::from_peaks(mmr.peaks());
1414        let node = mmr.get(1).unwrap();
1415        let proof = mmr.open(1).unwrap();
1416        partial_with_node.track(1, node, proof.path().merkle_path()).unwrap();
1417
1418        // Serialize and verify it deserializes correctly first
1419        let valid_bytes = partial_with_node.to_bytes();
1420        let valid_decoded = PartialMmr::read_from_bytes(&valid_bytes);
1421        assert!(valid_decoded.is_ok());
1422
1423        // Now create malformed data with index 0 via manual byte construction
1424        // This tests that deserialization properly validates inputs
1425        let mut bad_bytes = Vec::new();
1426        // forest (7 leaves)
1427        bad_bytes.extend_from_slice(&7usize.to_bytes());
1428        // peaks (3 peaks for forest 0b111)
1429        bad_bytes.extend_from_slice(&3usize.to_bytes()); // vec length
1430        for i in 0..3 {
1431            bad_bytes.extend_from_slice(&int_to_node(i as u64).to_bytes());
1432        }
1433        // nodes: 1 entry with index 0
1434        bad_bytes.extend_from_slice(&1usize.to_bytes()); // BTreeMap length
1435        bad_bytes.extend_from_slice(&0usize.to_bytes()); // invalid index 0
1436        bad_bytes.extend_from_slice(&int_to_node(0).to_bytes()); // value
1437        // tracked_leaves: empty vec
1438        bad_bytes.extend_from_slice(&0usize.to_bytes());
1439
1440        let result = PartialMmr::read_from_bytes(&bad_bytes);
1441        assert!(result.is_err());
1442    }
1443
1444    #[test]
1445    fn test_from_parts_unchecked() {
1446        use alloc::collections::BTreeMap;
1447
1448        // Build a valid MMR
1449        let mmr = Mmr::try_from_iter(LEAVES.iter().copied()).unwrap();
1450        let peaks = mmr.peaks();
1451
1452        // from_parts_unchecked should not validate and always succeed
1453        let partial =
1454            PartialMmr::from_parts_unchecked(peaks.clone(), BTreeMap::new(), BTreeSet::new());
1455        assert_eq!(partial.forest(), peaks.forest());
1456
1457        // Even invalid combinations should work (no validation)
1458        let mut invalid_tracked = BTreeSet::new();
1459        invalid_tracked.insert(999);
1460        let partial = PartialMmr::from_parts_unchecked(peaks, BTreeMap::new(), invalid_tracked);
1461        assert!(partial.tracked_leaves.contains(&999));
1462    }
1463}