Skip to main content

ab_merkle_tree/
balanced.rs

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