commonware_storage/mmr/
grafting.rs

1//! Hasher and Storage implementations for a merkle tree _grafted_ onto another MMR.
2//!
3//! ## Terminology
4//!
5//! * **Peak Tree**: The MMR or Merkle tree that is being grafted.
6//! * **Base MMR**: The MMR onto which we are grafting (cannot be a Merkle tree).
7//!
8//! Grafting involves mapping the leaves of the peak tree to corresponding nodes in the base MMR. It
9//! allows for shorter inclusion proofs over the combined trees compared to treating them as
10//! independent.
11//!
12//! One example use case is the [crate::qmdb::current] authenticated database, where a MMR is built
13//! over a log of operations, and a merkle tree over a bitmap indicating the activity state of each
14//! operation. If we were to treat the two trees as independent, then an inclusion proof for an
15//! operation and its activity state would involve a full branch from each structure. When using
16//! grafting, we can trim the branch from the base MMR at the point it "flows" up into the peak
17//! tree, reducing the size of the proof by a constant factor up to 2.
18//!
19//! For concreteness, let's assume we have a base MMR over a log of 8 operations represented by the
20//! 8 leaves:
21//!
22//! ```text
23//!    Height
24//!      3              14
25//!                   /    \
26//!                  /      \
27//!                 /        \
28//!                /          \
29//!      2        6            13
30//!             /   \        /    \
31//!      1     2     5      9     12
32//!           / \   / \    / \   /  \
33//!      0   0   1 3   4  7   8 10  11
34//! ```
35//!
36//! Let's assume each leaf in our peak tree corresponds to 4 leaves in the base MMR. The structure
37//! of the peak tree can be obtained by chopping off the bottom log2(4)=2 levels of the base MMR
38//! structure:
39//!
40//!
41//! ```text
42//!    Height
43//!      1              2 (was 14)
44//!                   /    \
45//!                  /      \
46//!                 /        \
47//!                /          \
48//!      0        0 (was 6)    1 (was 13)
49//! ```
50//!
51//! The inverse of this procedure provides our algorithm for mapping a peak tree leaf's position to
52//! a base MMR node position: take the leaf's position in the peak tree, map it to any of the
53//! corresponding leaves in the base MMR, then walk up the base MMR structure exactly the number of
54//! levels we removed.
55//!
56//! In this example, leaf 0 in the peak tree corresponds to leaves \[0,1,3,4\] in the base MMR.
57//! Walking up two levels from any of these base MMR leaves produces node 6 of the base MMR, which
58//! is thus its grafting point. Leaf 1 in the peak tree corresponds to leaves \[7,8,10,11\] in the
59//! base MMR, yielding node 13 as its grafting point.
60
61use crate::mmr::{
62    hasher::Hasher as HasherTrait,
63    iterator::{pos_to_height, PeakIterator},
64    storage::Storage as StorageTrait,
65    Error, Location, Position, StandardHasher,
66};
67use commonware_cryptography::Hasher as CHasher;
68use futures::future::try_join_all;
69use std::collections::HashMap;
70use tracing::debug;
71
72/// A hasher for computing digests in a grafted MMR, where a peak tree is grafted onto a base MMR.
73///
74/// # Usage
75///
76/// Before calling any methods that compute leaf digests (such as `leaf_digest` or `add`), callers
77/// must first call Self::load_grafted_digests for all leaves that will be hashed. Failure to do
78/// so will result in a panic.
79pub struct Hasher<'a, H: CHasher> {
80    hasher: &'a mut StandardHasher<H>,
81    height: u32,
82
83    /// Maps a leaf's position to the digest of the node on which the leaf is grafted.
84    grafted_digests: HashMap<Position, H::Digest>,
85}
86
87impl<'a, H: CHasher> Hasher<'a, H> {
88    pub fn new(hasher: &'a mut StandardHasher<H>, height: u32) -> Self {
89        Self {
90            hasher,
91            height,
92            grafted_digests: HashMap::new(),
93        }
94    }
95
96    /// Access the underlying [StandardHasher] for non-grafted hashing.
97    pub const fn standard(&mut self) -> &mut StandardHasher<H> {
98        self.hasher
99    }
100
101    /// Loads the grafted digests for the specified leaves into the internal map. Does not clear out
102    /// any previously loaded digests. This method must be used to provide grafted digests for any
103    /// leaf whose `leaf_digest` needs to be computed.
104    ///
105    /// # Errors
106    ///
107    /// Returns [Error::LocationOverflow] if any leaf location is invalid.
108    /// Returns [Error::MissingGraftedDigest] if any of the grafted digests are missing from the MMR.
109    /// If an error occurs, no digests are inserted (all-or-nothing semantics).
110    pub(crate) async fn load_grafted_digests(
111        &mut self,
112        leaves: &[Location],
113        mmr: &impl StorageTrait<H::Digest>,
114    ) -> Result<(), Error> {
115        // Validate all leaf locations and convert to positions upfront
116        let leaf_positions: Vec<Position> = leaves
117            .iter()
118            .map(|&loc| Position::try_from(loc))
119            .collect::<Result<_, _>>()?;
120
121        // Fetch all digests from MMR
122        let futures = leaf_positions
123            .iter()
124            .map(|&pos| mmr.get_node(self.destination_pos(pos)));
125        let digests = try_join_all(futures).await?;
126
127        // Validate all digests are present before modifying state
128        for (i, digest) in digests.iter().enumerate() {
129            if digest.is_none() {
130                return Err(Error::MissingGraftedDigest(leaves[i]));
131            }
132        }
133
134        // Insert all digests (now that we know they all exist and are valid)
135        for (pos, digest) in leaf_positions.into_iter().zip(digests) {
136            self.grafted_digests.insert(pos, digest.unwrap());
137        }
138
139        Ok(())
140    }
141
142    /// Compute the position of the leaf in the base tree onto which we should graft the leaf at
143    /// position `pos` in the source tree.
144    fn destination_pos(&self, pos: Position) -> Position {
145        destination_pos(pos, self.height)
146    }
147}
148
149/// A lightweight, short-lived shallow copy of a Grafting hasher that can be used in parallel
150/// computations.
151pub struct HasherFork<'a, H: CHasher> {
152    hasher: StandardHasher<H>,
153    height: u32,
154    grafted_digests: &'a HashMap<Position, H::Digest>,
155}
156
157/// Compute the position of the node in the base tree onto which we should graft the node at
158/// position `pos` in the source tree.
159///
160/// This algorithm performs walks down corresponding branches of the peak and base trees. When we
161/// find the node in the peak tree we are looking for, we return the position of the corresponding
162/// node reached in the base tree.
163fn destination_pos(peak_node_pos: Position, height: u32) -> Position {
164    let peak_node_pos = *peak_node_pos;
165    let leading_zeros = (peak_node_pos + 1).leading_zeros();
166    assert!(leading_zeros >= height, "destination_pos > u64::MAX");
167    let mut peak_pos = u64::MAX >> leading_zeros;
168    let mut base_pos = u64::MAX >> (leading_zeros - height);
169    let mut peak_height = peak_pos.trailing_ones() - 1;
170    let mut base_height = peak_height + height;
171    peak_pos -= 1;
172    base_pos -= 1;
173
174    while base_height >= height {
175        if peak_pos == peak_node_pos {
176            break;
177        }
178
179        let left_pos = peak_pos - (1 << peak_height);
180        if left_pos < peak_node_pos {
181            peak_pos -= 1;
182            base_pos -= 1;
183        } else {
184            peak_pos = left_pos;
185            base_pos -= 1 << base_height;
186        }
187
188        peak_height -= 1;
189        base_height -= 1;
190    }
191
192    Position::new(base_pos)
193}
194
195/// Inverse computation of destination_pos, with an analogous implementation involving walks down
196/// corresponding branches of both trees. Returns none if there is no corresponding node.
197pub(super) fn source_pos(base_node_pos: Position, height: u32) -> Option<Position> {
198    if pos_to_height(base_node_pos) < height {
199        // Nodes below the grafting height do not have a corresponding peak tree node.
200        return None;
201    }
202
203    let leading_zeros = (base_node_pos + 1).leading_zeros();
204    let mut base_pos = u64::MAX >> leading_zeros;
205    let mut peak_pos = u64::MAX >> (leading_zeros + height);
206    let mut base_height = base_pos.trailing_ones() - 1;
207    let mut peak_height = base_height - height;
208    base_pos -= 1;
209    peak_pos -= 1;
210
211    while base_pos != base_node_pos {
212        let left_pos = base_pos - (1 << base_height);
213        if left_pos < base_node_pos {
214            base_pos -= 1;
215            peak_pos -= 1;
216        } else {
217            base_pos = left_pos;
218            peak_pos -= 1 << peak_height;
219        }
220
221        base_height -= 1;
222        peak_height -= 1;
223    }
224
225    Some(Position::new(peak_pos))
226}
227
228impl<H: CHasher> HasherTrait<H::Digest> for Hasher<'_, H> {
229    type Inner = H;
230
231    /// Computes the digest of a leaf in the peak_tree of a grafted MMR.
232    ///
233    /// # Panics
234    ///
235    /// Panics if the grafted_digest was not previously loaded for the leaf via Self::load_grafted_digests.
236    fn leaf_digest(&mut self, pos: Position, element: &[u8]) -> H::Digest {
237        let grafted_digest = self.grafted_digests.get(&pos);
238        let Some(grafted_digest) = grafted_digest else {
239            panic!("missing grafted digest for leaf_pos {pos} - load_grafted_digests must be called first");
240        };
241
242        // We do not include position in the digest material here since the position information is
243        // already captured in the grafted_digest.
244        self.hasher.update_with_element(element);
245        self.hasher.update_with_digest(grafted_digest);
246
247        self.hasher.finalize()
248    }
249
250    fn fork(&self) -> impl HasherTrait<H::Digest> {
251        HasherFork::<H> {
252            hasher: StandardHasher::new(),
253            height: self.height,
254            grafted_digests: &self.grafted_digests,
255        }
256    }
257
258    fn node_digest(
259        &mut self,
260        pos: Position,
261        left_digest: &H::Digest,
262        right_digest: &H::Digest,
263    ) -> H::Digest {
264        self.hasher
265            .node_digest(self.destination_pos(pos), left_digest, right_digest)
266    }
267
268    fn root<'a>(
269        &mut self,
270        size: Position,
271        peak_digests: impl Iterator<Item = &'a H::Digest>,
272    ) -> H::Digest {
273        let dest_pos = self.destination_pos(size);
274        self.hasher.root(dest_pos, peak_digests)
275    }
276
277    fn digest(&mut self, data: &[u8]) -> H::Digest {
278        self.hasher.digest(data)
279    }
280
281    fn inner(&mut self) -> &mut H {
282        self.hasher.inner()
283    }
284}
285
286impl<H: CHasher> HasherTrait<H::Digest> for HasherFork<'_, H> {
287    type Inner = H;
288
289    /// Computes the digest of a leaf in the peak_tree of a grafted MMR.
290    ///
291    /// # Panics
292    ///
293    /// Panics if the grafted_digest was not previously loaded via Hasher::load_grafted_digests.
294    fn leaf_digest(&mut self, pos: Position, element: &[u8]) -> H::Digest {
295        let grafted_digest = self.grafted_digests.get(&pos);
296        let Some(grafted_digest) = grafted_digest else {
297            panic!("missing grafted digest for leaf_pos {pos} - load_grafted_digests must be called first");
298        };
299
300        // We do not include position in the digest material here since the position information is
301        // already captured in the base_node_digest.
302        self.hasher.update_with_element(element);
303        self.hasher.update_with_digest(grafted_digest);
304
305        self.hasher.finalize()
306    }
307
308    fn fork(&self) -> impl HasherTrait<H::Digest> {
309        HasherFork::<H> {
310            hasher: StandardHasher::<H>::new(),
311            height: self.height,
312            grafted_digests: self.grafted_digests,
313        }
314    }
315
316    fn node_digest(
317        &mut self,
318        pos: Position,
319        left_digest: &H::Digest,
320        right_digest: &H::Digest,
321    ) -> H::Digest {
322        self.hasher
323            .node_digest(destination_pos(pos, self.height), left_digest, right_digest)
324    }
325
326    fn root<'a>(
327        &mut self,
328        size: Position,
329        peak_digests: impl Iterator<Item = &'a H::Digest>,
330    ) -> H::Digest {
331        let dest_pos = destination_pos(size, self.height);
332        self.hasher.root(dest_pos, peak_digests)
333    }
334
335    fn digest(&mut self, data: &[u8]) -> H::Digest {
336        self.hasher.digest(data)
337    }
338
339    fn inner(&mut self) -> &mut H {
340        self.hasher.inner()
341    }
342}
343
344/// A [Hasher] implementation to use when verifying proofs over GraftedStorage.
345pub struct Verifier<'a, H: CHasher> {
346    hasher: StandardHasher<H>,
347    height: u32,
348
349    /// The required leaf elements from the peak tree that we are verifying.
350    elements: Vec<&'a [u8]>,
351
352    /// The location of the first element we are verifying
353    loc: Location,
354}
355
356impl<'a, H: CHasher> Verifier<'a, H> {
357    /// Create a new Verifier.
358    ///
359    /// # Panics
360    ///
361    /// Panics if `loc` is too large to be safely converted to a Position (> MAX_LOCATION).
362    pub fn new(height: u32, loc: Location, elements: Vec<&'a [u8]>) -> Self {
363        assert!(loc.is_valid(), "location {loc} > MAX_LOCATION");
364        Self {
365            hasher: StandardHasher::new(),
366            height,
367            elements,
368            loc,
369        }
370    }
371
372    pub const fn standard(&mut self) -> &mut StandardHasher<H> {
373        &mut self.hasher
374    }
375}
376
377impl<H: CHasher> HasherTrait<H::Digest> for Verifier<'_, H> {
378    type Inner = H;
379
380    fn leaf_digest(&mut self, pos: Position, element: &[u8]) -> H::Digest {
381        self.hasher.leaf_digest(pos, element)
382    }
383
384    fn fork(&self) -> impl HasherTrait<H::Digest> {
385        Verifier::<H> {
386            hasher: StandardHasher::new(),
387            height: self.height,
388            elements: self.elements.clone(),
389            loc: self.loc,
390        }
391    }
392
393    fn node_digest(
394        &mut self,
395        pos: Position,
396        left_digest: &H::Digest,
397        right_digest: &H::Digest,
398    ) -> H::Digest {
399        let digest = self.hasher.node_digest(pos, left_digest, right_digest);
400        if pos_to_height(pos) != self.height {
401            // If we're not at the grafting boundary we use the digest as-is.
402            return digest;
403        }
404
405        // This base tree node corresponds to a peak-tree leaf, so we need to perform the peak-tree
406        // leaf digest computation.
407        let source_pos = source_pos(pos, self.height);
408        let Some(source_pos) = source_pos else {
409            // malformed proof input
410            debug!(?pos, "no grafting source pos");
411            return digest;
412        };
413        let Ok(index) = Location::try_from(source_pos) else {
414            // malformed proof input
415            debug!(?source_pos, "grafting source pos is not a leaf");
416            return digest;
417        };
418        if index < self.loc {
419            // malformed proof input
420            debug!(
421                ?index,
422                ?self.loc,
423                "grafting index is negative"
424            );
425            return digest;
426        };
427        let index = index - self.loc;
428        if index >= self.elements.len() as u64 {
429            // malformed proof input
430            debug!(
431                ?index,
432                len = self.elements.len(),
433                "grafting index is out of bounds"
434            );
435            return digest;
436        }
437        self.hasher
438            .update_with_element(self.elements[*index as usize]);
439        self.hasher.update_with_digest(&digest);
440
441        self.hasher.finalize()
442    }
443
444    fn root<'a>(
445        &mut self,
446        size: Position,
447        peak_digests: impl Iterator<Item = &'a H::Digest>,
448    ) -> H::Digest {
449        self.hasher.root(size, peak_digests)
450    }
451
452    fn digest(&mut self, data: &[u8]) -> H::Digest {
453        self.hasher.digest(data)
454    }
455
456    fn inner(&mut self) -> &mut H {
457        self.hasher.inner()
458    }
459}
460
461/// A [Storage] implementation that makes grafted trees look like a single MMR for conveniently
462/// generating inclusion proofs.
463pub struct Storage<'a, H: CHasher, S1: StorageTrait<H::Digest>, S2: StorageTrait<H::Digest>> {
464    peak_tree: &'a S1,
465    base_mmr: &'a S2,
466    height: u32,
467
468    _marker: std::marker::PhantomData<H>,
469}
470
471impl<'a, H: CHasher, S1: StorageTrait<H::Digest>, S2: StorageTrait<H::Digest>>
472    Storage<'a, H, S1, S2>
473{
474    /// Creates a new grafted [Storage] instance.
475    pub const fn new(peak_tree: &'a S1, base_mmr: &'a S2, height: u32) -> Self {
476        Self {
477            peak_tree,
478            base_mmr,
479            height,
480            _marker: std::marker::PhantomData,
481        }
482    }
483
484    pub async fn root(&self, hasher: &mut StandardHasher<H>) -> Result<H::Digest, Error> {
485        let size = self.size();
486        let peak_futures = PeakIterator::new(size).map(|(peak_pos, _)| self.get_node(peak_pos));
487        let peaks = try_join_all(peak_futures).await?;
488        let unwrapped_peaks = peaks.iter().map(|p| {
489            p.as_ref()
490                .expect("peak should be non-none, are the trees unaligned?")
491        });
492        let digest = hasher.root(self.base_mmr.size(), unwrapped_peaks);
493
494        Ok(digest)
495    }
496}
497
498impl<H: CHasher, S1: StorageTrait<H::Digest>, S2: StorageTrait<H::Digest>> StorageTrait<H::Digest>
499    for Storage<'_, H, S1, S2>
500{
501    fn size(&self) -> Position {
502        self.base_mmr.size()
503    }
504
505    async fn get_node(&self, pos: Position) -> Result<Option<H::Digest>, Error> {
506        let height = pos_to_height(pos);
507        if height < self.height {
508            return self.base_mmr.get_node(pos).await;
509        }
510
511        let source_pos = source_pos(pos, self.height);
512        let Some(source_pos) = source_pos else {
513            return Ok(None);
514        };
515
516        self.peak_tree.get_node(source_pos).await
517    }
518}
519
520#[cfg(test)]
521mod tests {
522    use super::*;
523    use crate::mmr::{
524        mem::CleanMmr,
525        stability::{build_test_mmr, ROOTS},
526        verification, Position, StandardHasher,
527    };
528    use commonware_cryptography::{Hasher as CHasher, Sha256};
529    use commonware_macros::test_traced;
530    use commonware_runtime::{deterministic, Runner};
531    use commonware_utils::hex;
532
533    #[test]
534    fn test_leaf_digest_sha256() {
535        test_leaf_digest::<Sha256>();
536    }
537
538    #[test]
539    fn test_node_digest_sha256() {
540        test_node_digest::<Sha256>();
541    }
542
543    #[test]
544    fn test_root_sha256() {
545        test_root::<Sha256>();
546    }
547
548    fn test_digest<H: CHasher>(value: u8) -> H::Digest {
549        let mut hasher = H::new();
550        hasher.update(&[value]);
551        hasher.finalize()
552    }
553
554    fn test_leaf_digest<H: CHasher>() {
555        let executor = deterministic::Runner::default();
556        executor.start(|_| async move {
557            let mut mmr_hasher: StandardHasher<H> = StandardHasher::new();
558            // input hashes to use
559            let digest1 = test_digest::<H>(1);
560            let digest2 = test_digest::<H>(2);
561
562            let out = mmr_hasher.leaf_digest(Position::new(0), &digest1);
563            assert_ne!(out, test_digest::<H>(0), "hash should be non-zero");
564
565            let mut out2 = mmr_hasher.leaf_digest(Position::new(0), &digest1);
566            assert_eq!(out, out2, "hash should be re-computed consistently");
567
568            out2 = mmr_hasher.leaf_digest(Position::new(1), &digest1);
569            assert_ne!(out, out2, "hash should change with different pos");
570
571            out2 = mmr_hasher.leaf_digest(Position::new(0), &digest2);
572            assert_ne!(out, out2, "hash should change with different input digest");
573        });
574    }
575
576    fn test_node_digest<H: CHasher>() {
577        let mut mmr_hasher: StandardHasher<H> = StandardHasher::new();
578        // input hashes to use
579
580        let d1 = test_digest::<H>(1);
581        let d2 = test_digest::<H>(2);
582        let d3 = test_digest::<H>(3);
583
584        let out = mmr_hasher.node_digest(Position::new(0), &d1, &d2);
585        assert_ne!(out, test_digest::<H>(0), "hash should be non-zero");
586
587        let mut out2 = mmr_hasher.node_digest(Position::new(0), &d1, &d2);
588        assert_eq!(out, out2, "hash should be re-computed consistently");
589
590        out2 = mmr_hasher.node_digest(Position::new(1), &d1, &d2);
591        assert_ne!(out, out2, "hash should change with different pos");
592
593        out2 = mmr_hasher.node_digest(Position::new(0), &d3, &d2);
594        assert_ne!(
595            out, out2,
596            "hash should change with different first input hash"
597        );
598
599        out2 = mmr_hasher.node_digest(Position::new(0), &d1, &d3);
600        assert_ne!(
601            out, out2,
602            "hash should change with different second input hash"
603        );
604
605        out2 = mmr_hasher.node_digest(Position::new(0), &d2, &d1);
606        assert_ne!(
607            out, out2,
608            "hash should change when swapping order of inputs"
609        );
610    }
611
612    fn test_root<H: CHasher>() {
613        let mut mmr_hasher: StandardHasher<H> = StandardHasher::new();
614        // input digests to use
615        let d1 = test_digest::<H>(1);
616        let d2 = test_digest::<H>(2);
617        let d3 = test_digest::<H>(3);
618        let d4 = test_digest::<H>(4);
619
620        let empty_vec: Vec<H::Digest> = Vec::new();
621        let empty_out = mmr_hasher.root(Position::new(0), empty_vec.iter());
622        assert_ne!(
623            empty_out,
624            test_digest::<H>(0),
625            "root of empty MMR should be non-zero"
626        );
627
628        let digests = [d1, d2, d3, d4];
629        let out = mmr_hasher.root(Position::new(10), digests.iter());
630        assert_ne!(out, test_digest::<H>(0), "root should be non-zero");
631        assert_ne!(out, empty_out, "root should differ from empty MMR");
632
633        let mut out2 = mmr_hasher.root(Position::new(10), digests.iter());
634        assert_eq!(out, out2, "root should be computed consistently");
635
636        out2 = mmr_hasher.root(Position::new(11), digests.iter());
637        assert_ne!(out, out2, "root should change with different position");
638
639        let digests = [d1, d2, d4, d3];
640        out2 = mmr_hasher.root(Position::new(10), digests.iter());
641        assert_ne!(out, out2, "root should change with different digest order");
642
643        let digests = [d1, d2, d3];
644        out2 = mmr_hasher.root(Position::new(10), digests.iter());
645        assert_ne!(
646            out, out2,
647            "root should change with different number of hashes"
648        );
649    }
650
651    /// For a variety of grafting heights and node positions, check that destination_pos and
652    /// source_pos are inverse functions.
653    #[test]
654    fn test_hasher_dest_source_pos_conversion() {
655        for grafting_height in 1..10 {
656            for pos in 0..10000 {
657                let pos = Position::new(pos);
658                let dest_pos = destination_pos(pos, grafting_height);
659                let source_pos = source_pos(dest_pos, grafting_height).unwrap();
660                assert_eq!(pos, source_pos);
661            }
662        }
663    }
664
665    #[test]
666    fn test_hasher_source_dest_pos_conversion() {
667        for grafting_height in 1..10 {
668            for pos in 0..10000 {
669                let pos = Position::new(pos);
670                if pos_to_height(pos) < grafting_height {
671                    // Base tree nodes below the grafting height do not have a corresponding peak
672                    // tree node.
673                    assert!(source_pos(pos, grafting_height).is_none());
674                    continue;
675                }
676                let source_pos = source_pos(pos, grafting_height).unwrap();
677                let dest_pos = destination_pos(source_pos, grafting_height);
678                assert_eq!(pos, dest_pos);
679            }
680        }
681    }
682
683    #[test]
684    fn test_hasher_grafting() {
685        let executor = deterministic::Runner::default();
686        executor.start(|_| async move {
687            let mut standard: StandardHasher<Sha256> = StandardHasher::new();
688            let mmr = CleanMmr::new(&mut standard);
689            let base_mmr = build_test_mmr(&mut standard, mmr);
690            let root = *base_mmr.root();
691            let expected_root = ROOTS[199];
692            assert_eq!(&hex(&root), expected_root);
693
694            let mut hasher: Hasher<'_, Sha256> = Hasher::new(&mut standard, 0);
695            hasher
696                .load_grafted_digests(
697                    &(0..199).map(Location::new_unchecked).collect::<Vec<_>>(),
698                    &base_mmr,
699                )
700                .await
701                .unwrap();
702
703            {
704                // Build another MMR with the same elements only using a grafting hasher, using the
705                // previous mmr as the base.
706
707                // Since we're grafting 1-1, the destination position computation should be the
708                // identity function.
709                assert_eq!(hasher.destination_pos(Position::new(0)), Position::new(0));
710                let rand_leaf_pos = Position::new(1234234);
711                assert_eq!(hasher.destination_pos(rand_leaf_pos), rand_leaf_pos);
712
713                let mmr = CleanMmr::new(&mut hasher);
714                let peak_mmr = build_test_mmr(&mut hasher, mmr);
715                let root = *peak_mmr.root();
716                // Peak digest should differ from the base MMR.
717                assert!(hex(&root) != expected_root);
718            }
719
720            // Try grafting at a height of 1 instead of 0, which requires we double the # of leaves
721            // in the base tree to maintain the corresponding # of segments.
722            let base_mmr = build_test_mmr(&mut standard, base_mmr);
723            {
724                let mut hasher: Hasher<'_, Sha256> = Hasher::new(&mut standard, 1);
725                hasher
726                    .load_grafted_digests(
727                        &(0..199).map(Location::new_unchecked).collect::<Vec<_>>(),
728                        &base_mmr,
729                    )
730                    .await
731                    .unwrap();
732
733                // Confirm we're now grafting leaves to the positions of their immediate parent in
734                // an MMR.
735                assert_eq!(
736                    hasher.destination_pos(Position::try_from(Location::new_unchecked(0)).unwrap()),
737                    Position::new(2)
738                );
739                assert_eq!(
740                    hasher.destination_pos(Position::try_from(Location::new_unchecked(1)).unwrap()),
741                    Position::new(5)
742                );
743                assert_eq!(
744                    hasher.destination_pos(Position::try_from(Location::new_unchecked(2)).unwrap()),
745                    Position::new(9)
746                );
747                assert_eq!(
748                    hasher.destination_pos(Position::try_from(Location::new_unchecked(3)).unwrap()),
749                    Position::new(12)
750                );
751                assert_eq!(
752                    hasher.destination_pos(Position::try_from(Location::new_unchecked(4)).unwrap()),
753                    Position::new(17)
754                );
755
756                let mmr = CleanMmr::new(&mut hasher);
757                let peak_mmr = build_test_mmr(&mut hasher, mmr);
758                let root = *peak_mmr.root();
759                // Peak digest should differ from the base MMR.
760                assert!(hex(&root) != expected_root);
761            }
762
763            // Height 2 grafting destination computation check.
764            let hasher: Hasher<'_, Sha256> = Hasher::new(&mut standard, 2);
765            assert_eq!(
766                hasher.destination_pos(Position::try_from(Location::new_unchecked(0)).unwrap()),
767                Position::new(6)
768            );
769            assert_eq!(
770                hasher.destination_pos(Position::try_from(Location::new_unchecked(1)).unwrap()),
771                Position::new(13)
772            );
773
774            // Height 3 grafting destination computation check.
775            let hasher: Hasher<'_, Sha256> = Hasher::new(&mut standard, 3);
776            assert_eq!(
777                hasher.destination_pos(Position::try_from(Location::new_unchecked(0)).unwrap()),
778                Position::new(14)
779            );
780        });
781    }
782
783    /// Builds a small grafted MMR, then generates & verifies proofs over it.
784    #[test_traced]
785    fn test_hasher_grafted_storage() {
786        let executor = deterministic::Runner::default();
787        const GRAFTING_HEIGHT: u32 = 1;
788        executor.start(|_| async move {
789            let b1 = Sha256::fill(0x01);
790            let b2 = Sha256::fill(0x02);
791            let b3 = Sha256::fill(0x03);
792            let b4 = Sha256::fill(0x04);
793            let mut standard: StandardHasher<Sha256> = StandardHasher::new();
794
795            // Make a base MMR with 4 leaves.
796            let mut base_mmr = CleanMmr::new(&mut standard);
797            base_mmr.add(&mut standard, &b1);
798            base_mmr.add(&mut standard, &b2);
799            base_mmr.add(&mut standard, &b3);
800            base_mmr.add(&mut standard, &b4);
801
802            let p1 = Sha256::fill(0xF1);
803            let p2 = Sha256::fill(0xF2);
804
805            // Since we are using grafting height of 1, peak tree must have half the leaves of the
806            // base (2).
807            let mut peak_tree = CleanMmr::new(&mut standard);
808            {
809                let mut grafter = Hasher::new(&mut standard, GRAFTING_HEIGHT);
810                grafter
811                    .load_grafted_digests(
812                        &[Location::new_unchecked(0), Location::new_unchecked(1)],
813                        &base_mmr,
814                    )
815                    .await
816                    .unwrap();
817                peak_tree.add(&mut grafter, &p1);
818                peak_tree.add(&mut grafter, &p2);
819            }
820
821            let peak_root = *peak_tree.root();
822            let base_root = *base_mmr.root();
823            assert_ne!(peak_root, base_root);
824
825            {
826                let grafted_mmr = Storage::new(&peak_tree, &base_mmr, GRAFTING_HEIGHT);
827                assert_eq!(grafted_mmr.size(), base_mmr.size());
828
829                let grafted_storage_root = grafted_mmr.root(&mut standard).await.unwrap();
830                assert_ne!(grafted_storage_root, base_root);
831
832                // Grafted storage root uses the size of the base MMR in its digest, so it will
833                // differ than the peak tree root even though these particular trees would otherwise
834                // produce the same root.
835                assert_ne!(grafted_storage_root, peak_root);
836
837                // Confirm we can generate and verify an inclusion proofs for each of the 4 leafs of
838                // the grafted MMR.
839                {
840                    let loc = Location::new_unchecked(0);
841                    let proof = verification::range_proof(&grafted_mmr, loc..loc + 1)
842                        .await
843                        .unwrap();
844
845                    let mut verifier = Verifier::<Sha256>::new(
846                        GRAFTING_HEIGHT,
847                        Location::new_unchecked(0),
848                        vec![&p1],
849                    );
850                    assert!(proof.verify_element_inclusion(
851                        &mut verifier,
852                        &b1,
853                        loc,
854                        &grafted_storage_root
855                    ));
856
857                    let loc = Location::new_unchecked(1);
858                    let proof = verification::range_proof(&grafted_mmr, loc..loc + 1)
859                        .await
860                        .unwrap();
861                    assert!(proof.verify_element_inclusion(
862                        &mut verifier,
863                        &b2,
864                        loc,
865                        &grafted_storage_root
866                    ));
867
868                    let loc = Location::new_unchecked(2);
869                    let proof = verification::range_proof(&grafted_mmr, loc..loc + 1)
870                        .await
871                        .unwrap();
872                    let mut verifier = Verifier::<Sha256>::new(
873                        GRAFTING_HEIGHT,
874                        Location::new_unchecked(1),
875                        vec![&p2],
876                    );
877                    assert!(proof.verify_element_inclusion(
878                        &mut verifier,
879                        &b3,
880                        loc,
881                        &grafted_storage_root
882                    ));
883
884                    let loc = Location::new_unchecked(3);
885                    let proof = verification::range_proof(&grafted_mmr, loc..loc + 1)
886                        .await
887                        .unwrap();
888                    assert!(proof.verify_element_inclusion(
889                        &mut verifier,
890                        &b4,
891                        loc,
892                        &grafted_storage_root
893                    ));
894                }
895
896                // Confirm element inclusion proof verification fails for various manipulations of the input.
897                {
898                    // Valid proof of the last element.
899                    let loc = Location::new_unchecked(3);
900                    let proof = verification::range_proof(&grafted_mmr, loc..loc + 1)
901                        .await
902                        .unwrap();
903                    let mut verifier = Verifier::<Sha256>::new(
904                        GRAFTING_HEIGHT,
905                        Location::new_unchecked(1),
906                        vec![&p2],
907                    );
908                    assert!(proof.verify_element_inclusion(
909                        &mut verifier,
910                        &b4,
911                        loc,
912                        &grafted_storage_root
913                    ));
914
915                    // Proof should fail if we try to verify the wrong leaf element.
916                    assert!(!proof.verify_element_inclusion(
917                        &mut verifier,
918                        &b3,
919                        loc,
920                        &grafted_storage_root
921                    ));
922
923                    // Proof should fail if we use the wrong root.
924                    assert!(!proof.verify_element_inclusion(&mut verifier, &b4, loc, &peak_root));
925
926                    // Proof should fail if we use the wrong position
927                    assert!(!proof.verify_element_inclusion(
928                        &mut verifier,
929                        &b4,
930                        loc + 1,
931                        &grafted_storage_root
932                    ));
933
934                    // Proof should fail if we inject the wrong peak element into the verifier.
935                    let mut verifier = Verifier::<Sha256>::new(
936                        GRAFTING_HEIGHT,
937                        Location::new_unchecked(0),
938                        vec![&p1],
939                    );
940                    assert!(!proof.verify_element_inclusion(
941                        &mut verifier,
942                        &b4,
943                        loc,
944                        &grafted_storage_root
945                    ));
946
947                    // Proof should fail if we give the verifier the wrong peak tree leaf number.
948                    let mut verifier = Verifier::<Sha256>::new(
949                        GRAFTING_HEIGHT,
950                        Location::new_unchecked(2),
951                        vec![&p2],
952                    );
953                    assert!(!proof.verify_element_inclusion(
954                        &mut verifier,
955                        &b4,
956                        loc,
957                        &grafted_storage_root
958                    ));
959                }
960
961                // test range proving
962                {
963                    // Confirm we can prove the entire range.
964                    let proof = verification::range_proof(
965                        &grafted_mmr,
966                        Location::new_unchecked(0)..Location::new_unchecked(4),
967                    )
968                    .await
969                    .unwrap();
970                    let range = vec![&b1, &b2, &b3, &b4];
971                    let mut verifier = Verifier::<Sha256>::new(
972                        GRAFTING_HEIGHT,
973                        Location::new_unchecked(0),
974                        vec![&p1, &p2],
975                    );
976                    assert!(proof.verify_range_inclusion(
977                        &mut verifier,
978                        &range,
979                        Location::new_unchecked(0),
980                        &grafted_storage_root
981                    ));
982
983                    // Confirm same proof fails with shortened verifier range.
984                    let mut verifier = Verifier::<Sha256>::new(
985                        GRAFTING_HEIGHT,
986                        Location::new_unchecked(0),
987                        vec![&p1],
988                    );
989                    assert!(!proof.verify_range_inclusion(
990                        &mut verifier,
991                        &range,
992                        Location::new_unchecked(0),
993                        &grafted_storage_root
994                    ));
995                }
996            }
997
998            // Add one more leaf to our base MMR, which will not have any corresponding peak tree
999            // leaf since it will have no ancestors at or above the grafting height.
1000            let b5 = Sha256::fill(0x05);
1001            base_mmr.add(&mut standard, &b5);
1002
1003            let grafted_mmr = Storage::new(&peak_tree, &base_mmr, GRAFTING_HEIGHT);
1004            assert_eq!(grafted_mmr.size(), base_mmr.size());
1005
1006            // Confirm we can generate and verify inclusion proofs for the "orphaned" leaf as well
1007            // as an existing one.
1008            let grafted_storage_root = grafted_mmr.root(&mut standard).await.unwrap();
1009            let loc = Location::new_unchecked(0);
1010            let proof = verification::range_proof(&grafted_mmr, loc..loc + 1)
1011                .await
1012                .unwrap();
1013
1014            let mut verifier = Verifier::<Sha256>::new(GRAFTING_HEIGHT, loc, vec![&p1]);
1015            assert!(proof.verify_element_inclusion(&mut verifier, &b1, loc, &grafted_storage_root));
1016
1017            let mut verifier = Verifier::<Sha256>::new(GRAFTING_HEIGHT, loc, vec![]);
1018            let loc = Location::new_unchecked(4);
1019            let proof = verification::range_proof(&grafted_mmr, loc..loc + 1)
1020                .await
1021                .unwrap();
1022            assert!(proof.verify_element_inclusion(&mut verifier, &b5, loc, &grafted_storage_root));
1023        });
1024    }
1025}