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 core::iter::TrustedLen;
6use core::mem;
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]],
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> {
68    /// Create a new tree from a fixed set of elements.
69    ///
70    /// The data structure is statically allocated and might be too large to fit on the stack!
71    /// If that is the case, use `new_boxed()` method.
72    // TODO: Unlock on RISC-V, it started failing since https://github.com/nazar-pc/abundance/pull/551
73    //  for unknown reason
74    #[cfg_attr(
75        all(feature = "no-panic", not(target_arch = "riscv64")),
76        no_panic::no_panic
77    )]
78    pub fn new(leaves: &'a [[u8; OUT_LEN]; N]) -> Self {
79        let mut tree = [MaybeUninit::<[u8; OUT_LEN]>::uninit(); _];
80
81        Self::init_internal(leaves, &mut tree);
82
83        Self {
84            leaves,
85            // SAFETY: Statically guaranteed for all elements to be initialized
86            tree: unsafe { tree.transpose().assume_init() },
87        }
88    }
89
90    /// Like [`Self::new()`], but used pre-allocated memory for instantiation
91    // TODO: Unlock on RISC-V, it started failing since https://github.com/nazar-pc/abundance/pull/551
92    //  for unknown reason
93    #[cfg_attr(
94        all(feature = "no-panic", not(target_arch = "riscv64")),
95        no_panic::no_panic
96    )]
97    pub fn new_in<'b>(
98        instance: &'b mut MaybeUninit<Self>,
99        leaves: &'a [[u8; OUT_LEN]; N],
100    ) -> &'b mut Self {
101        let instance_ptr = instance.as_mut_ptr();
102        // SAFETY: Valid and correctly aligned non-null pointer
103        unsafe {
104            (&raw mut (*instance_ptr).leaves).write(leaves);
105        }
106        let tree = {
107            // SAFETY: Valid and correctly aligned non-null pointer
108            let tree_ptr = unsafe { &raw mut (*instance_ptr).tree };
109            // SAFETY: Allocated and correctly aligned uninitialized data
110            unsafe {
111                tree_ptr
112                    .cast::<[MaybeUninit<[u8; OUT_LEN]>; TREE_SIZE_WITHOUT_LEAVES::<N>]>()
113                    .as_mut_unchecked()
114            }
115        };
116
117        Self::init_internal(leaves, tree);
118
119        // SAFETY: Initialized field by field above
120        unsafe { instance.assume_init_mut() }
121    }
122
123    /// Like [`Self::new()`], but creates heap-allocated instance, avoiding excessive stack usage
124    /// for large trees
125    #[cfg(feature = "alloc")]
126    pub fn new_boxed(leaves: &'a [[u8; OUT_LEN]; N]) -> Box<Self> {
127        let mut instance = Box::<Self>::new_uninit();
128
129        Self::new_in(&mut instance, leaves);
130
131        // SAFETY: Initialized by constructor above
132        unsafe { instance.assume_init() }
133    }
134
135    // TODO: Unlock on RISC-V, it started failing since https://github.com/nazar-pc/abundance/pull/551
136    //  for unknown reason
137    #[cfg_attr(
138        all(feature = "no-panic", not(target_arch = "riscv64")),
139        no_panic::no_panic
140    )]
141    fn init_internal(
142        leaves: &[[u8; OUT_LEN]; N],
143        tree: &mut [MaybeUninit<[u8; OUT_LEN]>; TREE_SIZE_WITHOUT_LEAVES::<N>],
144    ) {
145        let mut tree_hashes = tree.as_mut_slice();
146        let mut level_hashes = leaves.as_slice();
147
148        while level_hashes.len() > 1 {
149            let num_pairs = level_hashes.len() / 2;
150            let parent_hashes;
151            // SAFETY: The size of the tree is statically known to match the number of leaves and
152            // levels of hashes
153            (parent_hashes, tree_hashes) = unsafe { tree_hashes.split_at_mut_unchecked(num_pairs) };
154
155            if parent_hashes.len().is_multiple_of(BATCH_HASH_NUM_BLOCKS) {
156                // SAFETY: Just checked to be a multiple of chunk size and not empty
157                let parent_hashes_chunks =
158                    unsafe { parent_hashes.as_chunks_unchecked_mut::<BATCH_HASH_NUM_BLOCKS>() };
159                for (pairs, hashes) in level_hashes
160                    .as_chunks::<BATCH_HASH_NUM_LEAVES>()
161                    .0
162                    .iter()
163                    .zip(parent_hashes_chunks)
164                {
165                    // TODO: Would be nice to have a convenient method for this:
166                    //  https://github.com/rust-lang/rust/pull/145504#pullrequestreview-3788155275
167                    // SAFETY: Identical layout
168                    let hashes = unsafe {
169                        mem::transmute::<
170                            &mut [MaybeUninit<[u8; OUT_LEN]>; BATCH_HASH_NUM_BLOCKS],
171                            &mut MaybeUninit<[[u8; OUT_LEN]; BATCH_HASH_NUM_BLOCKS]>,
172                        >(hashes)
173                    };
174
175                    // TODO: This memory copy is unfortunate, make hashing write into this memory
176                    //  directly once blake3 API improves
177                    hashes.write(hash_pairs(pairs));
178                }
179            } else {
180                for (pair, parent_hash) in level_hashes
181                    .as_chunks()
182                    .0
183                    .iter()
184                    .zip(parent_hashes.iter_mut())
185                {
186                    // SAFETY: Same size and alignment
187                    let pair = unsafe {
188                        mem::transmute::<&[[u8; OUT_LEN]; BLOCK_LEN / OUT_LEN], &[u8; BLOCK_LEN]>(
189                            pair,
190                        )
191                    };
192                    parent_hash.write(hash_pair_block(pair));
193                }
194            }
195
196            // SAFETY: Just initialized
197            level_hashes = unsafe { parent_hashes.assume_init_ref() };
198        }
199    }
200
201    // TODO: Method that generates not only root, but also proof, like Unbalanced Merkle Tree
202    /// Compute Merkle Tree root.
203    ///
204    /// This is functionally equivalent to creating an instance first and calling [`Self::root()`]
205    /// method, but is faster and avoids heap allocation when root is the only thing that is needed.
206    #[inline]
207    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
208    pub fn compute_root_only(leaves: &[[u8; OUT_LEN]; N]) -> [u8; OUT_LEN] {
209        // Special case for small trees below optimal SIMD width
210        match N {
211            2 => {
212                let [root] = hash_pairs(leaves);
213
214                return root;
215            }
216            4 => {
217                let hashes = hash_pairs::<2, _>(leaves);
218                let [root] = hash_pairs(&hashes);
219
220                return root;
221            }
222            8 => {
223                let hashes = hash_pairs::<4, _>(leaves);
224                let hashes = hash_pairs::<2, _>(&hashes);
225                let [root] = hash_pairs(&hashes);
226
227                return root;
228            }
229            16 => {
230                let hashes = hash_pairs::<8, _>(leaves);
231                let hashes = hash_pairs::<4, _>(&hashes);
232                let hashes = hash_pairs::<2, _>(&hashes);
233                let [root] = hash_pairs(&hashes);
234
235                return root;
236            }
237            _ => {
238                // We know this is the case
239                assert!(N >= BATCH_HASH_NUM_LEAVES);
240            }
241        }
242
243        // Stack of intermediate nodes per tree level. The logic here is the same as with a small
244        // tree above, except we store `BATCH_HASH_NUM_BLOCKS` hashes per level and do a
245        // post-processing step at the very end to collapse them into a single root hash.
246        let mut stack = [[[0u8; OUT_LEN]; BATCH_HASH_NUM_BLOCKS]; ROOT_ONLY_LARGE_STACK_SIZE::<N>];
247
248        // This variable allows reusing and reducing stack usage instead of having a separate
249        // `current` variable
250        let mut parent_current = [[0u8; OUT_LEN]; BATCH_HASH_NUM_LEAVES];
251        for (num_chunks, chunk_leaves) in leaves
252            .as_chunks::<BATCH_HASH_NUM_LEAVES>()
253            .0
254            .iter()
255            .enumerate()
256        {
257            let current_half = &mut parent_current[BATCH_HASH_NUM_BLOCKS..];
258
259            let current = hash_pairs::<BATCH_HASH_NUM_BLOCKS, _>(chunk_leaves);
260            current_half.copy_from_slice(&current);
261
262            // Every bit set to `1` corresponds to an active Merkle Tree level
263            let lowest_active_levels = num_chunks.trailing_ones() as usize;
264            for parent in &mut stack[..lowest_active_levels] {
265                let parent_half = &mut parent_current[..BATCH_HASH_NUM_BLOCKS];
266                parent_half.copy_from_slice(parent);
267
268                let current = hash_pairs::<BATCH_HASH_NUM_BLOCKS, _>(&parent_current);
269
270                let current_half = &mut parent_current[BATCH_HASH_NUM_BLOCKS..];
271                current_half.copy_from_slice(&current);
272            }
273
274            let current_half = &mut parent_current[BATCH_HASH_NUM_BLOCKS..];
275
276            // Place freshly computed 8 hashes into the first inactive level
277            stack[lowest_active_levels].copy_from_slice(current_half);
278        }
279
280        let hashes = &stack[ROOT_ONLY_LARGE_STACK_SIZE::<N> - 1];
281        let hashes = hash_pairs::<{ BATCH_HASH_NUM_BLOCKS / 2 }, _>(hashes);
282        let hashes = hash_pairs::<{ BATCH_HASH_NUM_BLOCKS / 4 }, _>(&hashes);
283        let hashes = hash_pairs::<{ BATCH_HASH_NUM_BLOCKS / 8 }, _>(&hashes);
284        let [root] = hash_pairs::<{ BATCH_HASH_NUM_BLOCKS / 16 }, _>(&hashes);
285
286        root
287    }
288
289    /// Get the root of Merkle Tree
290    #[inline]
291    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
292    pub fn root(&self) -> [u8; OUT_LEN] {
293        *self
294            .tree
295            .last()
296            .or(self.leaves.last())
297            .expect("There is always at least one leaf hash; qed")
298    }
299
300    /// Iterator over proofs in the same order as provided leaf hashes
301    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
302    pub fn all_proofs(&self) -> ProofsIterator<'_, N> {
303        ProofsIterator {
304            leaves: self.leaves,
305            tree: &self.tree,
306            leaf_index: 0,
307            len: N,
308        }
309    }
310
311    /// Verify previously generated proof
312    #[inline]
313    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
314    pub fn verify(
315        root: &[u8; OUT_LEN],
316        proof: &[[u8; OUT_LEN]; PROOF_ELEMENTS::<N>],
317        leaf_index: usize,
318        leaf: [u8; OUT_LEN],
319    ) -> bool {
320        if leaf_index >= N {
321            return false;
322        }
323
324        let mut computed_root = leaf;
325
326        let mut position = leaf_index;
327        for hash in proof {
328            computed_root = if position.is_multiple_of(2) {
329                hash_pair(&computed_root, hash)
330            } else {
331                hash_pair(hash, &computed_root)
332            };
333
334            position /= 2;
335        }
336
337        root == &computed_root
338    }
339}
340
341/// Iterator over proofs for a balanced Merkle tree
342#[derive(Debug)]
343pub struct ProofsIterator<'a, const N: usize> {
344    leaves: &'a [[u8; OUT_LEN]],
345    tree: &'a [[u8; OUT_LEN]; TREE_SIZE_WITHOUT_LEAVES::<N>],
346    leaf_index: usize,
347    len: usize,
348}
349
350impl<'a, const N: usize> Iterator for ProofsIterator<'a, N> {
351    type Item = [[u8; OUT_LEN]; PROOF_ELEMENTS::<N>];
352
353    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
354    fn next(&mut self) -> Option<Self::Item> {
355        if self.len == 0 {
356            return None;
357        }
358        self.len -= 1;
359
360        let index = self.leaf_index;
361        self.leaf_index += 1;
362
363        // The line below is a more efficient branchless version of this:
364        // let sibling_index = if index % 2 == 0 {
365        //     index + 1
366        // } else {
367        //     index - 1
368        // };
369        let sibling_index = index ^ 1;
370        // SAFETY: `index < N` guaranteed by `len` tracking
371        let sibling_hash = *unsafe { self.leaves.get_unchecked(sibling_index) };
372
373        let mut proof = [MaybeUninit::<[u8; OUT_LEN]>::uninit(); _];
374        proof[0].write(sibling_hash);
375
376        // Part that is shared between left and right leaf proofs
377        let shared_proof = &mut proof[1..];
378
379        let mut tree_hashes = self.tree.as_slice();
380        let mut parent_position = index / 2;
381        let mut parent_level_size = N / 2;
382
383        for hash in shared_proof {
384            let parent_other_position = parent_position ^ 1;
385
386            // SAFETY: Statically guaranteed to be present by constructor
387            let other_hash = unsafe { tree_hashes.get_unchecked(parent_other_position) };
388            hash.write(*other_hash);
389            tree_hashes = &tree_hashes[parent_level_size..];
390
391            parent_position /= 2;
392            parent_level_size /= 2;
393        }
394
395        // SAFETY: Just initialized
396        Some(unsafe { proof.transpose().assume_init() })
397    }
398
399    #[inline(always)]
400    fn size_hint(&self) -> (usize, Option<usize>) {
401        (self.len, Some(self.len))
402    }
403
404    #[inline(always)]
405    fn count(self) -> usize {
406        self.len
407    }
408
409    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
410    fn last(mut self) -> Option<Self::Item> {
411        if self.len == 0 {
412            return None;
413        }
414        self.leaf_index = N - 1;
415        self.len = 1;
416        self.next()
417    }
418
419    #[inline(always)]
420    fn advance_by(&mut self, n: usize) -> Result<(), NonZero<usize>> {
421        let advance = n.min(self.len);
422        self.leaf_index += advance;
423        self.len -= advance;
424        NonZero::new(n - advance).map_or(Ok(()), Err)
425    }
426
427    #[inline(always)]
428    fn nth(&mut self, n: usize) -> Option<Self::Item> {
429        match self.advance_by(n) {
430            Ok(()) => self.next(),
431            Err(_) => None,
432        }
433    }
434}
435
436impl<'a, const N: usize> ExactSizeIterator for ProofsIterator<'a, N> {
437    #[inline(always)]
438    fn len(&self) -> usize {
439        self.len
440    }
441}
442
443// SAFETY: size_hint is always exact
444unsafe impl<'a, const N: usize> TrustedLen for ProofsIterator<'a, N> {}