Skip to main content

commonware_storage/merkle/
proof.rs

1//! Defines the generic inclusion [Proof] structure for Merkle-family data structures.
2//!
3//! The [Proof] struct is parameterized by a [`Family`] marker and a [`Digest`] type. Each Merkle
4//! family (MMR, MMB, etc.) reuses the shared verification and reconstruction logic in this module,
5//! while retaining any family-specific proof helpers in its submodule.
6
7use crate::merkle::{hasher::Hasher, Bagging, Error, Family, Location, Position};
8use alloc::{
9    collections::{BTreeMap, BTreeSet},
10    vec,
11    vec::Vec,
12};
13use bytes::{Buf, BufMut};
14use commonware_codec::{varint::UInt, EncodeSize, ReadExt, ReadRangeExt, Write};
15use commonware_cryptography::Digest;
16use core::ops::Range;
17
18/// Errors that can occur when reconstructing a digest from a proof due to invalid input.
19#[derive(thiserror::Error, Debug)]
20pub enum ReconstructionError {
21    #[error("invalid proof")]
22    InvalidProof,
23    #[error("missing digests in proof")]
24    MissingDigests,
25    #[error("extra digests in proof")]
26    ExtraDigests,
27    #[error("start location is out of bounds")]
28    InvalidStartLoc,
29    #[error("end location is out of bounds")]
30    InvalidEndLoc,
31    #[error("missing elements")]
32    MissingElements,
33    #[error("invalid size")]
34    InvalidSize,
35}
36
37/// Contains the information necessary for proving the inclusion of an element, or some range of
38/// elements, in a Merkle-family data structure from its root digest.
39///
40/// For range proofs, the `digests` vector uses a fold-based layout:
41///
42/// 1. If there are folded peaks entirely before the proven range, the first digest is a single
43///    accumulator produced by folding those peaks: `fold(fold(..., peak0), peak1)`. If there are
44///    no such peaks, this entry is absent.
45///
46/// 2. The digests of any non-folded peaks entirely before the proven range, in peak iteration
47///    order.
48///
49/// 3. For `ForwardFold`, the digests of peaks entirely after the proven range, in peak iteration
50///    order. For `BackwardFold`, inactive after-peaks are still listed individually, while active
51///    after-peaks are collapsed into one optional suffix accumulator.
52///
53/// 4. The sibling digests needed to reconstruct each range-peak digest from the proven elements,
54///    in depth-first (forward consumption) order for each range peak.
55///
56/// Multi-proofs use a different, position-keyed layout: `digests` contains the sorted set of node
57/// digests required by the requested `inactive_peaks` and bagging policy. For `BackwardFold`, this
58/// may include active suffix peaks that a single range proof could collapse into a synthetic suffix
59/// accumulator.
60#[derive(Clone, Debug, Eq)]
61pub struct Proof<F: Family, D: Digest> {
62    /// The total number of leaves in the data structure. For MMR proofs, this is the number of
63    /// leaves in the MMR, though other authenticated data structures may override the meaning of
64    /// this field. For example, the authenticated [crate::AuthenticatedBitMap] stores the number
65    /// of bits in the bitmap within this field.
66    pub leaves: Location<F>,
67    /// The number of inactive peaks in the structure when this proof was generated.
68    pub inactive_peaks: usize,
69    /// The digests necessary for proving inclusion.
70    pub digests: Vec<D>,
71}
72
73impl<F: Family, D: Digest> PartialEq for Proof<F, D> {
74    fn eq(&self, other: &Self) -> bool {
75        self.leaves == other.leaves
76            && self.inactive_peaks == other.inactive_peaks
77            && self.digests == other.digests
78    }
79}
80
81impl<F: Family, D: Digest> EncodeSize for Proof<F, D> {
82    fn encode_size(&self) -> usize {
83        self.leaves.encode_size()
84            + UInt(self.inactive_peaks as u64).encode_size()
85            + self.digests.encode_size()
86    }
87}
88
89impl<F: Family, D: Digest> Write for Proof<F, D> {
90    fn write(&self, buf: &mut impl BufMut) {
91        self.leaves.write(buf);
92        UInt(self.inactive_peaks as u64).write(buf);
93        self.digests.write(buf);
94    }
95}
96
97impl<F: Family, D: Digest> commonware_codec::Read for Proof<F, D> {
98    /// The maximum number of digests in the proof.
99    type Cfg = usize;
100
101    fn read_cfg(
102        buf: &mut impl Buf,
103        max_digests: &Self::Cfg,
104    ) -> Result<Self, commonware_codec::Error> {
105        let leaves = Location::<F>::read(buf)?;
106        let inactive_peaks = usize::try_from(UInt::<u64>::read(buf)?.0).map_err(|_| {
107            commonware_codec::Error::Invalid("Proof", "inactive_peaks exceeds usize")
108        })?;
109        let digests = Vec::<D>::read_range(buf, ..=*max_digests)?;
110        Ok(Self {
111            leaves,
112            inactive_peaks,
113            digests,
114        })
115    }
116}
117
118impl<F: Family, D: Digest> Default for Proof<F, D> {
119    /// Create an empty proof. The empty proof will verify only against the root digest of an empty
120    /// (`leaves == 0`) data structure.
121    fn default() -> Self {
122        Self {
123            leaves: Location::new(0),
124            inactive_peaks: 0,
125            digests: vec![],
126        }
127    }
128}
129
130impl<F: Family, D: Digest> Proof<F, D> {
131    /// Return true if this proof proves that `element` appears at location `loc` within the
132    /// structure with root digest `root`, using the bagging carried by `hasher`.
133    pub fn verify_element_inclusion<H>(
134        &self,
135        hasher: &H,
136        element: &[u8],
137        loc: Location<F>,
138        root: &D,
139    ) -> bool
140    where
141        H: Hasher<F, Digest = D>,
142    {
143        self.verify_range_inclusion(hasher, &[element], loc, root)
144    }
145
146    /// Return true if this proof verifies against the supplied root, using the bagging carried by
147    /// `hasher`.
148    pub fn verify_range_inclusion<H, E>(
149        &self,
150        hasher: &H,
151        elements: &[E],
152        start_loc: Location<F>,
153        root: &D,
154    ) -> bool
155    where
156        H: Hasher<F, Digest = D>,
157        E: AsRef<[u8]>,
158    {
159        match self.reconstruct_root_inner(hasher, elements, start_loc, None) {
160            Ok(reconstructed_root) => *root == reconstructed_root,
161            Err(_error) => {
162                #[cfg(feature = "std")]
163                tracing::debug!(error = ?_error, "invalid proof input");
164                false
165            }
166        }
167    }
168
169    /// Returns true if this proof's `inactive_peaks` field matches the canonical value derived
170    /// from `size` and `inactivity_floor`.
171    pub fn matches_canonical_inactive_peaks(
172        &self,
173        size: Position<F>,
174        inactivity_floor: Location<F>,
175    ) -> bool {
176        self.inactive_peaks == F::inactive_peaks(size, inactivity_floor)
177    }
178
179    /// Verify a position-keyed multi-proof using the bagging carried by `hasher`.
180    ///
181    /// Multi-proofs keep every witness tied to a concrete node position, so this path may include
182    /// extra backward-bagged suffix peaks that range proofs can collapse into a suffix accumulator.
183    pub fn verify_multi_inclusion<H, E>(
184        &self,
185        hasher: &H,
186        elements: &[(E, Location<F>)],
187        root: &D,
188    ) -> bool
189    where
190        H: Hasher<F, Digest = D>,
191        E: AsRef<[u8]>,
192    {
193        let bagging = hasher.root_bagging();
194        // Empty proof is valid only for an empty tree with no extra digest data.
195        if elements.is_empty() {
196            return self.digests.is_empty()
197                && self.leaves == Location::new(0)
198                && self.inactive_peaks == 0
199                && *root
200                    == hasher
201                        .root(Location::new(0), 0, core::iter::empty())
202                        .expect("zero inactive peaks is always valid");
203        }
204
205        // Collect all required positions with deduplication, and blueprints per element.
206        let mut node_positions = BTreeSet::new();
207        let mut blueprints = BTreeMap::new();
208
209        for (_, loc) in elements {
210            if !loc.is_valid_index() {
211                return false;
212            }
213            // `loc` is valid so it won't overflow from +1
214            let Ok(bp) = Blueprint::new(self.leaves, self.inactive_peaks, bagging, *loc..*loc + 1)
215            else {
216                return false;
217            };
218            node_positions.extend(bp.fold_prefix.iter().map(|s| s.pos));
219            node_positions.extend(&bp.fetch_nodes);
220            if let Some(suffix_peaks) = bp.suffix_peaks() {
221                node_positions.extend(suffix_peaks);
222            }
223            blueprints.insert(*loc, bp);
224        }
225
226        // Verify we have the exact number of digests needed
227        if node_positions.len() != self.digests.len() {
228            return false;
229        }
230
231        // Build position to digest mapping once
232        let node_digests: BTreeMap<Position<F>, D> = node_positions
233            .iter()
234            .zip(self.digests.iter())
235            .map(|(&pos, digest)| (pos, *digest))
236            .collect();
237
238        // Verify each element by constructing its sub-proof in fold-based format
239        for (element, loc) in elements {
240            let bp = &blueprints[loc];
241
242            let suffix_count = usize::from(bp.suffix_peaks().is_some());
243            let mut digests = Vec::with_capacity(
244                if bp.fold_prefix.is_empty() { 0 } else { 1 } + bp.fetch_nodes.len() + suffix_count,
245            );
246            if let Some((first_sub, rest)) = bp.fold_prefix.split_first() {
247                let first = *node_digests
248                    .get(&first_sub.pos)
249                    .expect("must exist by construction");
250                let acc = rest.iter().fold(first, |acc, sub| {
251                    let d = node_digests
252                        .get(&sub.pos)
253                        .expect("must exist by construction");
254                    hasher.fold(&acc, d)
255                });
256                digests.push(acc);
257            }
258            let prefix_active_count = bp.prefix_active_count();
259            let after_count = bp.after_peaks_count();
260            for &pos in &bp.fetch_nodes[..prefix_active_count + after_count] {
261                let d = node_digests.get(&pos).expect("must exist by construction");
262                digests.push(*d);
263            }
264            if let Some(suffix_peaks) = bp.suffix_peaks() {
265                let (last_pos, rest_pos) = suffix_peaks
266                    .split_last()
267                    .expect("suffix_peaks is non-empty when returned");
268                let mut acc = *node_digests
269                    .get(last_pos)
270                    .expect("must exist by construction");
271                for pos in rest_pos.iter().rev() {
272                    let d = node_digests.get(pos).expect("must exist by construction");
273                    acc = hasher.fold(d, &acc);
274                }
275                digests.push(acc);
276            }
277            for &pos in &bp.fetch_nodes[prefix_active_count + after_count..] {
278                let d = node_digests.get(&pos).expect("must exist by construction");
279                digests.push(*d);
280            }
281            let proof = Self {
282                leaves: self.leaves,
283                inactive_peaks: self.inactive_peaks,
284                digests,
285            };
286
287            match proof.reconstruct_root_inner(hasher, &[element.as_ref()], *loc, None) {
288                Ok(reconstructed_root) if &reconstructed_root == root => {}
289                Ok(_) | Err(_) => return false,
290            }
291        }
292
293        true
294    }
295
296    /// Reconstruct the root digest from this proof and the given consecutive elements using
297    /// the bagging carried by `hasher`, or return a `ReconstructionError` if the input data is
298    /// invalid.
299    pub fn reconstruct_root<H, E>(
300        &self,
301        hasher: &H,
302        elements: &[E],
303        start_loc: Location<F>,
304    ) -> Result<D, ReconstructionError>
305    where
306        H: Hasher<F, Digest = D>,
307        E: AsRef<[u8]>,
308    {
309        self.reconstruct_root_inner(hasher, elements, start_loc, None)
310    }
311
312    /// Verify this proof against `root` and extract all authenticated digests.
313    ///
314    /// Reconstructs the root from the proof and provided elements and returns every
315    /// `(position, digest)` pair required by that reconstruction, including the proof's own
316    /// digests. Returns [`Error::InvalidProof`] if the input data is malformed and
317    /// [`Error::RootMismatch`] if the reconstructed root does not match `root`.
318    pub fn verify_range_inclusion_and_extract_digests<H, E>(
319        &self,
320        hasher: &H,
321        elements: &[E],
322        start_loc: Location<F>,
323        root: &D,
324    ) -> Result<Vec<(Position<F>, D)>, Error<F>>
325    where
326        H: Hasher<F, Digest = D>,
327        E: AsRef<[u8]>,
328    {
329        let mut collected_digests = Vec::new();
330        let Ok(reconstructed_root) =
331            self.reconstruct_root_inner(hasher, elements, start_loc, Some(&mut collected_digests))
332        else {
333            return Err(Error::InvalidProof);
334        };
335
336        if reconstructed_root != *root {
337            return Err(Error::RootMismatch);
338        }
339
340        Ok(collected_digests)
341    }
342
343    /// Verify this proof and the pinned nodes against `root`.
344    ///
345    /// The proof's `inactive_peaks` field commits to the split boundary; peak bagging is selected
346    /// by `hasher`.
347    ///
348    /// The `pinned_nodes` are the peak digests of the sub-structure at `start_loc`, in the order
349    /// returned by `Family::nodes_to_pin`. The proof authenticates the prefix `[0, start_loc)` via:
350    ///
351    /// - fold-prefix peaks of the larger tree, and
352    /// - sibling subtrees inside the first range peak that lie wholly before `start_loc`.
353    ///
354    /// When the larger tree has merged smaller subtrees into a bigger parent, the pins sit below
355    /// these authenticated subtrees. The verifier hashes pairs of pins up to each authenticated
356    /// subtree's root and compares against the proof.
357    ///
358    /// For example, in MMB at `leaves=5, start_loc=4`, the proof describes `[0, 4)` as one
359    /// height-2 subtree `p7`, while the pins cover the same leaves as two height-1 subtrees
360    /// `p2`, `p5`:
361    ///
362    /// ```text
363    ///     proof authenticates:         pins contain:
364    ///
365    ///             p7
366    ///           /    \
367    ///          p2    p5                p2         p5
368    ///         / \    / \              / \        / \
369    ///        L0 L1  L2 L3            L0 L1      L2 L3
370    /// ```
371    ///
372    /// The verifier walks down from `p7` via `F::children`, pulls the pins for `p2` and `p5`, and
373    /// hashes them back up (`node_digest(p7, pin[p2], pin[p5])`) to compare against the `p7`
374    /// digest the proof authenticates.
375    ///
376    /// Returns `true` only if the proof reconstructs to `root` and every pinned node digest is
377    /// accounted for. When `start_loc` is 0, `pinned_nodes` must be empty.
378    pub fn verify_proof_and_pinned_nodes<H, E>(
379        &self,
380        hasher: &H,
381        elements: &[E],
382        start_loc: Location<F>,
383        pinned_nodes: &[D],
384        root: &D,
385    ) -> bool
386    where
387        H: Hasher<F, Digest = D>,
388        E: AsRef<[u8]>,
389    {
390        self.try_verify_proof_and_pinned_nodes(hasher, elements, start_loc, pinned_nodes, root)
391            .is_some()
392    }
393
394    /// Fallible implementation of [`verify_proof_and_pinned_nodes`](Self::verify_proof_and_pinned_nodes).
395    ///
396    /// Returns `Some(())` if the proof and pins are consistent with `root`, `None` otherwise. The
397    /// `Option` return lets the body use `?` on each fallible step; the public wrapper converts to
398    /// `bool` via `.is_some()`.
399    fn try_verify_proof_and_pinned_nodes<H, E>(
400        &self,
401        hasher: &H,
402        elements: &[E],
403        start_loc: Location<F>,
404        pinned_nodes: &[D],
405        root: &D,
406    ) -> Option<()>
407    where
408        H: Hasher<F, Digest = D>,
409        E: AsRef<[u8]>,
410    {
411        let bagging = hasher.root_bagging();
412        let collected = self
413            .verify_range_inclusion_and_extract_digests(hasher, elements, start_loc, root)
414            .ok()?;
415
416        if elements.is_empty() {
417            return pinned_nodes.is_empty().then_some(());
418        }
419
420        if !start_loc.is_valid() || start_loc > self.leaves {
421            return None;
422        }
423
424        let pinned_positions: Vec<_> = F::nodes_to_pin(start_loc).collect();
425        if pinned_positions.len() != pinned_nodes.len() {
426            return None;
427        }
428
429        let end_loc = start_loc.checked_add(elements.len() as u64)?;
430        let bp = Blueprint::new(
431            self.leaves,
432            self.inactive_peaks,
433            bagging,
434            start_loc..end_loc,
435        )
436        .ok()?;
437
438        let mut pinned_map: BTreeMap<Position<F>, D> = pinned_positions
439            .into_iter()
440            .zip(pinned_nodes.iter().copied())
441            .collect();
442
443        // Fold-prefix peaks of the larger tree may have merged several pins together. Reconstruct
444        // each peak's digest by hashing the pins beneath it up to the peak, then compare the
445        // folded accumulator against the one the proof carries.
446        if let Some((first_sub, rest)) = bp.fold_prefix.split_first() {
447            let &expected = self.digests.first()?;
448            let mut acc = first_sub.reconstruct_from_pins(hasher, &mut pinned_map)?;
449            for sub in rest {
450                let d = sub.reconstruct_from_pins(hasher, &mut pinned_map)?;
451                acc = hasher.fold(&acc, &d);
452            }
453            if acc != expected {
454                return None;
455            }
456        }
457
458        let extracted: BTreeMap<Position<F>, D> = collected.into_iter().collect();
459
460        // Verify prefix active peaks that were not folded.
461        for sub in &bp.prefix_active_peaks {
462            let &expected = extracted.get(&sub.pos)?;
463            let d = sub.reconstruct_from_pins(hasher, &mut pinned_map)?;
464            if d != expected {
465                return None;
466            }
467        }
468
469        // Sibling subtrees inside the first range peak that lie wholly before `start_loc` are
470        // authenticated directly by the proof (their digests appear in `extracted`). Rebuild each
471        // from the pins and compare.
472        for sibling in bp.prefix_siblings() {
473            let &expected = extracted.get(&sibling.pos)?;
474            let d = sibling.reconstruct_from_pins(hasher, &mut pinned_map)?;
475            if d != expected {
476                return None;
477            }
478        }
479
480        // Every pin must have been consumed by one of the two reconstructions above.
481        pinned_map.is_empty().then_some(())
482    }
483
484    /// Reconstruct a root from range-proof digests and optionally collect authenticated nodes.
485    ///
486    /// Reads the bagging policy from `hasher`. When `collected` is supplied, the verifier records
487    /// the intermediate node digests it authenticates while reconstructing the range.
488    pub(crate) fn reconstruct_root_inner<H, E>(
489        &self,
490        hasher: &H,
491        elements: &[E],
492        start_loc: Location<F>,
493        collected: Option<&mut Vec<(Position<F>, D)>>,
494    ) -> Result<D, ReconstructionError>
495    where
496        H: Hasher<F, Digest = D>,
497        E: AsRef<[u8]>,
498    {
499        let bagging = hasher.root_bagging();
500        let mut collected = collected;
501        if elements.is_empty() {
502            if start_loc == 0 {
503                if self.inactive_peaks != 0 {
504                    return Err(ReconstructionError::InvalidProof);
505                }
506                if self.leaves != Location::new(0) {
507                    return Err(ReconstructionError::MissingElements);
508                }
509                return if self.digests.is_empty() {
510                    Ok(hasher.digest(&self.leaves.to_be_bytes()))
511                } else {
512                    Err(ReconstructionError::ExtraDigests)
513                };
514            }
515            return Err(ReconstructionError::MissingElements);
516        }
517        if !start_loc.is_valid_index() {
518            return Err(ReconstructionError::InvalidStartLoc);
519        }
520        let end_loc = start_loc
521            .checked_add(elements.len() as u64)
522            .ok_or(ReconstructionError::InvalidEndLoc)?;
523        if end_loc > self.leaves {
524            return Err(ReconstructionError::InvalidEndLoc);
525        }
526        let range = start_loc..end_loc;
527
528        let bp = Blueprint::new(self.leaves, self.inactive_peaks, bagging, range)
529            .map_err(|_| ReconstructionError::InvalidSize)?;
530
531        let proof_digests = bp.split_proof_digests(&self.digests)?;
532
533        // Collect all peak digests to provide to hasher.root().
534        let mut peak_digests = Vec::new();
535        if let Some(&digest) = proof_digests.fold_prefix {
536            peak_digests.push(digest);
537        }
538        for (sub, &digest) in bp
539            .prefix_active_peaks
540            .iter()
541            .zip(proof_digests.prefix_active_peaks)
542        {
543            peak_digests.push(digest);
544            if let Some(ref mut cd) = collected {
545                cd.push((sub.pos, digest));
546            }
547        }
548
549        let mut sibling_cursor = 0usize;
550        let mut elements_iter = elements.iter();
551        for peak in &bp.range_peaks {
552            let peak_digest = peak.reconstruct_digest(
553                hasher,
554                &bp.range,
555                &mut elements_iter,
556                proof_digests.siblings,
557                &mut sibling_cursor,
558                collected.as_deref_mut(),
559            )?;
560            if let Some(ref mut cd) = collected {
561                cd.push((peak.pos, peak_digest));
562            }
563            peak_digests.push(peak_digest);
564        }
565
566        for (&after_peak_pos, &digest) in bp.after_peaks.iter().zip(proof_digests.after_peaks) {
567            if let Some(ref mut cd) = collected {
568                cd.push((after_peak_pos, digest));
569            }
570            peak_digests.push(digest);
571        }
572        if let Some(&digest) = proof_digests.suffix_acc {
573            peak_digests.push(digest);
574        }
575
576        // Verify all elements were consumed.
577        if elements_iter.next().is_some() {
578            return Err(ReconstructionError::ExtraDigests);
579        }
580
581        // Verify all siblings were consumed.
582        if sibling_cursor != proof_digests.siblings.len() {
583            return Err(ReconstructionError::ExtraDigests);
584        }
585
586        hasher
587            .root_with_folded_peaks(
588                self.leaves,
589                bp.inactive_peaks_after_prefix_fold(self.inactive_peaks),
590                self.inactive_peaks,
591                peak_digests.iter(),
592            )
593            .ok_or(ReconstructionError::InvalidProof)
594    }
595}
596
597/// A perfect binary subtree within a peak, identified by its root position, height,
598/// and the first leaf location it covers.
599#[derive(Copy, Clone)]
600pub(crate) struct Subtree<F: Family> {
601    /// Position of the subtree root node.
602    pub pos: Position<F>,
603    pub height: u32,
604    pub leaf_start: Location<F>,
605}
606
607impl<F: Family> Subtree<F> {
608    fn leaf_end(&self) -> Location<F> {
609        self.leaf_start + (1u64 << self.height)
610    }
611
612    /// True if this subtree's leaves lie wholly before `range.start`.
613    fn is_before(&self, range: &Range<Location<F>>) -> bool {
614        self.leaf_end() <= range.start
615    }
616
617    /// True if this subtree's leaves lie wholly outside `range` (either before it or after it).
618    fn is_outside(&self, range: &Range<Location<F>>) -> bool {
619        self.is_before(range) || self.leaf_start >= range.end
620    }
621
622    /// True if this subtree's leaves lie wholly inside `range`.
623    fn is_inside(&self, range: &Range<Location<F>>) -> bool {
624        self.leaf_start >= range.start && self.leaf_end() <= range.end
625    }
626
627    fn children(&self) -> (Self, Self) {
628        let (left_pos, right_pos) = F::children(self.pos, self.height);
629        let child_height = self.height - 1;
630        let mid = self.leaf_start + (1u64 << child_height);
631        (
632            Self {
633                pos: left_pos,
634                height: child_height,
635                leaf_start: self.leaf_start,
636            },
637            Self {
638                pos: right_pos,
639                height: child_height,
640                leaf_start: mid,
641            },
642        )
643    }
644
645    /// Collect sibling positions needed to reconstruct this subtree digest from a range of
646    /// elements, in left-first DFS order.
647    ///
648    /// Emits outside subtrees and skips fully covered subtrees.
649    fn collect_siblings(&self, range: &Range<Location<F>>, out: &mut Vec<Position<F>>) {
650        if self.is_outside(range) {
651            out.push(self.pos);
652            return;
653        }
654
655        if self.is_inside(range) {
656            return;
657        }
658
659        if self.height > 0 {
660            let (left, right) = self.children();
661            left.collect_siblings(range, out);
662            right.collect_siblings(range, out);
663        }
664    }
665
666    /// Collect sibling subtrees that lie wholly before the proven range, in the same
667    /// left-first DFS order as [`collect_siblings`](Self::collect_siblings).
668    ///
669    /// Only `range.start` is consulted because the `range.end` side cannot affect prefix siblings.
670    fn collect_prefix_siblings(&self, range: &Range<Location<F>>, out: &mut Vec<Self>) {
671        if self.is_before(range) {
672            out.push(*self);
673            return;
674        }
675
676        if self.leaf_start >= range.start {
677            return;
678        }
679
680        if self.height > 0 {
681            let (left, right) = self.children();
682            left.collect_prefix_siblings(range, out);
683            right.collect_prefix_siblings(range, out);
684        }
685    }
686
687    /// Reconstruct this subtree's digest from a set of finer-grained pinned positions, consuming
688    /// each pin as it is used.
689    ///
690    /// Walks down via [`Self::children`] until each recursion hits a pin, then hashes back up with
691    /// [`Hasher::node_digest`] for position-keyed domain separation. Returns `None` if any required
692    /// pin is missing.
693    ///
694    /// On failure, `pinned_map` may have been partially consumed. Callers are expected to return
695    /// immediately without inspecting it further.
696    fn reconstruct_from_pins<D, H>(
697        &self,
698        hasher: &H,
699        pinned_map: &mut BTreeMap<Position<F>, D>,
700    ) -> Option<D>
701    where
702        D: Digest,
703        H: Hasher<F, Digest = D>,
704    {
705        if let Some(d) = pinned_map.remove(&self.pos) {
706            return Some(d);
707        }
708        if self.height == 0 {
709            return None;
710        }
711        let (left, right) = self.children();
712        let left_d = left.reconstruct_from_pins(hasher, pinned_map)?;
713        let right_d = right.reconstruct_from_pins(hasher, pinned_map)?;
714        Some(hasher.node_digest(self.pos, &left_d, &right_d))
715    }
716
717    /// Reconstruct the digest of this subtree from a range of elements and sibling digests,
718    /// consuming both in left-first DFS order.
719    ///
720    /// At each node:
721    /// - If the subtree is entirely outside the range: consume a sibling digest.
722    /// - If it's a leaf in the range: hash the next element.
723    /// - Otherwise: recurse into children via [`Family::children`] and compute the node digest.
724    ///
725    /// If `collected` is `Some`, every child `(position, digest)` pair encountered during
726    /// reconstruction is appended to the vector.
727    fn reconstruct_digest<D, H, E>(
728        &self,
729        hasher: &H,
730        range: &Range<Location<F>>,
731        elements: &mut E,
732        siblings: &[D],
733        cursor: &mut usize,
734        mut collected: Option<&mut Vec<(Position<F>, D)>>,
735    ) -> Result<D, ReconstructionError>
736    where
737        D: Digest,
738        H: Hasher<F, Digest = D>,
739        E: Iterator<Item: AsRef<[u8]>>,
740    {
741        // Entirely outside the range: consume a sibling digest.
742        if self.is_outside(range) {
743            let Some(digest) = siblings.get(*cursor).copied() else {
744                return Err(ReconstructionError::MissingDigests);
745            };
746            *cursor += 1;
747            return Ok(digest);
748        }
749
750        // Leaf in range: hash the next element.
751        if self.height == 0 {
752            let elem = elements
753                .next()
754                .ok_or(ReconstructionError::MissingElements)?;
755            return Ok(hasher.leaf_digest(self.pos, elem.as_ref()));
756        }
757
758        // Recurse into children.
759        let (left, right) = self.children();
760        let left_d = left.reconstruct_digest(
761            hasher,
762            range,
763            elements,
764            siblings,
765            cursor,
766            collected.as_deref_mut(),
767        )?;
768        let right_d = right.reconstruct_digest(
769            hasher,
770            range,
771            elements,
772            siblings,
773            cursor,
774            collected.as_deref_mut(),
775        )?;
776
777        if let Some(ref mut cd) = collected {
778            cd.push((left.pos, left_d));
779            cd.push((right.pos, right_d));
780        }
781
782        Ok(hasher.node_digest(self.pos, &left_d, &right_d))
783    }
784}
785
786/// Return the peaks of a tree of `leaves` that overlap `range`, validating both the range and the
787/// declared `inactive_peaks` boundary.
788///
789/// The returned subtrees are bagging-independent: `Blueprint::new`'s prefix/suffix accumulator
790/// layout depends on bagging, but the per-peak partition of the proven range does not.
791///
792/// Blueprint for a range proof, separating fold-prefix peaks from nodes that must be fetched.
793pub(crate) struct Blueprint<F: Family> {
794    /// Total number of leaves in the structure this blueprint was built for.
795    leaves: Location<F>,
796    /// The location range this blueprint was built for.
797    range: Range<Location<F>>,
798    /// Peaks that precede the proven range (to be folded into a single accumulator).
799    pub(crate) fold_prefix: Vec<Subtree<F>>,
800    prefix_active_peaks: Vec<Subtree<F>>,
801    /// Peak positions entirely after the proven range.
802    after_peaks: Vec<Position<F>>,
803    /// Active peak positions after the proven range that are collapsed into one suffix accumulator.
804    suffix_peaks: Vec<Position<F>>,
805    /// The peaks that overlap the proven range.
806    range_peaks: Vec<Subtree<F>>,
807    /// Node positions to include in the proof: after-peaks followed by DFS siblings.
808    pub(crate) fetch_nodes: Vec<Position<F>>,
809}
810
811pub(crate) struct ProofDigestLayout<'a, D> {
812    pub(crate) fold_prefix: Option<&'a D>,
813    pub(crate) prefix_active_peaks: &'a [D],
814    pub(crate) after_peaks: &'a [D],
815    pub(crate) suffix_acc: Option<&'a D>,
816    pub(crate) siblings: &'a [D],
817}
818
819impl<F: Family> Blueprint<F> {
820    /// Build a range-proof blueprint for a caller-supplied bagging policy.
821    ///
822    /// Forward bagging folds peaks before the range into one prefix accumulator. Backward bagging
823    /// also collapses active peaks after the range into one suffix accumulator while leaving inactive
824    /// after-peaks position-keyed.
825    pub(crate) fn new(
826        leaves: Location<F>,
827        inactive_peaks: usize,
828        bagging: Bagging,
829        range: Range<Location<F>>,
830    ) -> Result<Self, super::Error<F>> {
831        if range.is_empty() {
832            return Err(super::Error::Empty);
833        }
834        let end_minus_one = range
835            .end
836            .checked_sub(1)
837            .expect("can't underflow because range is non-empty");
838        if end_minus_one >= leaves {
839            return Err(super::Error::RangeOutOfBounds(range.end));
840        }
841
842        let size = Position::try_from(leaves)?;
843
844        let mut fold_prefix = Vec::new();
845        let mut prefix_active_peaks = Vec::new();
846        let mut after_peaks = Vec::new();
847        let mut suffix_peaks = Vec::new();
848        let mut range_peaks = Vec::new();
849        let mut leaf_cursor = Location::new(0);
850
851        let mut peak_index = 0;
852        for (peak_pos, height) in F::peaks(size) {
853            let leaf_start = leaf_cursor;
854            let leaf_end = leaf_start + (1u64 << height);
855
856            if leaf_end <= range.start {
857                if peak_index < inactive_peaks || bagging == Bagging::ForwardFold {
858                    fold_prefix.push(Subtree {
859                        pos: peak_pos,
860                        height,
861                        leaf_start,
862                    });
863                } else {
864                    prefix_active_peaks.push(Subtree {
865                        pos: peak_pos,
866                        height,
867                        leaf_start,
868                    });
869                }
870            } else if leaf_start >= range.end {
871                if bagging == Bagging::BackwardFold && peak_index >= inactive_peaks {
872                    suffix_peaks.push(peak_pos);
873                } else {
874                    after_peaks.push(peak_pos);
875                }
876            } else {
877                range_peaks.push(Subtree {
878                    pos: peak_pos,
879                    height,
880                    leaf_start,
881                });
882            }
883            leaf_cursor = leaf_end;
884            peak_index += 1;
885        }
886        // `inactive_peaks` is a global boundary over the tree's peaks, not just the peaks before
887        // this range. It may point into or beyond the proven range; reconstruction then folds the
888        // same global boundary and the final root comparison rejects non-canonical proofs.
889        if inactive_peaks > peak_index {
890            return Err(super::Error::InvalidProof);
891        }
892
893        assert!(
894            !range_peaks.is_empty(),
895            "at least one peak must contain range elements"
896        );
897
898        let mut fetch_nodes: Vec<_> = prefix_active_peaks.iter().map(|s| s.pos).collect();
899        fetch_nodes.extend_from_slice(&after_peaks);
900        for peak in &range_peaks {
901            peak.collect_siblings(&range, &mut fetch_nodes);
902        }
903
904        Ok(Self {
905            leaves,
906            range,
907            fold_prefix,
908            prefix_active_peaks,
909            after_peaks,
910            suffix_peaks,
911            range_peaks,
912            fetch_nodes,
913        })
914    }
915
916    /// Sibling subtrees of the first range peak that lie wholly before `self.range.start`.
917    ///
918    /// Only the first range peak can contain such siblings; later range peaks are entirely at or
919    /// after `range.start` by this blueprint's classification.
920    pub(crate) fn prefix_siblings(&self) -> Vec<Subtree<F>> {
921        let mut out = Vec::new();
922        if let Some(peak) = self.range_peaks.first() {
923            peak.collect_prefix_siblings(&self.range, &mut out);
924        }
925        out
926    }
927
928    /// Return the number of active prefix peak digests stored before after-peak digests.
929    pub(crate) const fn prefix_active_count(&self) -> usize {
930        self.prefix_active_peaks.len()
931    }
932
933    /// Return the number of non-collapsed after-peak digests in the proof layout.
934    pub(crate) const fn after_peaks_count(&self) -> usize {
935        self.after_peaks.len()
936    }
937
938    /// Return active after-peaks that are collapsed into a backward-folded suffix accumulator.
939    pub(crate) fn suffix_peaks(&self) -> Option<&[Position<F>]> {
940        (!self.suffix_peaks.is_empty()).then_some(&self.suffix_peaks)
941    }
942
943    /// Split a proof's digest vector according to this blueprint's range-proof layout.
944    pub(crate) fn split_proof_digests<'a, D>(
945        &self,
946        digests: &'a [D],
947    ) -> Result<ProofDigestLayout<'a, D>, ReconstructionError> {
948        let fold_count = usize::from(!self.fold_prefix.is_empty());
949        let suffix_count = usize::from(!self.suffix_peaks.is_empty());
950        let required = fold_count + self.fetch_nodes.len() + suffix_count;
951        if digests.len() < required {
952            return Err(ReconstructionError::MissingDigests);
953        }
954        if digests.len() > required {
955            return Err(ReconstructionError::ExtraDigests);
956        }
957
958        let prefix_start = fold_count;
959        let after_start = prefix_start + self.prefix_active_peaks.len();
960        let siblings_start = after_start + self.after_peaks.len();
961        let suffix_start = siblings_start;
962        let suffix_end = suffix_start + suffix_count;
963
964        Ok(ProofDigestLayout {
965            fold_prefix: (!self.fold_prefix.is_empty()).then(|| &digests[0]),
966            prefix_active_peaks: &digests[prefix_start..after_start],
967            after_peaks: &digests[after_start..siblings_start],
968            suffix_acc: (!self.suffix_peaks.is_empty()).then(|| &digests[suffix_start]),
969            siblings: &digests[suffix_end..],
970        })
971    }
972
973    /// Map the original `inactive_peaks` count to the count for the reconstructed peak list,
974    /// where `fold_prefix.len()` peaks have been collapsed into one leading accumulator entry.
975    ///
976    /// The accumulator counts as 1 inactive peak; any inactive peaks beyond `fold_prefix.len()`
977    /// remain unfolded after it. Under `ForwardFold` the accumulator may absorb active peaks too
978    /// (`fold_prefix.len() > inactive_peaks`); `saturating_sub` clamps and the result is 1.
979    const fn inactive_peaks_after_prefix_fold(&self, inactive_peaks: usize) -> usize {
980        if self.fold_prefix.is_empty() {
981            return inactive_peaks;
982        }
983        inactive_peaks.saturating_sub(self.fold_prefix.len()) + 1
984    }
985
986    /// Build a range proof from this blueprint and a node-fetching closure.
987    ///
988    /// The prover folds prefix peak digests into a single accumulator. The resulting proof
989    /// contains:
990    /// `[fold_acc? | prefix_active_peaks... | after_peaks... | suffix_acc? | siblings_dfs...]`.
991    ///
992    /// Returns an error via `element_pruned` if `get_node` returns `None` for any required
993    /// position.
994    pub(crate) fn build_proof<D, H, E>(
995        self,
996        hasher: &H,
997        inactive_peaks: usize,
998        get_node: impl Fn(Position<F>) -> Option<D>,
999        element_pruned: impl Fn(Position<F>) -> E,
1000    ) -> Result<Proof<F, D>, E>
1001    where
1002        D: Digest,
1003        H: Hasher<F, Digest = D>,
1004    {
1005        let mut digests = Vec::with_capacity(
1006            if self.fold_prefix.is_empty() { 0 } else { 1 }
1007                + self.fetch_nodes.len()
1008                + usize::from(!self.suffix_peaks.is_empty()),
1009        );
1010
1011        if let Some((first_sub, rest)) = self.fold_prefix.split_first() {
1012            let first = get_node(first_sub.pos).ok_or_else(|| element_pruned(first_sub.pos))?;
1013            let acc = rest.iter().try_fold(first, |acc, sub| {
1014                let d = get_node(sub.pos).ok_or_else(|| element_pruned(sub.pos))?;
1015                Ok(hasher.fold(&acc, &d))
1016            })?;
1017            digests.push(acc);
1018        }
1019
1020        for sub in &self.prefix_active_peaks {
1021            digests.push(get_node(sub.pos).ok_or_else(|| element_pruned(sub.pos))?);
1022        }
1023        for &pos in &self.after_peaks {
1024            digests.push(get_node(pos).ok_or_else(|| element_pruned(pos))?);
1025        }
1026        if let Some((last_pos, rest)) = self.suffix_peaks.split_last() {
1027            let last = get_node(*last_pos).ok_or_else(|| element_pruned(*last_pos))?;
1028            let acc = rest.iter().rev().try_fold(last, |acc, &pos| {
1029                let d = get_node(pos).ok_or_else(|| element_pruned(pos))?;
1030                Ok(hasher.fold(&d, &acc))
1031            })?;
1032            digests.push(acc);
1033        }
1034
1035        let sibling_start = self.prefix_active_peaks.len() + self.after_peaks.len();
1036        for &pos in &self.fetch_nodes[sibling_start..] {
1037            digests.push(get_node(pos).ok_or_else(|| element_pruned(pos))?);
1038        }
1039
1040        Ok(Proof {
1041            leaves: self.leaves,
1042            inactive_peaks,
1043            digests,
1044        })
1045    }
1046}
1047
1048/// The maximum number of digests in a proof per element being proven.
1049///
1050/// This accounts for the worst case proof size, in an MMR/MMB with 62 peaks. The
1051/// left-most leaf in such a tree requires 122 digests, for 61 path siblings
1052/// and 61 peak digests.
1053pub const MAX_PROOF_DIGESTS_PER_ELEMENT: usize = 122;
1054
1055/// Build a range proof from a node-fetching closure. The bagging policy is read from `hasher`.
1056/// This is the generic implementation shared by all Merkle families. The `element_pruned` closure
1057/// is called when `get_node` returns `None` for a required position.
1058pub(crate) fn build_range_proof<F, D, H, E>(
1059    hasher: &H,
1060    leaves: Location<F>,
1061    inactive_peaks: usize,
1062    range: Range<Location<F>>,
1063    get_node: impl Fn(Position<F>) -> Option<D>,
1064    element_pruned: impl Fn(Position<F>) -> E,
1065) -> Result<Proof<F, D>, E>
1066where
1067    F: Family,
1068    D: Digest,
1069    H: Hasher<F, Digest = D>,
1070    E: From<super::Error<F>>,
1071{
1072    Blueprint::new(leaves, inactive_peaks, hasher.root_bagging(), range)?.build_proof(
1073        hasher,
1074        inactive_peaks,
1075        get_node,
1076        element_pruned,
1077    )
1078}
1079
1080/// Returns the positions of the minimal set of nodes whose digests are required to prove the
1081/// inclusion of the elements at the specified `locations`, using the provided root bagging.
1082#[cfg(any(feature = "std", test))]
1083pub(crate) fn nodes_required_for_multi_proof<F: Family>(
1084    leaves: Location<F>,
1085    inactive_peaks: usize,
1086    bagging: Bagging,
1087    locations: &[Location<F>],
1088) -> Result<BTreeSet<Position<F>>, super::Error<F>> {
1089    if locations.is_empty() {
1090        return Err(super::Error::Empty);
1091    }
1092    locations.iter().try_fold(BTreeSet::new(), |mut acc, loc| {
1093        if !loc.is_valid_index() {
1094            return Err(super::Error::LocationOverflow(*loc));
1095        }
1096        let bp = Blueprint::new(leaves, inactive_peaks, bagging, *loc..*loc + 1)?;
1097        if let Some(suffix_peaks) = bp.suffix_peaks() {
1098            acc.extend(suffix_peaks);
1099        }
1100        acc.extend(bp.fold_prefix.into_iter().map(|s| s.pos));
1101        acc.extend(bp.fetch_nodes);
1102        Ok(acc)
1103    })
1104}
1105
1106#[cfg(feature = "arbitrary")]
1107impl<F: Family, D: Digest> arbitrary::Arbitrary<'_> for Proof<F, D>
1108where
1109    D: for<'a> arbitrary::Arbitrary<'a>,
1110{
1111    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
1112        Ok(Self {
1113            leaves: u.arbitrary()?,
1114            inactive_peaks: u.arbitrary()?,
1115            digests: u.arbitrary()?,
1116        })
1117    }
1118}
1119
1120#[cfg(test)]
1121mod tests {
1122    use super::*;
1123    use crate::merkle::{
1124        hasher::Standard,
1125        mem::Mem,
1126        mmb, mmr,
1127        proof::{nodes_required_for_multi_proof, Blueprint, Proof},
1128        Bagging::{BackwardFold, ForwardFold},
1129        Family, Location, LocationRangeExt as _,
1130    };
1131    use alloc::vec;
1132    use commonware_codec::{Decode, Encode, EncodeSize};
1133    use commonware_cryptography::{sha256, Sha256};
1134    use commonware_macros::test_traced;
1135
1136    type D = sha256::Digest;
1137    type H = Standard<Sha256>;
1138
1139    fn test_digest(v: u8) -> D {
1140        <Sha256 as commonware_cryptography::Hasher>::hash(&[v])
1141    }
1142
1143    /// Build an in-memory Merkle structure with `n` elements (element i = i.to_be_bytes()).
1144    fn build_raw<F: Family>(hasher: &H, n: u64) -> Mem<F, D> {
1145        let mut mem = Mem::new();
1146        let batch = {
1147            let mut batch = mem.new_batch();
1148            for i in 0..n {
1149                batch = batch.add(hasher, &i.to_be_bytes());
1150            }
1151            batch.merkleize(&mem, hasher)
1152        };
1153        mem.apply_batch(&batch).unwrap();
1154        mem
1155    }
1156
1157    fn build_inactive_prefix<F: Family>(hasher: &H, n: u64, inactive_peaks: usize) -> Mem<F, D> {
1158        let mut mem = Mem::new();
1159        let batch = {
1160            let mut batch = mem.new_batch();
1161            for i in 0..n {
1162                batch = batch.add(hasher, &i.to_be_bytes());
1163            }
1164            let batch = batch.merkleize(&mem, hasher);
1165            batch.root(&mem, hasher, inactive_peaks).unwrap();
1166            batch
1167        };
1168        mem.apply_batch(&batch).unwrap();
1169        mem
1170    }
1171
1172    fn plain_root<F: Family>(mem: &Mem<F, D>, hasher: &H) -> D {
1173        mem.root(hasher, 0).unwrap()
1174    }
1175
1176    fn split_root<F: Family>(mem: &Mem<F, D>, inactive_peaks: usize) -> D {
1177        let backward_hasher: H = Standard::new(BackwardFold);
1178        mem.root(&backward_hasher, inactive_peaks).unwrap()
1179    }
1180
1181    /// Hasher tuned for `bagging` so callers can mix forward/backward and full/split policies.
1182    fn hasher_for_bagging(bagging: Bagging) -> H {
1183        Standard::new(bagging)
1184    }
1185
1186    fn push_unique_shape(shapes: &mut Vec<(Bagging, usize)>, shape: (Bagging, usize)) {
1187        if !shapes.contains(&shape) {
1188            shapes.push(shape);
1189        }
1190    }
1191
1192    fn supported_root_shapes<F: Family>(leaves: Location<F>) -> Vec<(Bagging, usize)> {
1193        let peak_count = F::peaks(F::location_to_position(leaves)).count();
1194        let mut shapes = Vec::new();
1195
1196        push_unique_shape(&mut shapes, (Bagging::ForwardFold, 0));
1197        push_unique_shape(&mut shapes, (Bagging::BackwardFold, 0));
1198        for inactive_peaks in 0..=peak_count {
1199            push_unique_shape(&mut shapes, (Bagging::ForwardFold, inactive_peaks));
1200            push_unique_shape(&mut shapes, (Bagging::BackwardFold, inactive_peaks));
1201        }
1202
1203        shapes
1204    }
1205
1206    fn inactive_leaf_floor<F: Family>(leaves: Location<F>, inactive_peaks: usize) -> u64 {
1207        F::peaks(F::location_to_position(leaves))
1208            .take(inactive_peaks)
1209            .map(|(_, height)| 1u64 << height)
1210            .sum()
1211    }
1212
1213    fn active_start_for_shape<F: Family>(
1214        leaves: Location<F>,
1215        inactive_peaks: usize,
1216        width: u64,
1217    ) -> Location<F> {
1218        let start = inactive_leaf_floor::<F>(leaves, inactive_peaks);
1219        if start + width <= *leaves {
1220            return Location::new(start);
1221        }
1222        Location::new(*leaves - width)
1223    }
1224
1225    fn range_proofs_verify_for_supported_root_shapes<F: Family>() {
1226        let mem = build_raw::<F>(&H::new(ForwardFold), 123);
1227        let leaves = mem.leaves();
1228
1229        for (bagging, inactive_peaks) in supported_root_shapes::<F>(leaves) {
1230            let hasher = hasher_for_bagging(bagging);
1231            let range_start = active_start_for_shape::<F>(leaves, inactive_peaks, 3);
1232            let range = range_start..range_start + 3;
1233            let root = mem.root(&hasher, inactive_peaks).unwrap();
1234            let elements: Vec<_> = (*range.start..*range.end)
1235                .map(|i| i.to_be_bytes())
1236                .collect();
1237            let proof: Proof<F, D> = build_range_proof(
1238                &hasher,
1239                leaves,
1240                inactive_peaks,
1241                range.clone(),
1242                |pos| mem.get_node(pos),
1243                Error::ElementPruned,
1244            )
1245            .unwrap();
1246
1247            assert_eq!(proof.inactive_peaks, inactive_peaks);
1248            assert!(
1249                proof.verify_range_inclusion(&hasher, &elements, range.start, &root),
1250                "range proof should verify for ({bagging:?}, {inactive_peaks})",
1251            );
1252
1253            let mut tampered_boundary = proof.clone();
1254            tampered_boundary.inactive_peaks = if inactive_peaks == 0 { 1 } else { 0 };
1255            assert!(
1256                !tampered_boundary.verify_range_inclusion(&hasher, &elements, range.start, &root),
1257                "inactive_peaks mutation should fail for ({bagging:?}, {inactive_peaks})",
1258            );
1259
1260            if !proof.digests.is_empty() {
1261                let mut tampered_digest = proof.clone();
1262                tampered_digest.digests[0].0[0] ^= 1;
1263                assert!(
1264                    !tampered_digest
1265                        .verify_range_inclusion(&hasher, &elements, range.start, &root,),
1266                    "digest mutation should fail for ({bagging:?}, {inactive_peaks})",
1267                );
1268            }
1269        }
1270    }
1271
1272    fn multi_proofs_verify_for_supported_root_shapes<F: Family>() {
1273        let mem = build_raw::<F>(&H::new(ForwardFold), 123);
1274        let leaves = mem.leaves();
1275
1276        for (bagging, inactive_peaks) in supported_root_shapes::<F>(leaves) {
1277            let hasher = hasher_for_bagging(bagging);
1278            let first = active_start_for_shape::<F>(leaves, inactive_peaks, 12);
1279            let locations = [first, first + 5, first + 11];
1280            let nodes = nodes_required_for_multi_proof(leaves, inactive_peaks, bagging, &locations)
1281                .expect("test locations valid");
1282            let proof = Proof {
1283                leaves,
1284                inactive_peaks,
1285                digests: nodes
1286                    .into_iter()
1287                    .map(|pos| mem.get_node(pos).unwrap())
1288                    .collect(),
1289            };
1290            let root = mem.root(&hasher, inactive_peaks).unwrap();
1291            let elements: Vec<_> = locations
1292                .iter()
1293                .map(|loc| ((*loc).to_be_bytes(), *loc))
1294                .collect();
1295
1296            assert!(
1297                proof.verify_multi_inclusion(&hasher, &elements, &root),
1298                "multi-proof should verify for ({bagging:?}, {inactive_peaks})",
1299            );
1300
1301            let mut tampered_boundary = proof.clone();
1302            tampered_boundary.inactive_peaks = if inactive_peaks == 0 { 1 } else { 0 };
1303            assert!(
1304                !tampered_boundary.verify_multi_inclusion(&hasher, &elements, &root),
1305                "inactive_peaks mutation should fail for ({bagging:?}, {inactive_peaks})",
1306            );
1307
1308            if !proof.digests.is_empty() {
1309                let mut tampered_digest = proof.clone();
1310                tampered_digest.digests[0].0[0] ^= 1;
1311                assert!(
1312                    !tampered_digest.verify_multi_inclusion(&hasher, &elements, &root),
1313                    "digest mutation should fail for ({bagging:?}, {inactive_peaks})",
1314                );
1315            }
1316        }
1317    }
1318
1319    fn backward_fold_proof_optimization_inner(inactive_peaks: usize) {
1320        let hasher: H = Standard::new(BackwardFold);
1321        let mem = build_inactive_prefix::<mmb::Family>(&hasher, 123, inactive_peaks);
1322        let leaves = mem.leaves();
1323        let root = split_root(&mem, inactive_peaks);
1324
1325        let mut selected = None;
1326        for loc in 0..*leaves {
1327            let range = Location::new(loc)..Location::new(loc + 1);
1328            let optimized =
1329                Blueprint::new(leaves, inactive_peaks, Bagging::BackwardFold, range.clone())
1330                    .unwrap();
1331            if optimized.suffix_peaks.len() > 1 {
1332                selected = Some((range, optimized));
1333                break;
1334            }
1335        }
1336        let (range, optimized) = selected.expect("test tree should expose a multi-peak suffix");
1337
1338        let suffix_len = optimized.suffix_peaks.len();
1339        let position_keyed_len = usize::from(!optimized.fold_prefix.is_empty())
1340            + optimized.fetch_nodes.len()
1341            + suffix_len;
1342        let suffix_idx = usize::from(!optimized.fold_prefix.is_empty())
1343            + optimized.prefix_active_peaks.len()
1344            + optimized.after_peaks.len();
1345        let proof = optimized
1346            .build_proof(
1347                &hasher,
1348                inactive_peaks,
1349                |pos| mem.get_node(pos),
1350                Error::ElementPruned,
1351            )
1352            .unwrap();
1353
1354        assert_eq!(position_keyed_len - proof.digests.len(), suffix_len - 1);
1355        assert!(proof.verify_range_inclusion(
1356            &hasher,
1357            &[range.start.to_be_bytes()],
1358            range.start,
1359            &root,
1360        ));
1361
1362        let mut tampered = proof;
1363        tampered.digests[suffix_idx].0[0] ^= 1;
1364        assert!(!tampered.verify_range_inclusion(
1365            &hasher,
1366            &[range.start.to_be_bytes()],
1367            range.start,
1368            &root,
1369        ));
1370    }
1371
1372    #[test]
1373    fn full_backward_root_proves_like_split_zero() {
1374        let hasher: H = Standard::new(BackwardFold);
1375        let mem = build_raw::<mmb::Family>(&hasher, 123);
1376        let range = Location::new(2)..Location::new(3);
1377
1378        let generated: Result<Proof<mmb::Family, D>, Error<mmb::Family>> = build_range_proof(
1379            &hasher,
1380            mem.leaves(),
1381            0,
1382            range.clone(),
1383            |pos| mem.get_node(pos),
1384            Error::ElementPruned,
1385        );
1386        let generated = generated.unwrap();
1387
1388        let full_backward_root = mem.root(&hasher, 0).unwrap();
1389        assert!(generated.verify_range_inclusion(
1390            &hasher,
1391            &[range.start.to_be_bytes()],
1392            range.start,
1393            &full_backward_root,
1394        ));
1395
1396        let locations = &[Location::new(0), Location::new(5), Location::new(10)];
1397        let nodes =
1398            nodes_required_for_multi_proof(mem.leaves(), 0, Bagging::BackwardFold, locations)
1399                .expect("valid locations");
1400        let multi_proof = Proof {
1401            leaves: mem.leaves(),
1402            inactive_peaks: 0,
1403            digests: nodes
1404                .into_iter()
1405                .map(|pos| mem.get_node(pos).unwrap())
1406                .collect(),
1407        };
1408        assert!(multi_proof.verify_multi_inclusion(
1409            &hasher,
1410            &[
1411                (0u64.to_be_bytes(), Location::new(0)),
1412                (5u64.to_be_bytes(), Location::new(5)),
1413                (10u64.to_be_bytes(), Location::new(10)),
1414            ],
1415            &full_backward_root,
1416        ));
1417
1418        // A zero inactive boundary is byte-identical to the corresponding full root.
1419        let split_root_value = mem.root(&hasher, 0).unwrap();
1420        assert_eq!(full_backward_root, split_root_value);
1421        let split_proof: Result<Proof<mmb::Family, D>, Error<mmb::Family>> = build_range_proof(
1422            &hasher,
1423            mem.leaves(),
1424            0,
1425            range.clone(),
1426            |pos| mem.get_node(pos),
1427            Error::ElementPruned,
1428        );
1429        let split_proof = split_proof.unwrap();
1430        assert!(split_proof.verify_range_inclusion(
1431            &hasher,
1432            &[range.start.to_be_bytes()],
1433            range.start,
1434            &split_root_value,
1435        ));
1436    }
1437
1438    fn empty_proof<F: Family>() {
1439        // Test that an empty proof authenticates an empty structure.
1440        let hasher = H::new(ForwardFold);
1441        let mem = Mem::<F, D>::new();
1442        let root = plain_root(&mem, &hasher);
1443        let proof: Proof<F, D> = Proof::default();
1444        let empty_range: &[D] = &[];
1445        let empty_multi: &[(D, Location<F>)] = &[];
1446        assert!(proof.verify_range_inclusion(&hasher, empty_range, Location::new(0), &root));
1447        assert!(proof.verify_multi_inclusion(&hasher, empty_multi, &root));
1448
1449        let mut inactive_proof = proof.clone();
1450        inactive_proof.inactive_peaks = 1;
1451        assert!(!inactive_proof.verify_range_inclusion(
1452            &hasher,
1453            empty_range,
1454            Location::new(0),
1455            &root,
1456        ));
1457        assert!(!inactive_proof.verify_multi_inclusion(&hasher, empty_multi, &root));
1458        assert!(matches!(
1459            inactive_proof.reconstruct_root(&hasher, empty_range, Location::new(0)),
1460            Err(ReconstructionError::InvalidProof)
1461        ));
1462
1463        // Any starting position other than 0 should fail to verify.
1464        assert!(!proof.verify_range_inclusion(&hasher, empty_range, Location::new(1), &root));
1465
1466        // Invalid root should fail to verify.
1467        let td = test_digest(0);
1468        assert!(!proof.verify_range_inclusion(&hasher, empty_range, Location::new(0), &td));
1469
1470        // Non-empty elements list should fail to verify.
1471        assert!(!proof.verify_range_inclusion(&hasher, &[td], Location::new(0), &root));
1472    }
1473
1474    fn verify_element<F: Family>() {
1475        // Create an 11 element structure and test single-element inclusion proofs.
1476        let element = D::from(*b"01234567012345670123456701234567");
1477        let hasher = H::new(ForwardFold);
1478        let mut mem = Mem::<F, D>::new();
1479        let batch = {
1480            let mut batch = mem.new_batch();
1481            for _ in 0..11 {
1482                batch = batch.add(&hasher, &element);
1483            }
1484            batch.merkleize(&mem, &hasher)
1485        };
1486        mem.apply_batch(&batch).unwrap();
1487        let root = plain_root(&mem, &hasher);
1488
1489        // Confirm the proof of inclusion for each leaf verifies.
1490        for leaf in 0u64..11 {
1491            let leaf = Location::new(leaf);
1492            let proof: Proof<F, D> = mem.proof(&hasher, leaf, 0).unwrap();
1493            assert!(
1494                proof.verify_element_inclusion(&hasher, &element, leaf, &root),
1495                "valid proof should verify successfully"
1496            );
1497        }
1498
1499        // Create a valid proof, then confirm various mangling of the proof or proof args results in
1500        // verification failure.
1501        let leaf = Location::<F>::new(10);
1502        let proof = mem.proof(&hasher, leaf, 0).unwrap();
1503        assert!(
1504            proof.verify_element_inclusion(&hasher, &element, leaf, &root),
1505            "proof verification should be successful"
1506        );
1507        assert!(
1508            !proof.verify_element_inclusion(&hasher, &element, leaf + 1, &root),
1509            "proof verification should fail with incorrect element position"
1510        );
1511        assert!(
1512            !proof.verify_element_inclusion(&hasher, &element, leaf - 1, &root),
1513            "proof verification should fail with incorrect element position 2"
1514        );
1515        assert!(
1516            !proof.verify_element_inclusion(&hasher, &test_digest(0), leaf, &root),
1517            "proof verification should fail with mangled element"
1518        );
1519        let root2 = test_digest(0);
1520        assert!(
1521            !proof.verify_element_inclusion(&hasher, &element, leaf, &root2),
1522            "proof verification should fail with mangled root"
1523        );
1524        let mut proof2 = proof.clone();
1525        proof2.digests[0] = test_digest(0);
1526        assert!(
1527            !proof2.verify_element_inclusion(&hasher, &element, leaf, &root),
1528            "proof verification should fail with mangled proof hash"
1529        );
1530        proof2 = proof.clone();
1531        proof2.leaves = Location::new(10);
1532        assert!(
1533            !proof2.verify_element_inclusion(&hasher, &element, leaf, &root),
1534            "proof verification should fail with incorrect leaves"
1535        );
1536        proof2 = proof.clone();
1537        proof2.digests.push(test_digest(0));
1538        assert!(
1539            !proof2.verify_element_inclusion(&hasher, &element, leaf, &root),
1540            "proof verification should fail with extra hash"
1541        );
1542        proof2 = proof.clone();
1543        while !proof2.digests.is_empty() {
1544            proof2.digests.pop();
1545            assert!(
1546                !proof2.verify_element_inclusion(&hasher, &element, leaf, &root),
1547                "proof verification should fail with missing digests"
1548            );
1549        }
1550        // Inserting an extra digest in the middle should cause verification failure.
1551        if proof.digests.len() >= 2 {
1552            proof2 = proof.clone();
1553            proof2.digests.clear();
1554            proof2.digests.extend(proof.digests[0..1].iter().cloned());
1555            proof2.digests.push(test_digest(0));
1556            proof2.digests.extend(proof.digests[1..].iter().cloned());
1557            assert!(
1558                !proof2.verify_element_inclusion(&hasher, &element, leaf, &root),
1559                "proof verification should fail with extra hash even if it's unused by the computation"
1560            );
1561        }
1562    }
1563
1564    fn verify_range<F: Family>() {
1565        // Create a structure and add 49 elements.
1566        let hasher = H::new(ForwardFold);
1567        let mut mem = Mem::<F, D>::new();
1568        let elements: Vec<_> = (0..49).map(test_digest).collect();
1569        let batch = {
1570            let mut batch = mem.new_batch();
1571            for element in &elements {
1572                batch = batch.add(&hasher, element);
1573            }
1574            batch.merkleize(&mem, &hasher)
1575        };
1576        mem.apply_batch(&batch).unwrap();
1577        let root = plain_root(&mem, &hasher);
1578
1579        // Test range proofs over all possible ranges of at least 2 elements.
1580        for i in 0..elements.len() {
1581            for j in i + 1..elements.len() {
1582                let range = Location::new(i as u64)..Location::new(j as u64);
1583                let range_proof = mem.range_proof(&hasher, range.clone(), 0).unwrap();
1584                assert!(
1585                    range_proof.verify_range_inclusion(
1586                        &hasher,
1587                        &elements[range.to_usize_range()],
1588                        range.start,
1589                        &root
1590                    ),
1591                    "valid range proof should verify successfully {i}:{j}",
1592                );
1593            }
1594        }
1595
1596        // Create a proof over a range, confirm it verifies, then mangle it in various ways.
1597        let range = Location::new(33)..Location::new(40);
1598        let range_proof = mem.range_proof(&hasher, range.clone(), 0).unwrap();
1599        let valid_elements = &elements[range.to_usize_range()];
1600        assert!(
1601            range_proof.verify_range_inclusion(&hasher, valid_elements, range.start, &root),
1602            "valid range proof should verify successfully"
1603        );
1604        let mut invalid_proof = range_proof.clone();
1605        invalid_proof.inactive_peaks = 1;
1606        assert!(
1607            !invalid_proof.verify_range_inclusion(&hasher, valid_elements, range.start, &root),
1608            "plain range proof with inactive peaks must fail verification"
1609        );
1610        // Remove digests from the proof until it's empty.
1611        let mut invalid_proof = range_proof.clone();
1612        for _i in 0..range_proof.digests.len() {
1613            invalid_proof.digests.remove(0);
1614            assert!(
1615                !invalid_proof.verify_range_inclusion(&hasher, valid_elements, range.start, &root),
1616                "range proof with removed elements should fail"
1617            );
1618        }
1619        // Confirm proof verification fails when providing an element range different than the one
1620        // used to generate the proof.
1621        for i in 0..elements.len() {
1622            for j in i + 1..elements.len() {
1623                if Location::<F>::from(i) == range.start && Location::<F>::from(j) == range.end {
1624                    continue;
1625                }
1626                assert!(
1627                    !range_proof.verify_range_inclusion(
1628                        &hasher,
1629                        &elements[i..j],
1630                        range.start,
1631                        &root
1632                    ),
1633                    "range proof with invalid element range should fail {i}:{j}",
1634                );
1635            }
1636        }
1637        // Confirm proof fails to verify with an invalid root.
1638        let invalid_root = test_digest(1);
1639        assert!(
1640            !range_proof.verify_range_inclusion(
1641                &hasher,
1642                valid_elements,
1643                range.start,
1644                &invalid_root
1645            ),
1646            "range proof with invalid root should fail"
1647        );
1648        // Mangle each element of the proof and confirm it fails to verify.
1649        for i in 0..range_proof.digests.len() {
1650            let mut invalid_proof = range_proof.clone();
1651            invalid_proof.digests[i] = test_digest(0);
1652            assert!(
1653                !invalid_proof.verify_range_inclusion(&hasher, valid_elements, range.start, &root),
1654                "mangled range proof should fail verification"
1655            );
1656        }
1657        // Inserting elements into the proof should also cause it to fail (malleability check)
1658        for i in 0..range_proof.digests.len() {
1659            let mut invalid_proof = range_proof.clone();
1660            invalid_proof.digests.insert(i, test_digest(0));
1661            assert!(
1662                !invalid_proof.verify_range_inclusion(&hasher, valid_elements, range.start, &root),
1663                "mangled range proof should fail verification. inserted element at: {i}",
1664            );
1665        }
1666        // Bad start_loc should cause verification to fail.
1667        for loc in 0..elements.len() {
1668            let loc = Location::new(loc as u64);
1669            if loc == range.start {
1670                continue;
1671            }
1672            assert!(
1673                !range_proof.verify_range_inclusion(&hasher, valid_elements, loc, &root),
1674                "bad start_loc should fail verification {loc}",
1675            );
1676        }
1677    }
1678
1679    fn retained_nodes_provable_after_pruning<F: Family>() {
1680        // Create a structure and add 49 elements.
1681        let hasher = H::new(ForwardFold);
1682        let mut mem = Mem::<F, D>::new();
1683        let elements: Vec<_> = (0..49).map(test_digest).collect();
1684        let batch = {
1685            let mut batch = mem.new_batch();
1686            for element in &elements {
1687                batch = batch.add(&hasher, element);
1688            }
1689            batch.merkleize(&mem, &hasher)
1690        };
1691        mem.apply_batch(&batch).unwrap();
1692
1693        // Confirm we can successfully prove all retained elements after pruning.
1694        let root = plain_root(&mem, &hasher);
1695        for prune_leaf in 1..*mem.leaves() {
1696            let prune_loc = Location::new(prune_leaf);
1697            mem.prune(prune_loc).unwrap();
1698            let pruned_root = plain_root(&mem, &hasher);
1699            assert_eq!(root, pruned_root);
1700            for loc in 0..elements.len() {
1701                let loc = Location::new(loc as u64);
1702                let proof = mem.proof(&hasher, loc, 0);
1703                if loc < prune_loc {
1704                    continue;
1705                }
1706                assert!(proof.is_ok());
1707                assert!(proof.unwrap().verify_element_inclusion(
1708                    &hasher,
1709                    &elements[*loc as usize],
1710                    loc,
1711                    &root
1712                ));
1713            }
1714        }
1715    }
1716
1717    fn ranges_provable_after_pruning<F: Family>() {
1718        // Create a structure and add 49 elements.
1719        let hasher = H::new(ForwardFold);
1720        let mut mem = Mem::<F, D>::new();
1721        let mut elements: Vec<_> = (0..49).map(test_digest).collect();
1722        let batch = {
1723            let mut batch = mem.new_batch();
1724            for element in &elements {
1725                batch = batch.add(&hasher, element);
1726            }
1727            batch.merkleize(&mem, &hasher)
1728        };
1729        mem.apply_batch(&batch).unwrap();
1730
1731        // Prune up to the first peak.
1732        let prune_loc = Location::<F>::new(32);
1733        mem.prune(prune_loc).unwrap();
1734        assert_eq!(mem.bounds().start, prune_loc);
1735
1736        // Test range proofs over all possible ranges of at least 2 elements
1737        let root = plain_root(&mem, &hasher);
1738        for i in 0..elements.len() - 1 {
1739            if Location::<F>::new(i as u64) < prune_loc {
1740                continue;
1741            }
1742            for j in (i + 2)..elements.len() {
1743                let range = Location::new(i as u64)..Location::new(j as u64);
1744                let range_proof = mem.range_proof(&hasher, range.clone(), 0).unwrap();
1745                assert!(
1746                    range_proof.verify_range_inclusion(
1747                        &hasher,
1748                        &elements[range.to_usize_range()],
1749                        range.start,
1750                        &root
1751                    ),
1752                    "valid range proof over remaining elements should verify successfully",
1753                );
1754            }
1755        }
1756
1757        // Add more nodes, prune again, and test again.
1758        let new_elements: Vec<_> = (0..37).map(test_digest).collect();
1759        let batch = {
1760            let mut batch = mem.new_batch();
1761            for element in &new_elements {
1762                batch = batch.add(&hasher, element);
1763            }
1764            batch.merkleize(&mem, &hasher)
1765        };
1766        mem.apply_batch(&batch).unwrap();
1767        elements.extend(new_elements);
1768        mem.prune(Location::new(66)).unwrap();
1769        assert_eq!(mem.bounds().start, Location::new(66));
1770
1771        let updated_root = plain_root(&mem, &hasher);
1772        let range = Location::new(elements.len() as u64 - 10)..Location::new(elements.len() as u64);
1773        let range_proof = mem.range_proof(&hasher, range.clone(), 0).unwrap();
1774        assert!(
1775            range_proof.verify_range_inclusion(
1776                &hasher,
1777                &elements[range.to_usize_range()],
1778                range.start,
1779                &updated_root
1780            ),
1781            "valid range proof over remaining elements after 2 pruning rounds should verify",
1782        );
1783    }
1784
1785    fn proof_serialization<F: Family>() {
1786        // Create a structure and add 25 elements.
1787        let hasher = H::new(ForwardFold);
1788        let mut mem = Mem::<F, D>::new();
1789        let elements: Vec<_> = (0..25).map(test_digest).collect();
1790        let batch = {
1791            let mut batch = mem.new_batch();
1792            for element in &elements {
1793                batch = batch.add(&hasher, element);
1794            }
1795            batch.merkleize(&mem, &hasher)
1796        };
1797        mem.apply_batch(&batch).unwrap();
1798
1799        // Generate proofs over all possible ranges of elements and confirm each
1800        // serializes=>deserializes correctly.
1801        for i in 0..elements.len() {
1802            for j in i + 1..elements.len() {
1803                let range = Location::new(i as u64)..Location::new(j as u64);
1804                let proof = mem.range_proof(&hasher, range, 0).unwrap();
1805
1806                let expected_size = proof.encode_size();
1807                let serialized_proof = proof.encode();
1808                assert_eq!(
1809                    serialized_proof.len(),
1810                    expected_size,
1811                    "serialized proof should have expected size"
1812                );
1813                let max_digests = proof.digests.len();
1814                let deserialized_proof =
1815                    Proof::<F, D>::decode_cfg(serialized_proof, &max_digests).unwrap();
1816                assert_eq!(
1817                    proof, deserialized_proof,
1818                    "deserialized proof should match source proof"
1819                );
1820
1821                // Remove one byte from the end and confirm it fails to deserialize.
1822                let serialized_proof = proof.encode();
1823                let serialized_proof = serialized_proof.slice(0..serialized_proof.len() - 1);
1824                assert!(
1825                    Proof::<F, D>::decode_cfg(serialized_proof, &max_digests).is_err(),
1826                    "proof should not deserialize with truncated data"
1827                );
1828
1829                // Add extra data and confirm it fails to deserialize.
1830                let mut serialized_proof = proof.encode_mut();
1831                serialized_proof.extend_from_slice(&[0; 10]);
1832                let serialized_proof = serialized_proof;
1833                assert!(
1834                    Proof::<F, D>::decode_cfg(serialized_proof, &max_digests).is_err(),
1835                    "proof should not deserialize with extra data"
1836                );
1837
1838                // Confirm deserialization fails when max_digests is too small.
1839                let actual_digests = proof.digests.len();
1840                if actual_digests > 0 {
1841                    let too_small = actual_digests - 1;
1842                    let serialized_proof = proof.encode();
1843                    assert!(
1844                        Proof::<F, D>::decode_cfg(serialized_proof, &too_small).is_err(),
1845                        "proof should not deserialize with max_digests too small"
1846                    );
1847                }
1848            }
1849        }
1850    }
1851
1852    fn multi_proof_generation_and_verify<F: Family>() {
1853        // Create a structure with 20 elements.
1854        let hasher = H::new(ForwardFold);
1855        let mut mem = Mem::<F, D>::new();
1856        let elements: Vec<_> = (0..20).map(test_digest).collect();
1857        let batch = {
1858            let mut batch = mem.new_batch();
1859            for element in &elements {
1860                batch = batch.add(&hasher, element);
1861            }
1862            batch.merkleize(&mem, &hasher)
1863        };
1864        mem.apply_batch(&batch).unwrap();
1865
1866        let root = plain_root(&mem, &hasher);
1867
1868        // Generate proof for non-contiguous single elements.
1869        let locations = &[Location::new(0), Location::new(5), Location::new(10)];
1870        let nodes_for_multi_proof =
1871            nodes_required_for_multi_proof(mem.leaves(), 0, Bagging::ForwardFold, locations)
1872                .expect("test locations valid");
1873        let digests = nodes_for_multi_proof
1874            .into_iter()
1875            .map(|pos| mem.get_node(pos).unwrap())
1876            .collect();
1877        let multi_proof = Proof {
1878            leaves: mem.leaves(),
1879            inactive_peaks: 0,
1880            digests,
1881        };
1882
1883        assert_eq!(multi_proof.leaves, mem.leaves());
1884
1885        // Verify the proof.
1886        assert!(multi_proof.verify_multi_inclusion(
1887            &hasher,
1888            &[
1889                (elements[0], Location::new(0)),
1890                (elements[5], Location::new(5)),
1891                (elements[10], Location::new(10)),
1892            ],
1893            &root
1894        ));
1895
1896        // Verify in different order.
1897        assert!(multi_proof.verify_multi_inclusion(
1898            &hasher,
1899            &[
1900                (elements[10], Location::new(10)),
1901                (elements[5], Location::new(5)),
1902                (elements[0], Location::new(0)),
1903            ],
1904            &root
1905        ));
1906
1907        let mut invalid_proof = multi_proof.clone();
1908        invalid_proof.inactive_peaks = 1;
1909        assert!(!invalid_proof.verify_multi_inclusion(
1910            &hasher,
1911            &[
1912                (elements[0], Location::new(0)),
1913                (elements[5], Location::new(5)),
1914                (elements[10], Location::new(10)),
1915            ],
1916            &root
1917        ));
1918
1919        // Verify with duplicate items.
1920        assert!(multi_proof.verify_multi_inclusion(
1921            &hasher,
1922            &[
1923                (elements[0], Location::new(0)),
1924                (elements[0], Location::new(0)),
1925                (elements[10], Location::new(10)),
1926                (elements[5], Location::new(5)),
1927            ],
1928            &root
1929        ));
1930
1931        // Verify mangling the location to something invalid should fail.
1932        let mut wrong_size_proof = multi_proof.clone();
1933        wrong_size_proof.leaves = Location::new(*F::MAX_LEAVES + 2);
1934        assert!(!wrong_size_proof.verify_multi_inclusion(
1935            &hasher,
1936            &[
1937                (elements[0], Location::new(0)),
1938                (elements[5], Location::new(5)),
1939                (elements[10], Location::new(10)),
1940            ],
1941            &root
1942        ));
1943
1944        // Verify with wrong positions.
1945        assert!(!multi_proof.verify_multi_inclusion(
1946            &hasher,
1947            &[
1948                (elements[0], Location::new(1)),
1949                (elements[5], Location::new(6)),
1950                (elements[10], Location::new(11)),
1951            ],
1952            &root
1953        ));
1954
1955        // Verify with wrong elements.
1956        let wrong_elements = [
1957            vec![255u8, 254u8, 253u8],
1958            vec![252u8, 251u8, 250u8],
1959            vec![249u8, 248u8, 247u8],
1960        ];
1961        let wrong_verification = multi_proof.verify_multi_inclusion(
1962            &hasher,
1963            &[
1964                (wrong_elements[0].as_slice(), Location::new(0)),
1965                (wrong_elements[1].as_slice(), Location::new(5)),
1966                (wrong_elements[2].as_slice(), Location::new(10)),
1967            ],
1968            &root,
1969        );
1970        assert!(!wrong_verification, "Should fail with wrong elements");
1971
1972        // Verify with out of range element.
1973        let wrong_verification = multi_proof.verify_multi_inclusion(
1974            &hasher,
1975            &[
1976                (elements[0], Location::new(0)),
1977                (elements[5], Location::new(5)),
1978                (elements[10], Location::new(1000)),
1979            ],
1980            &root,
1981        );
1982        assert!(
1983            !wrong_verification,
1984            "Should fail with out of range elements"
1985        );
1986
1987        // Verify with wrong root should fail.
1988        let wrong_root = test_digest(99);
1989        assert!(!multi_proof.verify_multi_inclusion(
1990            &hasher,
1991            &[
1992                (elements[0], Location::new(0)),
1993                (elements[5], Location::new(5)),
1994                (elements[10], Location::new(10)),
1995            ],
1996            &wrong_root
1997        ));
1998
1999        // Empty multi-proof.
2000        let hasher = H::new(ForwardFold);
2001        let empty_mem = Mem::<F, D>::new();
2002        let empty_root = plain_root(&empty_mem, &hasher);
2003        let empty_proof: Proof<F, D> = Proof::default();
2004        let empty_multi: &[(D, Location<F>)] = &[];
2005        assert!(empty_proof.verify_multi_inclusion(&hasher, empty_multi, &empty_root));
2006
2007        // Malformed empty proof with extra digests must be rejected.
2008        let malformed_proof: Proof<F, D> = Proof {
2009            leaves: Location::new(0),
2010            inactive_peaks: 0,
2011            digests: vec![test_digest(0)],
2012        };
2013        assert!(!malformed_proof.verify_multi_inclusion(&hasher, empty_multi, &empty_root));
2014    }
2015
2016    fn multi_proof_deduplication<F: Family>() {
2017        let hasher = H::new(ForwardFold);
2018        let mut mem = Mem::<F, D>::new();
2019        let elements: Vec<_> = (0..30).map(test_digest).collect();
2020        let batch = {
2021            let mut batch = mem.new_batch();
2022            for element in &elements {
2023                batch = batch.add(&hasher, element);
2024            }
2025            batch.merkleize(&mem, &hasher)
2026        };
2027        mem.apply_batch(&batch).unwrap();
2028
2029        // Get individual proofs that will share some digests (elements in same subtree).
2030        let proof1 = mem.proof(&hasher, Location::new(0), 0).unwrap();
2031        let proof2 = mem.proof(&hasher, Location::new(1), 0).unwrap();
2032        let total_digests_separate = proof1.digests.len() + proof2.digests.len();
2033
2034        // Generate multi-proof for the same positions.
2035        let locations = &[Location::new(0), Location::new(1)];
2036        let multi_proof_nodes =
2037            nodes_required_for_multi_proof(mem.leaves(), 0, Bagging::ForwardFold, locations)
2038                .expect("test locations valid");
2039        let digests = multi_proof_nodes
2040            .into_iter()
2041            .map(|pos| mem.get_node(pos).unwrap())
2042            .collect();
2043        let multi_proof = Proof {
2044            leaves: mem.leaves(),
2045            inactive_peaks: 0,
2046            digests,
2047        };
2048
2049        // The combined proof should have fewer digests due to deduplication.
2050        assert!(multi_proof.digests.len() < total_digests_separate);
2051
2052        // Verify it still works.
2053        let root = plain_root(&mem, &hasher);
2054        assert!(multi_proof.verify_multi_inclusion(
2055            &hasher,
2056            &[
2057                (elements[0], Location::new(0)),
2058                (elements[1], Location::new(1))
2059            ],
2060            &root
2061        ));
2062    }
2063
2064    fn proof_leaves_malleability<F: Family>() {
2065        let hasher = H::new(ForwardFold);
2066        let mut mem = Mem::<F, D>::new();
2067
2068        // 252 leaves. Leaf 240 sits in a peak preceded by prefix peaks.
2069        let elements: Vec<D> = (0..252u16)
2070            .map(|i| <Sha256 as commonware_cryptography::Hasher>::hash(&i.to_be_bytes()))
2071            .collect();
2072        let batch = {
2073            let mut batch = mem.new_batch();
2074            for e in &elements {
2075                batch = batch.add(&hasher, e);
2076            }
2077            batch.merkleize(&mem, &hasher)
2078        };
2079        mem.apply_batch(&batch).unwrap();
2080        let root = plain_root(&mem, &hasher);
2081
2082        let loc = Location::new(240);
2083        let proof = mem.proof(&hasher, loc, 0).unwrap();
2084        assert!(proof.verify_element_inclusion(&hasher, &elements[240], loc, &root));
2085
2086        // Tamper with the leaves field (249 has the same peak layout for leaf 240).
2087        let mut tampered = proof.clone();
2088        tampered.leaves = Location::new(249);
2089        assert_ne!(tampered, proof);
2090        assert!(
2091            !tampered.verify_element_inclusion(&hasher, &elements[240], loc, &root),
2092            "proof with tampered leaves field must not verify"
2093        );
2094
2095        let mut tampered = proof.clone();
2096        tampered.inactive_peaks = 1;
2097        assert_ne!(tampered, proof);
2098        assert!(
2099            !tampered.verify_element_inclusion(&hasher, &elements[240], loc, &root),
2100            "proof with tampered inactive_peaks must not verify"
2101        );
2102    }
2103
2104    fn blueprint_errors<F: Family>() {
2105        let leaves = Location::<F>::new(10);
2106
2107        // Empty range.
2108        assert!(matches!(
2109            Blueprint::<F>::new(
2110                leaves,
2111                0,
2112                Bagging::ForwardFold,
2113                Location::new(3)..Location::new(3)
2114            ),
2115            Err(crate::merkle::Error::Empty)
2116        ));
2117
2118        // Out of bounds.
2119        assert!(matches!(
2120            Blueprint::<F>::new(
2121                leaves,
2122                0,
2123                Bagging::ForwardFold,
2124                Location::new(0)..Location::new(11)
2125            ),
2126            Err(crate::merkle::Error::RangeOutOfBounds(_))
2127        ));
2128
2129        // Inactive prefix cannot exceed the number of peaks.
2130        let peak_count = F::peaks(Position::try_from(leaves).unwrap()).count();
2131        assert!(matches!(
2132            Blueprint::<F>::new(
2133                leaves,
2134                peak_count + 1,
2135                Bagging::ForwardFold,
2136                Location::new(0)..Location::new(1)
2137            ),
2138            Err(crate::merkle::Error::InvalidProof)
2139        ));
2140
2141        // Empty locations for multi-proof.
2142        assert!(matches!(
2143            nodes_required_for_multi_proof::<F>(leaves, 0, Bagging::ForwardFold, &[]),
2144            Err(crate::merkle::Error::Empty)
2145        ));
2146    }
2147
2148    fn single_element_proof_reconstruction<F: Family>() {
2149        for n in 1u64..=64 {
2150            let hasher = H::new(ForwardFold);
2151            let mem = build_raw::<F>(&hasher, n);
2152            let root = plain_root(&mem, &hasher);
2153
2154            for loc_idx in 0..n {
2155                let proof = mem
2156                    .proof(&hasher, Location::new(loc_idx), 0)
2157                    .unwrap_or_else(|e| panic!("n={n}, loc={loc_idx}: build failed: {e:?}"));
2158
2159                let elements = [loc_idx.to_be_bytes()];
2160                let start_loc = Location::new(loc_idx);
2161
2162                let reconstructed = proof
2163                    .reconstruct_root(&hasher, &elements, start_loc)
2164                    .unwrap_or_else(|e| panic!("n={n}, loc={loc_idx}: reconstruct failed: {e:?}"));
2165                assert_eq!(reconstructed, root, "n={n}, loc={loc_idx}: root mismatch");
2166            }
2167        }
2168    }
2169
2170    fn range_proof_reconstruction<F: Family>() {
2171        for n in 2u64..=32 {
2172            let hasher = H::new(ForwardFold);
2173            let mem = build_raw::<F>(&hasher, n);
2174            let root = plain_root(&mem, &hasher);
2175
2176            let ranges: Vec<(u64, u64)> = vec![
2177                (0, n),
2178                (0, 1),
2179                (n - 1, n),
2180                (0, n.min(3)),
2181                (n.saturating_sub(3), n),
2182            ];
2183
2184            for (start, end) in ranges {
2185                if start >= end || end > n {
2186                    continue;
2187                }
2188                let proof = mem
2189                    .range_proof(&hasher, Location::new(start)..Location::new(end), 0)
2190                    .unwrap_or_else(|e| panic!("n={n}, range={start}..{end}: build failed: {e:?}"));
2191                let elements: Vec<_> = (start..end).map(|i| i.to_be_bytes()).collect();
2192                let start_loc = Location::new(start);
2193
2194                let reconstructed = proof
2195                    .reconstruct_root(&hasher, &elements, start_loc)
2196                    .unwrap_or_else(|e| {
2197                        panic!("n={n}, range={start}..{end}: reconstruct failed: {e}")
2198                    });
2199                assert_eq!(
2200                    reconstructed, root,
2201                    "n={n}, range={start}..{end}: root mismatch"
2202                );
2203            }
2204        }
2205    }
2206
2207    fn verify_element_inclusion<F: Family>() {
2208        for n in 1u64..=32 {
2209            let hasher = H::new(ForwardFold);
2210            let mem = build_raw::<F>(&hasher, n);
2211            let root = plain_root(&mem, &hasher);
2212
2213            for loc_idx in 0..n {
2214                let proof = mem.proof(&hasher, Location::new(loc_idx), 0).unwrap();
2215                let loc = Location::new(loc_idx);
2216
2217                assert!(
2218                    proof.verify_element_inclusion(&hasher, &loc_idx.to_be_bytes(), loc, &root),
2219                    "n={n}, loc={loc_idx}: verification failed"
2220                );
2221
2222                // Wrong element should fail.
2223                assert!(
2224                    !proof.verify_element_inclusion(
2225                        &hasher,
2226                        &(loc_idx + 1000).to_be_bytes(),
2227                        loc,
2228                        &root
2229                    ),
2230                    "n={n}, loc={loc_idx}: wrong element should not verify"
2231                );
2232            }
2233        }
2234    }
2235
2236    fn full_range<F: Family>() {
2237        for n in 1u64..=32 {
2238            let hasher = H::new(ForwardFold);
2239            let mem = build_raw::<F>(&hasher, n);
2240            let root = plain_root(&mem, &hasher);
2241
2242            let proof = mem
2243                .range_proof(&hasher, Location::new(0)..Location::new(n), 0)
2244                .unwrap();
2245            let elements: Vec<_> = (0..n).map(|i| i.to_be_bytes()).collect();
2246            let reconstructed = proof
2247                .reconstruct_root(&hasher, &elements, Location::new(0))
2248                .unwrap();
2249            assert_eq!(reconstructed, root, "n={n}: full range failed");
2250
2251            // Full range should have 0 digests.
2252            assert_eq!(
2253                proof.digests.len(),
2254                0,
2255                "n={n}: full range proof should have 0 digests"
2256            );
2257        }
2258    }
2259
2260    fn empty_proof_verifies_empty_tree<F: Family>() {
2261        let hasher = H::new(ForwardFold);
2262        let mem = Mem::<F, D>::new();
2263        let root = plain_root(&mem, &hasher);
2264        let proof = Proof::<F, D>::default();
2265
2266        // Empty proof should verify against the empty root.
2267        assert!(proof.verify_range_inclusion(&hasher, &[] as &[&[u8]], Location::new(0), &root));
2268
2269        let mut inactive_proof = proof.clone();
2270        inactive_proof.inactive_peaks = 1;
2271        assert!(!inactive_proof.verify_range_inclusion(
2272            &hasher,
2273            &[] as &[&[u8]],
2274            Location::new(0),
2275            &root
2276        ));
2277        assert!(!inactive_proof.verify_multi_inclusion(
2278            &hasher,
2279            &[] as &[(&[u8], Location<F>)],
2280            &root
2281        ));
2282
2283        // Non-zero start_loc with empty elements should fail.
2284        assert!(!proof.verify_range_inclusion(&hasher, &[] as &[&[u8]], Location::new(1), &root));
2285    }
2286
2287    fn every_element_contributes_to_root<F: Family>() {
2288        for n in [8u64, 13, 20, 32] {
2289            let hasher = H::new(ForwardFold);
2290            let mem = build_raw::<F>(&hasher, n);
2291            let root = plain_root(&mem, &hasher);
2292
2293            let start = 1;
2294            let end = n - 1;
2295            let proof = mem
2296                .range_proof(&hasher, Location::new(start)..Location::new(end), 0)
2297                .unwrap();
2298            let elements: Vec<_> = (start..end).map(|i| i.to_be_bytes()).collect();
2299
2300            // Valid elements verify.
2301            assert!(
2302                proof.verify_range_inclusion(&hasher, &elements, Location::new(start), &root),
2303                "n={n}: valid range should verify"
2304            );
2305
2306            // Flipping one byte in each element must cause failure.
2307            for flip_idx in 0..elements.len() {
2308                let mut tampered = elements.clone();
2309                tampered[flip_idx][0] ^= 0xFF;
2310                assert!(
2311                    !proof.verify_range_inclusion(&hasher, &tampered, Location::new(start), &root),
2312                    "n={n}: tampered element at index {flip_idx} should not verify"
2313                );
2314            }
2315        }
2316    }
2317
2318    fn multi_proof_generation_and_verify_raw<F: Family>() {
2319        let hasher = H::new(ForwardFold);
2320        let mem = build_raw::<F>(&hasher, 20);
2321        let root = plain_root(&mem, &hasher);
2322
2323        let locations = &[Location::new(0), Location::new(5), Location::new(10)];
2324        let nodes =
2325            nodes_required_for_multi_proof(mem.leaves(), 0, Bagging::ForwardFold, locations)
2326                .expect("valid locations");
2327        let digests = nodes
2328            .into_iter()
2329            .map(|pos| mem.get_node(pos).unwrap())
2330            .collect();
2331        let multi_proof = Proof {
2332            leaves: mem.leaves(),
2333            inactive_peaks: 0,
2334            digests,
2335        };
2336
2337        // Verify the proof.
2338        assert!(multi_proof.verify_multi_inclusion(
2339            &hasher,
2340            &[
2341                (0u64.to_be_bytes(), Location::new(0)),
2342                (5u64.to_be_bytes(), Location::new(5)),
2343                (10u64.to_be_bytes(), Location::new(10)),
2344            ],
2345            &root
2346        ));
2347
2348        // Different order should also verify.
2349        assert!(multi_proof.verify_multi_inclusion(
2350            &hasher,
2351            &[
2352                (10u64.to_be_bytes(), Location::new(10)),
2353                (5u64.to_be_bytes(), Location::new(5)),
2354                (0u64.to_be_bytes(), Location::new(0)),
2355            ],
2356            &root
2357        ));
2358
2359        // Wrong elements should fail.
2360        assert!(!multi_proof.verify_multi_inclusion(
2361            &hasher,
2362            &[
2363                (99u64.to_be_bytes(), Location::new(0)),
2364                (5u64.to_be_bytes(), Location::new(5)),
2365                (10u64.to_be_bytes(), Location::new(10)),
2366            ],
2367            &root
2368        ));
2369
2370        // Wrong root should fail.
2371        let wrong_root = hasher.digest(b"wrong");
2372        assert!(!multi_proof.verify_multi_inclusion(
2373            &hasher,
2374            &[
2375                (0u64.to_be_bytes(), Location::new(0)),
2376                (5u64.to_be_bytes(), Location::new(5)),
2377                (10u64.to_be_bytes(), Location::new(10)),
2378            ],
2379            &wrong_root
2380        ));
2381
2382        // Empty multi-proof on empty tree.
2383        let hasher2 = H::new(ForwardFold);
2384        let empty_mem = Mem::<F, D>::new();
2385        let empty_proof: Proof<F, D> = Proof::default();
2386        assert!(empty_proof.verify_multi_inclusion(
2387            &hasher2,
2388            &[] as &[([u8; 8], Location<F>)],
2389            &plain_root(&empty_mem, &hasher2)
2390        ));
2391
2392        // Malformed empty proof with extra digests must be rejected.
2393        let malformed_proof: Proof<F, D> = Proof {
2394            leaves: Location::new(0),
2395            inactive_peaks: 0,
2396            digests: vec![test_digest(0)],
2397        };
2398        assert!(!malformed_proof.verify_multi_inclusion(
2399            &hasher2,
2400            &[] as &[([u8; 8], Location<F>)],
2401            &plain_root(&empty_mem, &hasher2)
2402        ));
2403    }
2404
2405    fn tampered_proof_digests_rejected<F: Family>() {
2406        for n in [8u64, 13, 20, 32] {
2407            let hasher = H::new(ForwardFold);
2408            let mem = build_raw::<F>(&hasher, n);
2409            let root = plain_root(&mem, &hasher);
2410
2411            for loc_idx in [0, n / 2, n - 1] {
2412                let proof = mem.proof(&hasher, Location::new(loc_idx), 0).unwrap();
2413                let element = loc_idx.to_be_bytes();
2414                let loc = Location::new(loc_idx);
2415
2416                assert!(proof.verify_element_inclusion(&hasher, &element, loc, &root));
2417
2418                for digest_idx in 0..proof.digests.len() {
2419                    let mut tampered = proof.clone();
2420                    tampered.digests[digest_idx].0[0] ^= 1;
2421                    assert!(
2422                        !tampered.verify_element_inclusion(&hasher, &element, loc, &root),
2423                        "n={n}, loc={loc_idx}: tampered digest[{digest_idx}] should not verify"
2424                    );
2425                }
2426            }
2427        }
2428    }
2429
2430    fn no_duplicate_positions<F: Family>() {
2431        use alloc::collections::BTreeSet;
2432        for n in 1u64..=64 {
2433            let hasher = H::new(ForwardFold);
2434            let mem = build_raw::<F>(&hasher, n);
2435            let leaves = mem.leaves();
2436            for loc in 0..n {
2437                let loc = Location::new(loc);
2438                let bp =
2439                    Blueprint::<F>::new(leaves, 0, Bagging::ForwardFold, loc..loc + 1).unwrap();
2440                let mut positions: Vec<Position<F>> = Vec::new();
2441                positions.extend(bp.fold_prefix.iter().map(|s| s.pos));
2442                positions.extend(&bp.fetch_nodes);
2443                let set: BTreeSet<_> = positions.iter().copied().collect();
2444                assert_eq!(
2445                    positions.len(),
2446                    set.len(),
2447                    "n={n}, loc={loc}: duplicate positions"
2448                );
2449            }
2450        }
2451    }
2452
2453    fn full_peak_range_blueprint_does_not_descend<F: Family>() {
2454        let leaves = Location::new(1u64 << 40);
2455        let range = Location::new(0)..leaves;
2456
2457        let bp = Blueprint::<F>::new(leaves, 0, Bagging::ForwardFold, range).unwrap();
2458
2459        assert!(
2460            bp.range_peaks.iter().any(|peak| peak.height >= 39),
2461            "test must include a large fully covered peak"
2462        );
2463        assert!(
2464            bp.fetch_nodes.is_empty(),
2465            "full-range proofs should not fetch per-peak siblings"
2466        );
2467    }
2468
2469    /// `verify_proof_and_pinned_nodes` must accept pinned nodes at
2470    /// `F::nodes_to_pin(start_loc)` positions for any `(leaves, start_loc)` pair.
2471    ///
2472    /// `nodes_to_pin(L)` returns the peaks of the tree at size L (the peaks you'd
2473    /// pin if you pruned to L). `fold_prefix(N, L)` returns the peaks of the size-N
2474    /// tree that lie entirely before leaf L. These can disagree when the larger tree
2475    /// has merged smaller peaks into larger subtrees. The verifier must handle this
2476    /// for both families.
2477    fn verify_proof_and_pinned_nodes_across_sizes<F: Family>() {
2478        // Sweep (leaves, start) pairs. Larger trees with start far from a peak
2479        // boundary are more likely to produce pinned positions that don't appear
2480        // as siblings in the proof walk.
2481        let cases: &[(u64, u64)] = &[
2482            // First delayed-merge birth-boundary case: the larger tree exposes a
2483            // fold-prefix peak that did not exist yet at `start`.
2484            (5, 4),
2485            (10, 3),
2486            (20, 5),
2487            (50, 10),
2488            (100, 10),
2489            (100, 30),
2490            (200, 50),
2491            (500, 100),
2492            (1000, 100),
2493            (1000, 300),
2494            (2000, 500),
2495        ];
2496
2497        let hasher = H::new(ForwardFold);
2498        for &(n, start) in cases {
2499            let mem = build_raw::<F>(&hasher, n);
2500            let root = plain_root(&mem, &hasher);
2501
2502            let pinned: Vec<D> = F::nodes_to_pin(Location::<F>::new(start))
2503                .map(|pos| mem.get_node(pos).unwrap())
2504                .collect();
2505
2506            let proof = mem
2507                .range_proof(
2508                    &hasher,
2509                    Location::<F>::new(start)..Location::<F>::new(start + 1),
2510                    0,
2511                )
2512                .unwrap();
2513
2514            assert!(
2515                proof.verify_proof_and_pinned_nodes(
2516                    &hasher,
2517                    &[start.to_be_bytes()],
2518                    Location::<F>::new(start),
2519                    &pinned,
2520                    &root
2521                ),
2522                "verify_proof_and_pinned_nodes failed: leaves={n}, start={start}"
2523            );
2524        }
2525    }
2526
2527    // ---------------------------------------------------------------------------
2528    // MMR tests
2529    // ---------------------------------------------------------------------------
2530
2531    #[test]
2532    fn mmr_empty_proof() {
2533        empty_proof::<mmr::Family>();
2534    }
2535    #[test]
2536    fn mmr_verify_element() {
2537        verify_element::<mmr::Family>();
2538    }
2539    #[test]
2540    fn mmr_verify_range() {
2541        verify_range::<mmr::Family>();
2542    }
2543    #[test_traced]
2544    fn mmr_retained_nodes_provable_after_pruning() {
2545        retained_nodes_provable_after_pruning::<mmr::Family>();
2546    }
2547    #[test]
2548    fn mmr_ranges_provable_after_pruning() {
2549        ranges_provable_after_pruning::<mmr::Family>();
2550    }
2551    #[test]
2552    fn mmr_proof_serialization() {
2553        proof_serialization::<mmr::Family>();
2554    }
2555    #[test]
2556    fn mmr_multi_proof_generation_and_verify() {
2557        multi_proof_generation_and_verify::<mmr::Family>();
2558    }
2559    #[test]
2560    fn mmr_multi_proof_deduplication() {
2561        multi_proof_deduplication::<mmr::Family>();
2562    }
2563    #[test]
2564    fn mmr_proof_leaves_malleability() {
2565        proof_leaves_malleability::<mmr::Family>();
2566    }
2567    #[test]
2568    fn mmr_blueprint_errors() {
2569        blueprint_errors::<mmr::Family>();
2570    }
2571    #[test]
2572    fn mmr_single_element_proof_reconstruction() {
2573        single_element_proof_reconstruction::<mmr::Family>();
2574    }
2575    #[test]
2576    fn mmr_range_proof_reconstruction() {
2577        range_proof_reconstruction::<mmr::Family>();
2578    }
2579    #[test]
2580    fn mmr_range_proofs_verify_for_supported_root_shapes() {
2581        range_proofs_verify_for_supported_root_shapes::<mmr::Family>();
2582    }
2583    #[test]
2584    fn mmr_multi_proofs_verify_for_supported_root_shapes() {
2585        multi_proofs_verify_for_supported_root_shapes::<mmr::Family>();
2586    }
2587    #[test]
2588    fn mmr_verify_element_inclusion() {
2589        verify_element_inclusion::<mmr::Family>();
2590    }
2591    #[test]
2592    fn mmr_full_range() {
2593        full_range::<mmr::Family>();
2594    }
2595    #[test]
2596    fn mmr_empty_proof_verifies_empty_tree() {
2597        empty_proof_verifies_empty_tree::<mmr::Family>();
2598    }
2599    #[test]
2600    fn mmr_every_element_contributes_to_root() {
2601        every_element_contributes_to_root::<mmr::Family>();
2602    }
2603    #[test]
2604    fn mmr_multi_proof_generation_and_verify_raw() {
2605        multi_proof_generation_and_verify_raw::<mmr::Family>();
2606    }
2607    #[test]
2608    fn mmr_tampered_proof_digests_rejected() {
2609        tampered_proof_digests_rejected::<mmr::Family>();
2610    }
2611    #[test]
2612    fn mmr_no_duplicate_positions() {
2613        no_duplicate_positions::<mmr::Family>();
2614    }
2615    #[test]
2616    fn mmr_full_peak_range_blueprint_does_not_descend() {
2617        full_peak_range_blueprint_does_not_descend::<mmr::Family>();
2618    }
2619    #[test]
2620    fn mmr_verify_proof_and_pinned_nodes_across_sizes() {
2621        verify_proof_and_pinned_nodes_across_sizes::<mmr::Family>();
2622    }
2623
2624    // ---------------------------------------------------------------------------
2625    // MMB tests
2626    // ---------------------------------------------------------------------------
2627
2628    #[test]
2629    fn mmb_empty_proof() {
2630        empty_proof::<mmb::Family>();
2631    }
2632    #[test]
2633    fn mmb_verify_element() {
2634        verify_element::<mmb::Family>();
2635    }
2636    #[test]
2637    fn mmb_verify_range() {
2638        verify_range::<mmb::Family>();
2639    }
2640    #[test_traced]
2641    fn mmb_retained_nodes_provable_after_pruning() {
2642        retained_nodes_provable_after_pruning::<mmb::Family>();
2643    }
2644    #[test]
2645    fn mmb_ranges_provable_after_pruning() {
2646        ranges_provable_after_pruning::<mmb::Family>();
2647    }
2648    #[test]
2649    fn mmb_proof_serialization() {
2650        proof_serialization::<mmb::Family>();
2651    }
2652    #[test]
2653    fn mmb_multi_proof_generation_and_verify() {
2654        multi_proof_generation_and_verify::<mmb::Family>();
2655    }
2656    #[test]
2657    fn mmb_multi_proof_deduplication() {
2658        multi_proof_deduplication::<mmb::Family>();
2659    }
2660    #[test]
2661    fn mmb_proof_leaves_malleability() {
2662        proof_leaves_malleability::<mmb::Family>();
2663    }
2664    #[test]
2665    fn mmb_blueprint_errors() {
2666        blueprint_errors::<mmb::Family>();
2667    }
2668    #[test]
2669    fn mmb_single_element_proof_reconstruction() {
2670        single_element_proof_reconstruction::<mmb::Family>();
2671    }
2672    #[test]
2673    fn mmb_range_proof_reconstruction() {
2674        range_proof_reconstruction::<mmb::Family>();
2675    }
2676    #[test]
2677    fn mmb_range_proofs_verify_for_supported_root_shapes() {
2678        range_proofs_verify_for_supported_root_shapes::<mmb::Family>();
2679    }
2680    #[test]
2681    fn mmb_multi_proofs_verify_for_supported_root_shapes() {
2682        multi_proofs_verify_for_supported_root_shapes::<mmb::Family>();
2683    }
2684    #[test]
2685    fn mmb_verify_element_inclusion() {
2686        verify_element_inclusion::<mmb::Family>();
2687    }
2688    #[test]
2689    fn mmb_full_range() {
2690        full_range::<mmb::Family>();
2691    }
2692    #[test]
2693    fn mmb_empty_proof_verifies_empty_tree() {
2694        empty_proof_verifies_empty_tree::<mmb::Family>();
2695    }
2696    #[test]
2697    fn mmb_every_element_contributes_to_root() {
2698        every_element_contributes_to_root::<mmb::Family>();
2699    }
2700    #[test]
2701    fn mmb_multi_proof_generation_and_verify_raw() {
2702        multi_proof_generation_and_verify_raw::<mmb::Family>();
2703    }
2704    #[test]
2705    fn mmb_tampered_proof_digests_rejected() {
2706        tampered_proof_digests_rejected::<mmb::Family>();
2707    }
2708    #[test]
2709    fn mmb_backward_fold_range_proof_collapses_active_suffix() {
2710        backward_fold_proof_optimization_inner(0);
2711    }
2712    #[test]
2713    fn mmb_backward_fold_range_proof_keeps_inactive_after_peaks_individual() {
2714        backward_fold_proof_optimization_inner(2);
2715    }
2716    #[test]
2717    fn mmb_no_duplicate_positions() {
2718        no_duplicate_positions::<mmb::Family>();
2719    }
2720    #[test]
2721    fn mmb_full_peak_range_blueprint_does_not_descend() {
2722        full_peak_range_blueprint_does_not_descend::<mmb::Family>();
2723    }
2724    #[test]
2725    fn mmb_verify_proof_and_pinned_nodes_across_sizes() {
2726        verify_proof_and_pinned_nodes_across_sizes::<mmb::Family>();
2727    }
2728}