1use 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#[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#[derive(Clone, Debug, Eq)]
61pub struct Proof<F: Family, D: Digest> {
62 pub leaves: Location<F>,
67 pub inactive_peaks: usize,
69 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 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 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 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 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 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 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 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 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 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 if node_positions.len() != self.digests.len() {
228 return false;
229 }
230
231 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 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 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 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 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 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 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 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 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 pinned_map.is_empty().then_some(())
482 }
483
484 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 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 if elements_iter.next().is_some() {
578 return Err(ReconstructionError::ExtraDigests);
579 }
580
581 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#[derive(Copy, Clone)]
600pub(crate) struct Subtree<F: Family> {
601 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 fn is_before(&self, range: &Range<Location<F>>) -> bool {
614 self.leaf_end() <= range.start
615 }
616
617 fn is_outside(&self, range: &Range<Location<F>>) -> bool {
619 self.is_before(range) || self.leaf_start >= range.end
620 }
621
622 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 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 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 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 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 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 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 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
786pub(crate) struct Blueprint<F: Family> {
794 leaves: Location<F>,
796 range: Range<Location<F>>,
798 pub(crate) fold_prefix: Vec<Subtree<F>>,
800 prefix_active_peaks: Vec<Subtree<F>>,
801 after_peaks: Vec<Position<F>>,
803 suffix_peaks: Vec<Position<F>>,
805 range_peaks: Vec<Subtree<F>>,
807 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 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 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 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 pub(crate) const fn prefix_active_count(&self) -> usize {
930 self.prefix_active_peaks.len()
931 }
932
933 pub(crate) const fn after_peaks_count(&self) -> usize {
935 self.after_peaks.len()
936 }
937
938 pub(crate) fn suffix_peaks(&self) -> Option<&[Position<F>]> {
940 (!self.suffix_peaks.is_empty()).then_some(&self.suffix_peaks)
941 }
942
943 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 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 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
1048pub const MAX_PROOF_DIGESTS_PER_ELEMENT: usize = 122;
1054
1055pub(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#[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 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 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 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 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 assert!(!proof.verify_range_inclusion(&hasher, empty_range, Location::new(1), &root));
1465
1466 let td = test_digest(0);
1468 assert!(!proof.verify_range_inclusion(&hasher, empty_range, Location::new(0), &td));
1469
1470 assert!(!proof.verify_range_inclusion(&hasher, &[td], Location::new(0), &root));
1472 }
1473
1474 fn verify_element<F: Family>() {
1475 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let prune_loc = Location::<F>::new(32);
1733 mem.prune(prune_loc).unwrap();
1734 assert_eq!(mem.bounds().start, prune_loc);
1735
1736 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 assert!(multi_proof.digests.len() < total_digests_separate);
2051
2052 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 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 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 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 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 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 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 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 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 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 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 assert!(
2302 proof.verify_range_inclusion(&hasher, &elements, Location::new(start), &root),
2303 "n={n}: valid range should verify"
2304 );
2305
2306 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 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 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 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 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 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 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 fn verify_proof_and_pinned_nodes_across_sizes<F: Family>() {
2478 let cases: &[(u64, u64)] = &[
2482 (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 #[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 #[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}