Skip to main content

ab_merkle_tree/
balanced.rs

1use crate::{MerkleHasher, NewMerkleHasherForTreeSize, hash_pairs};
2#[cfg(feature = "alloc")]
3use alloc::boxed::Box;
4use core::iter::TrustedLen;
5use core::mem::MaybeUninit;
6use core::num::NonZero;
7
8/// Optimal number of pairs for hashing at once to saturate SIMD of hash functions like BLAKE3 on
9/// any hardware
10const BATCH_HASH_NUM_PAIRS: usize = 16;
11/// Number of leaves that corresponds to [`BATCH_HASH_NUM_PAIRS`]
12const BATCH_HASH_NUM_LEAVES: usize = BATCH_HASH_NUM_PAIRS * 2;
13
14const TREE_SIZE_WITHOUT_LEAVES<const N: usize>: usize = {
15    // Ensuring only supported `N` can be specified for [`BalancedMerkleTree`]
16    assert!(
17        N.is_power_of_two(),
18        "Balanced Merkle Tree must have a number of leaves that is a power of 2"
19    );
20
21    assert!(
22        N > 1,
23        "This Balanced Merkle Tree must have more than one leaf"
24    );
25
26    N - 1
27};
28/// Number of elements in a proof for a tree with `N` leaves
29pub const PROOF_ELEMENTS<const N: usize>: usize = N.ilog2() as usize;
30const ROOT_ONLY_LARGE_STACK_SIZE<const N: usize>: usize = {
31    // For small trees the large stack is not used, so the returned value does not matter as long as
32    // it compiles
33    if N < BATCH_HASH_NUM_LEAVES {
34        1
35    } else {
36        (N / BATCH_HASH_NUM_LEAVES).ilog2() as usize + 1
37    }
38};
39
40/// Merkle Tree variant that has hash-sized leaves and is fully balanced according to configured
41/// generic parameter.
42///
43/// This can be considered a general case of [`UnbalancedMerkleTree`]. The root and proofs are
44/// identical for both in case the number of leaves is a power of two. For the number of leaves that
45/// is a power of two [`UnbalancedMerkleTree`] is useful when a single proof needs to be generated
46/// and the number of leaves is very large (it can generate proofs with very little RAM usage
47/// compared to this version).
48///
49/// [`UnbalancedMerkleTree`]: crate::unbalanced::UnbalancedMerkleTree
50///
51/// This Merkle Tree implementation is best suited for use cases when proofs for all (or most) of
52/// the elements need to be generated and the whole tree easily fits into memory. It can also be
53/// constructed and proofs can be generated efficiently without heap allocations.
54///
55/// With all parameters of the tree known statically, it results in the most efficient version of
56/// the code being generated for a given set of parameters.
57///
58/// Inner nodes are hashed with `H`.
59#[derive(Debug)]
60pub struct BalancedMerkleTree<'a, const N: usize, H>
61where
62    H: MerkleHasher,
63{
64    leaves: &'a [[u8; H::OUT_LEN]; N],
65    // This tree doesn't include leaves because we have them in `leaves` field
66    tree: [[u8; H::OUT_LEN]; TREE_SIZE_WITHOUT_LEAVES::<N>],
67}
68
69impl<'a, const N: usize, H> BalancedMerkleTree<'a, N, H>
70where
71    [(); TREE_SIZE_WITHOUT_LEAVES::<N>]:,
72    H: MerkleHasher + const NewMerkleHasherForTreeSize,
73{
74    /// Create a new tree from a fixed set of elements.
75    ///
76    /// The data structure is statically allocated and might be too large to fit on the stack!
77    /// If that is the case, use `new_boxed()` method.
78    // TODO: Unlock on RISC-V, it started failing since https://github.com/nazar-pc/abundance/pull/551
79    //  for unknown reason
80    #[cfg_attr(
81        all(feature = "no-panic", not(target_arch = "riscv64")),
82        no_panic::no_panic
83    )]
84    pub fn new(leaves: &'a [[u8; H::OUT_LEN]; N]) -> Self {
85        // TODO: Technically redundant with the where-clause, which is not enforced in generic code
86        //  due to https://github.com/rust-lang/rust/issues/164019, remove once fixed
87        const { TREE_SIZE_WITHOUT_LEAVES::<N> };
88
89        let mut tree = [MaybeUninit::<[u8; H::OUT_LEN]>::uninit(); _];
90
91        Self::init_internal(leaves, &mut tree);
92
93        Self {
94            leaves,
95            // SAFETY: Statically guaranteed for all elements to be initialized
96            tree: unsafe { tree.transpose().assume_init() },
97        }
98    }
99
100    /// Like [`Self::new()`], but used pre-allocated memory for instantiation
101    // TODO: Unlock on RISC-V, it started failing since https://github.com/nazar-pc/abundance/pull/551
102    //  for unknown reason
103    #[cfg_attr(
104        all(feature = "no-panic", not(target_arch = "riscv64")),
105        no_panic::no_panic
106    )]
107    pub fn new_in<'b>(
108        instance: &'b mut MaybeUninit<Self>,
109        leaves: &'a [[u8; H::OUT_LEN]; N],
110    ) -> &'b mut Self {
111        // TODO: Technically redundant with the where-clause, which is not enforced in generic code
112        //  due to https://github.com/rust-lang/rust/issues/164019, remove once fixed
113        const { TREE_SIZE_WITHOUT_LEAVES::<N> };
114
115        let instance_ptr = instance.as_mut_ptr();
116        // SAFETY: Valid and correctly aligned non-null pointer
117        unsafe {
118            (&raw mut (*instance_ptr).leaves).write(leaves);
119        }
120        let tree = {
121            // SAFETY: Valid and correctly aligned non-null pointer
122            let tree_ptr = unsafe { &raw mut (*instance_ptr).tree };
123            // SAFETY: Allocated and correctly aligned uninitialized data
124            unsafe {
125                tree_ptr
126                    .cast::<[MaybeUninit<[u8; H::OUT_LEN]>; TREE_SIZE_WITHOUT_LEAVES::<N>]>()
127                    .as_mut_unchecked()
128            }
129        };
130
131        Self::init_internal(leaves, tree);
132
133        // SAFETY: Initialized field by field above
134        unsafe { instance.assume_init_mut() }
135    }
136
137    /// Like [`Self::new()`], but creates heap-allocated instance, avoiding excessive stack usage
138    /// for large trees
139    #[cfg(feature = "alloc")]
140    pub fn new_boxed(leaves: &'a [[u8; H::OUT_LEN]; N]) -> Box<Self> {
141        let mut instance = Box::<Self>::new_uninit();
142
143        Self::new_in(&mut instance, leaves);
144
145        // SAFETY: Initialized by constructor above
146        unsafe { instance.assume_init() }
147    }
148
149    // TODO: Unlock on RISC-V, it started failing since https://github.com/nazar-pc/abundance/pull/551
150    //  for unknown reason
151    #[cfg_attr(
152        all(feature = "no-panic", not(target_arch = "riscv64")),
153        no_panic::no_panic
154    )]
155    fn init_internal(
156        leaves: &[[u8; H::OUT_LEN]; N],
157        tree: &mut [MaybeUninit<[u8; H::OUT_LEN]>; TREE_SIZE_WITHOUT_LEAVES::<N>],
158    ) {
159        let hasher = const { H::new_for_tree_size(N as u64) };
160        let mut tree_hashes = tree.as_mut_slice();
161        let mut level_hashes = leaves.as_slice();
162
163        while level_hashes.len() > 1 {
164            let num_pairs = level_hashes.len() / 2;
165            let parent_hashes;
166            // SAFETY: The size of the tree is statically known to match the number of leaves and
167            // levels of hashes
168            (parent_hashes, tree_hashes) = unsafe { tree_hashes.split_at_mut_unchecked(num_pairs) };
169
170            let (parent_hashes_chunks, parent_hashes_remainder) =
171                parent_hashes.as_chunks_mut::<BATCH_HASH_NUM_PAIRS>();
172            if parent_hashes_remainder.is_empty() {
173                for (pairs, hashes) in level_hashes
174                    .as_chunks::<BATCH_HASH_NUM_LEAVES>()
175                    .0
176                    .iter()
177                    .zip(parent_hashes_chunks)
178                {
179                    // TODO: This memory copy is unfortunate, make hashing write into this memory
180                    //  directly once blake3 API improves
181                    *hashes = MaybeUninit::new(hash_pairs(&hasher, pairs)).into();
182                }
183            } else {
184                for (pair, parent_hash) in level_hashes
185                    .as_chunks::<2>()
186                    .0
187                    .iter()
188                    .zip(parent_hashes.iter_mut())
189                {
190                    parent_hash.write(hasher.hash_pair(pair));
191                }
192            }
193
194            // SAFETY: Just initialized
195            level_hashes = unsafe { parent_hashes.assume_init_ref() };
196        }
197    }
198
199    // TODO: Method that generates not only root, but also proof, like Unbalanced Merkle Tree
200    /// Compute Merkle Tree root.
201    ///
202    /// This is functionally equivalent to creating an instance first and calling [`Self::root()`]
203    /// method, but is faster and avoids heap allocation when root is the only thing that is needed.
204    #[inline]
205    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
206    pub fn compute_root_only(leaves: &[[u8; H::OUT_LEN]; N]) -> [u8; H::OUT_LEN] {
207        // TODO: Technically redundant with the where-clause, which is not enforced in generic code
208        //  due to https://github.com/rust-lang/rust/issues/164019, remove once fixed
209        const { TREE_SIZE_WITHOUT_LEAVES::<N> };
210
211        let hasher = const { H::new_for_tree_size(N as u64) };
212
213        // Special case for small trees below optimal SIMD width
214        match N {
215            2 => {
216                let [root] = hash_pairs(&hasher, leaves);
217
218                return root;
219            }
220            4 => {
221                let hashes = hash_pairs::<2, _, _>(&hasher, leaves);
222                let [root] = hash_pairs(&hasher, &hashes);
223
224                return root;
225            }
226            8 => {
227                let hashes = hash_pairs::<4, _, _>(&hasher, leaves);
228                let hashes = hash_pairs::<2, _, _>(&hasher, &hashes);
229                let [root] = hash_pairs(&hasher, &hashes);
230
231                return root;
232            }
233            16 => {
234                let hashes = hash_pairs::<8, _, _>(&hasher, leaves);
235                let hashes = hash_pairs::<4, _, _>(&hasher, &hashes);
236                let hashes = hash_pairs::<2, _, _>(&hasher, &hashes);
237                let [root] = hash_pairs(&hasher, &hashes);
238
239                return root;
240            }
241            _ => {
242                // We know this is the case
243                assert!(N >= BATCH_HASH_NUM_LEAVES);
244            }
245        }
246
247        // Stack of intermediate nodes per tree level. The logic here is the same as with a small
248        // tree above, except we store `BATCH_HASH_NUM_PAIRS` hashes per level and do a
249        // post-processing step at the very end to collapse them into a single root hash.
250        let mut stack =
251            [[[0u8; H::OUT_LEN]; BATCH_HASH_NUM_PAIRS]; ROOT_ONLY_LARGE_STACK_SIZE::<N>];
252
253        // This variable allows reusing and reducing stack usage instead of having a separate
254        // `current` variable
255        let mut parent_current = [[0u8; H::OUT_LEN]; BATCH_HASH_NUM_LEAVES];
256        for (num_chunks, chunk_leaves) in leaves
257            .as_chunks::<BATCH_HASH_NUM_LEAVES>()
258            .0
259            .iter()
260            .enumerate()
261        {
262            let current_half = &mut parent_current[BATCH_HASH_NUM_PAIRS..];
263
264            let current = hash_pairs::<BATCH_HASH_NUM_PAIRS, _, _>(&hasher, chunk_leaves);
265            current_half.copy_from_slice(&current);
266
267            // Every bit set to `1` corresponds to an active Merkle Tree level
268            let lowest_active_levels = num_chunks.trailing_ones() as usize;
269            for parent in &mut stack[..lowest_active_levels] {
270                let parent_half = &mut parent_current[..BATCH_HASH_NUM_PAIRS];
271                parent_half.copy_from_slice(parent);
272
273                let current = hash_pairs::<BATCH_HASH_NUM_PAIRS, _, _>(&hasher, &parent_current);
274
275                let current_half = &mut parent_current[BATCH_HASH_NUM_PAIRS..];
276                current_half.copy_from_slice(&current);
277            }
278
279            let current_half = &mut parent_current[BATCH_HASH_NUM_PAIRS..];
280
281            // Place freshly computed 8 hashes into the first inactive level
282            stack[lowest_active_levels].copy_from_slice(current_half);
283        }
284
285        let hashes = &stack[ROOT_ONLY_LARGE_STACK_SIZE::<N> - 1];
286        let hashes = hash_pairs::<{ BATCH_HASH_NUM_PAIRS / 2 }, _, _>(&hasher, hashes);
287        let hashes = hash_pairs::<{ BATCH_HASH_NUM_PAIRS / 4 }, _, _>(&hasher, &hashes);
288        let hashes = hash_pairs::<{ BATCH_HASH_NUM_PAIRS / 8 }, _, _>(&hasher, &hashes);
289        let [root] = hash_pairs::<{ BATCH_HASH_NUM_PAIRS / 16 }, _, _>(&hasher, &hashes);
290
291        root
292    }
293
294    /// Get the root of Merkle Tree
295    #[inline]
296    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
297    pub fn root(&self) -> [u8; H::OUT_LEN] {
298        *self
299            .tree
300            .last()
301            .or(self.leaves.last())
302            .expect("There is always at least one leaf hash; qed")
303    }
304
305    /// Iterator over proofs in the same order as provided leaf hashes
306    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
307    pub fn all_proofs(&self) -> ProofsIterator<'_, N, H> {
308        ProofsIterator {
309            leaves: self.leaves,
310            tree: &self.tree,
311            leaf_index: 0,
312        }
313    }
314
315    /// Verify previously generated proof
316    #[inline]
317    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
318    pub fn verify(
319        root: &[u8; H::OUT_LEN],
320        proof: &[[u8; H::OUT_LEN]; PROOF_ELEMENTS::<N>],
321        leaf_index: usize,
322        leaf: [u8; H::OUT_LEN],
323    ) -> bool {
324        // TODO: Technically redundant with the where-clause, which is not enforced in generic code
325        //  due to https://github.com/rust-lang/rust/issues/164019, remove once fixed
326        const { TREE_SIZE_WITHOUT_LEAVES::<N> };
327
328        if leaf_index >= N {
329            return false;
330        }
331
332        let hasher = const { H::new_for_tree_size(N as u64) };
333        let mut computed_root = leaf;
334
335        let mut position = leaf_index;
336        for hash in proof {
337            computed_root = if position.is_multiple_of(2) {
338                hasher.hash_pair(&[computed_root, *hash])
339            } else {
340                hasher.hash_pair(&[*hash, computed_root])
341            };
342
343            position /= 2;
344        }
345
346        root == &computed_root
347    }
348}
349
350/// Iterator over proofs for a balanced Merkle tree
351#[derive(Debug)]
352pub struct ProofsIterator<'a, const N: usize, H>
353where
354    H: MerkleHasher,
355{
356    leaves: &'a [[u8; H::OUT_LEN]; N],
357    tree: &'a [[u8; H::OUT_LEN]; TREE_SIZE_WITHOUT_LEAVES::<N>],
358    /// Index of the next leaf, the iterator is exhausted once it reaches `N`
359    leaf_index: usize,
360}
361
362impl<const N: usize, H> ProofsIterator<'_, N, H>
363where
364    H: MerkleHasher,
365{
366    #[inline(always)]
367    fn remaining(&self) -> usize {
368        N - self.leaf_index
369    }
370}
371
372impl<const N: usize, H> Iterator for ProofsIterator<'_, N, H>
373where
374    H: MerkleHasher,
375{
376    type Item = [[u8; H::OUT_LEN]; PROOF_ELEMENTS::<N>];
377
378    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
379    fn next(&mut self) -> Option<Self::Item> {
380        let index = self.leaf_index;
381
382        // The line below is a more efficient branchless version of this:
383        // let sibling_index = if index % 2 == 0 {
384        //     index + 1
385        // } else {
386        //     index - 1
387        // };
388        let sibling_index = index ^ 1;
389        // `N` is an even number, so the sibling is missing exactly when `index >= N`, meaning the
390        // iterator is exhausted
391        let sibling_hash = *self.leaves.get(sibling_index)?;
392        self.leaf_index += 1;
393
394        let mut proof = [MaybeUninit::<[u8; H::OUT_LEN]>::uninit(); _];
395        proof[0].write(sibling_hash);
396
397        // Part that is shared between left and right leaf proofs
398        let shared_proof = &mut proof[1..];
399
400        let mut tree_hashes = self.tree.as_slice();
401        let mut parent_position = index / 2;
402        let mut parent_level_size = N / 2;
403
404        for hash in shared_proof {
405            let parent_other_position = parent_position ^ 1;
406
407            let other_hash = tree_hashes.get(parent_other_position).expect(
408                "`parent_position` is within the current level of the tree, whose size is an \
409                    even number; qed",
410            );
411            hash.write(*other_hash);
412            tree_hashes = &tree_hashes[parent_level_size..];
413
414            parent_position /= 2;
415            parent_level_size /= 2;
416        }
417
418        // SAFETY: Just initialized
419        Some(unsafe { proof.transpose().assume_init() })
420    }
421
422    #[inline(always)]
423    fn size_hint(&self) -> (usize, Option<usize>) {
424        let remaining = self.remaining();
425        (remaining, Some(remaining))
426    }
427
428    #[inline(always)]
429    fn count(self) -> usize {
430        self.remaining()
431    }
432
433    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
434    fn last(mut self) -> Option<Self::Item> {
435        if self.leaf_index >= N {
436            return None;
437        }
438        self.leaf_index = N - 1;
439        self.next()
440    }
441
442    #[inline(always)]
443    fn advance_by(&mut self, n: usize) -> Result<(), NonZero<usize>> {
444        let advance = n.min(self.remaining());
445        self.leaf_index += advance;
446        NonZero::new(n - advance).map_or(Ok(()), Err)
447    }
448
449    #[inline(always)]
450    fn nth(&mut self, n: usize) -> Option<Self::Item> {
451        match self.advance_by(n) {
452            Ok(()) => self.next(),
453            Err(_) => None,
454        }
455    }
456}
457
458impl<const N: usize, H> ExactSizeIterator for ProofsIterator<'_, N, H>
459where
460    H: MerkleHasher,
461{
462    #[inline(always)]
463    fn len(&self) -> usize {
464        self.remaining()
465    }
466}
467
468// SAFETY: size_hint is always exact
469unsafe impl<const N: usize, H> TrustedLen for ProofsIterator<'_, N, H> where H: MerkleHasher {}