Skip to main content

ab_merkle_tree/
unbalanced.rs

1use crate::hash_pair;
2use ab_blake3::OUT_LEN;
3#[cfg(feature = "alloc")]
4use alloc::boxed::Box;
5#[cfg(feature = "alloc")]
6use alloc::vec::Vec;
7use core::mem::MaybeUninit;
8
9/// Number of elements in a proof for a tree with `MAX_N` leaves
10pub const PROOF_ELEMENTS<const MAX_N: u64>: usize = MAX_N.next_power_of_two().ilog2() as usize;
11const STACK_SIZE<const MAX_N: u64>: usize = MAX_N.next_power_of_two().ilog2() as usize + 1;
12const SUPPORTED_LEAVES_ARRAY_SIZE<const N: usize>: usize = {
13    assert!(N != 0, "Number of leaves must be greater than 0");
14    0
15};
16const USIZE_TO_U64<const N: usize>: u64 = N as u64;
17
18/// Merkle Tree variant that has pre-hashed leaves with arbitrary number of elements.
19///
20/// This can be considered a general case of [`BalancedMerkleTree`]. The root and proofs are
21/// identical for both in case the number of leaves is a power of two. [`BalancedMerkleTree`] is
22/// more efficient and should be preferred when possible.
23///
24/// [`BalancedMerkleTree`]: crate::balanced::BalancedMerkleTree
25///
26/// The unbalanced tree is not padded, it is created the same way Merkle Mountain Range would be:
27/// ```text
28///               Root
29///         /--------------\
30///        H3              H4
31///    /-------\         /----\
32///   H0       H1       H2     \
33///  /  \     /  \     /  \     \
34/// L0  L1   L2  L3   L4  L5    L6
35/// ```
36#[derive(Debug)]
37pub struct UnbalancedMerkleTree;
38
39// TODO: Optimize by implementing SIMD-accelerated hashing of multiple values:
40//  https://github.com/BLAKE3-team/BLAKE3/issues/478
41// TODO: Experiment with replacing a single pass with splitting the whole data set with a sequence
42//  of power-of-two elements that can be processed in parallel and do it recursively until a single
43//  element is left. This can be done for both root creation and proof generation.
44impl UnbalancedMerkleTree {
45    /// Compute Merkle Tree Root.
46    ///
47    /// `MAX_N` generic constant defines the maximum number of elements supported and controls stack
48    /// usage.
49    ///
50    /// Returns `None` for an empty list of leaves, or if the number of leaves is larger than
51    /// `MAX_N`.
52    #[inline]
53    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
54    pub fn compute_root_only<'a, const MAX_N: u64, Item, Iter>(
55        leaves: Iter,
56    ) -> Option<[u8; OUT_LEN]>
57    where
58        Item: Into<[u8; OUT_LEN]>,
59        Iter: IntoIterator<Item = Item> + 'a,
60    {
61        // Stack of intermediate nodes per tree level
62        let mut stack = [[0u8; OUT_LEN]; STACK_SIZE::<MAX_N>];
63        let mut num_leaves = 0u64;
64
65        for hash in leaves {
66            // How many leaves were processed so far. Should have been `num_leaves == MAX_N`, but
67            // `>=` helps compiler with panic safety checks.
68            // TODO: Use `==` once https://github.com/rust-lang/rust/issues/162834 is resolved
69            if num_leaves >= MAX_N {
70                return None;
71            }
72
73            let mut current = hash.into();
74
75            // Every bit set to `1` corresponds to an active Merkle Tree level
76            let lowest_active_levels = num_leaves.trailing_ones() as usize;
77            for item in stack.iter().take(lowest_active_levels) {
78                current = hash_pair(item, &current);
79            }
80
81            // Place the current hash at the first inactive level
82            stack[lowest_active_levels] = current;
83            num_leaves += 1;
84        }
85
86        if num_leaves == 0 {
87            // If no leaves were provided
88            return None;
89        }
90
91        let mut stack_bits = num_leaves;
92
93        {
94            let lowest_active_level = stack_bits.trailing_zeros() as usize;
95            // Reuse `stack[0]` for resulting value
96            // SAFETY: Active level must have been set successfully before, hence it exists
97            stack[0] = *unsafe { stack.get_unchecked(lowest_active_level) };
98            // Clear lowest active level
99            stack_bits &= !(1 << lowest_active_level);
100        }
101
102        // Hash remaining peaks (if any) of the potentially unbalanced tree together
103        loop {
104            let lowest_active_level = stack_bits.trailing_zeros() as usize;
105
106            if lowest_active_level == u64::BITS as usize {
107                break;
108            }
109
110            // Clear lowest active level for next iteration
111            stack_bits &= !(1 << lowest_active_level);
112
113            // SAFETY: Active level must have been set successfully before, hence it exists
114            let lowest_active_level_item = unsafe { stack.get_unchecked(lowest_active_level) };
115
116            stack[0] = hash_pair(lowest_active_level_item, &stack[0]);
117        }
118
119        Some(stack[0])
120    }
121
122    /// Compute Merkle Tree Root for an array of leaves.
123    ///
124    /// Similar to [`Self::compute_root_only()`], but for inputs that are small arrays where `MAX_N`
125    /// is the same as the array length.
126    #[inline(always)]
127    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
128    pub fn compute_root_only_array<const N: usize, Item>(leaves: &[Item; N]) -> [u8; OUT_LEN]
129    where
130        Item: Into<[u8; OUT_LEN]> + Copy,
131        [(); SUPPORTED_LEAVES_ARRAY_SIZE::<N>]:,
132    {
133        // TODO: Technically redundant with the where-clause, which is not enforced in generic code
134        //  due to https://github.com/rust-lang/rust/issues/164019, remove once fixed
135        const { SUPPORTED_LEAVES_ARRAY_SIZE::<N> };
136
137        // Iterating over indices rather than with `leaves.iter().copied()` allows the compiler to
138        // prove that the root always exists on aarch64 and riscv64 too
139        let leaves = (0..N).map(|index| *leaves.get(index).expect("Index is within array; qed"));
140        let maybe_root = Self::compute_root_only::<{ USIZE_TO_U64::<N> }, _, _>(leaves);
141        maybe_root.expect("Non-empty array with length matching `MAX_N` always has a root; qed")
142    }
143
144    /// Compute Merkle Tree root and generate a proof for the `leaf` at `target_index`.
145    ///
146    /// Returns `Some((root, proof))` on success, `None` if index is outside of list of leaves.
147    ///
148    /// `MAX_N` generic constant defines the maximum number of elements supported and controls stack
149    /// usage.
150    #[inline]
151    #[cfg(feature = "alloc")]
152    pub fn compute_root_and_proof<'a, const MAX_N: u64, Item, Iter>(
153        leaves: Iter,
154        target_index: usize,
155    ) -> Option<([u8; OUT_LEN], Vec<[u8; OUT_LEN]>)>
156    where
157        Item: Into<[u8; OUT_LEN]>,
158        Iter: IntoIterator<Item = Item> + 'a,
159    {
160        // Stack of intermediate nodes per tree level
161        let mut stack = [[0u8; OUT_LEN]; _];
162        // SAFETY: Inner value is `MaybeUninit`
163        let mut proof =
164            unsafe { Box::<[MaybeUninit<[u8; OUT_LEN]>; _]>::new_uninit().assume_init() };
165
166        let (root, proof_length) = Self::compute_root_and_proof_inner::<MAX_N, _, _>(
167            leaves,
168            target_index,
169            &mut stack,
170            &mut proof,
171        )?;
172
173        let proof_capacity = proof.len();
174        let proof = Box::into_raw(proof);
175        // SAFETY: Points to correctly allocated memory where `proof_length` elements were
176        // initialized
177        let proof = unsafe {
178            Vec::from_raw_parts(proof.cast::<[u8; OUT_LEN]>(), proof_length, proof_capacity)
179        };
180
181        Some((root, proof))
182    }
183
184    /// Compute Merkle Tree root and generate a proof for the `leaf` at `target_index`.
185    ///
186    /// Returns `Some((root, proof))` on success, `None` if index is outside of list of leaves.
187    ///
188    /// `MAX_N` generic constant defines the maximum number of elements supported and controls stack
189    /// usage.
190    #[inline]
191    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
192    pub fn compute_root_and_proof_in<'a, 'proof, const MAX_N: u64, Item, Iter>(
193        leaves: Iter,
194        target_index: usize,
195        proof: &'proof mut [MaybeUninit<[u8; OUT_LEN]>; PROOF_ELEMENTS::<MAX_N>],
196    ) -> Option<([u8; OUT_LEN], &'proof mut [[u8; OUT_LEN]])>
197    where
198        Item: Into<[u8; OUT_LEN]>,
199        Iter: IntoIterator<Item = Item> + 'a,
200    {
201        // Stack of intermediate nodes per tree level
202        let mut stack = [[0u8; OUT_LEN]; _];
203
204        let (root, proof_length) = Self::compute_root_and_proof_inner::<MAX_N, _, _>(
205            leaves,
206            target_index,
207            &mut stack,
208            proof,
209        )?;
210        // SAFETY: Just correctly initialized `proof_length` elements
211        let proof = unsafe { proof.get_unchecked_mut(..proof_length).assume_init_mut() };
212
213        Some((root, proof))
214    }
215
216    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
217    fn compute_root_and_proof_inner<'a, const MAX_N: u64, Item, Iter>(
218        leaves: Iter,
219        target_index: usize,
220        stack: &mut [[u8; OUT_LEN]; STACK_SIZE::<MAX_N>],
221        proof: &mut [MaybeUninit<[u8; OUT_LEN]>; PROOF_ELEMENTS::<MAX_N>],
222    ) -> Option<([u8; OUT_LEN], usize)>
223    where
224        Item: Into<[u8; OUT_LEN]>,
225        Iter: IntoIterator<Item = Item> + 'a,
226    {
227        let mut proof_length = 0;
228        let mut num_leaves = 0u64;
229
230        let mut current_target_level = None;
231        let mut position = target_index;
232
233        for (current_index, hash) in leaves.into_iter().enumerate() {
234            // How many leaves were processed so far. Should have been `num_leaves == MAX_N`, but
235            // `>=` helps compiler with panic safety checks.
236            // TODO: Use `==` once https://github.com/rust-lang/rust/issues/162834 is resolved
237            if num_leaves >= MAX_N {
238                return None;
239            }
240
241            let mut current = hash.into();
242
243            // Every bit set to `1` corresponds to an active Merkle Tree level
244            let lowest_active_levels = num_leaves.trailing_ones() as usize;
245
246            if current_index == target_index {
247                for item in stack.iter().take(lowest_active_levels) {
248                    // If at the target leaf index, need to collect the proof
249                    // SAFETY: Method signature guarantees upper bound of the proof length
250                    unsafe { proof.get_unchecked_mut(proof_length) }.write(*item);
251                    proof_length += 1;
252
253                    current = hash_pair(item, &current);
254
255                    // Move up the tree
256                    position /= 2;
257                }
258
259                current_target_level = Some(lowest_active_levels);
260            } else {
261                for (level, item) in stack.iter().enumerate().take(lowest_active_levels) {
262                    if current_target_level == Some(level) {
263                        // SAFETY: Method signature guarantees upper bound of the proof length
264                        unsafe { proof.get_unchecked_mut(proof_length) }.write(
265                            if position.is_multiple_of(2) {
266                                current
267                            } else {
268                                *item
269                            },
270                        );
271                        proof_length += 1;
272
273                        current_target_level = Some(level + 1);
274
275                        // Move up the tree
276                        position /= 2;
277                    }
278
279                    current = hash_pair(item, &current);
280                }
281            }
282
283            // Place the current hash at the first inactive level
284            stack[lowest_active_levels] = current;
285            num_leaves += 1;
286        }
287
288        // `active_levels` here contains the number of leaves after above loop
289        if target_index >= num_leaves as usize {
290            // If no leaves were provided
291            return None;
292        }
293
294        let Some(current_target_level) = current_target_level else {
295            // Index not found
296            return None;
297        };
298
299        let mut stack_bits = num_leaves;
300
301        {
302            let lowest_active_level = stack_bits.trailing_zeros() as usize;
303            // Reuse `stack[0]` for resulting value
304            // SAFETY: Active level must have been set successfully before, hence it exists
305            stack[0] = *unsafe { stack.get_unchecked(lowest_active_level) };
306            // Clear lowest active level
307            stack_bits &= !(1 << lowest_active_level);
308        }
309
310        // Hash remaining peaks (if any) of the potentially unbalanced tree together and collect
311        // proof hashes
312        let mut merged_peaks = false;
313        loop {
314            let lowest_active_level = stack_bits.trailing_zeros() as usize;
315
316            if lowest_active_level == u64::BITS as usize {
317                break;
318            }
319
320            // Clear lowest active level for next iteration
321            stack_bits &= !(1 << lowest_active_level);
322
323            // SAFETY: Active level must have been set successfully before, hence it exists
324            let lowest_active_level_item = unsafe { stack.get_unchecked(lowest_active_level) };
325
326            if lowest_active_level > current_target_level
327                || (lowest_active_level == current_target_level
328                    && !position.is_multiple_of(2)
329                    && !merged_peaks)
330            {
331                // SAFETY: Method signature guarantees upper bound of the proof length
332                unsafe { proof.get_unchecked_mut(proof_length) }.write(*lowest_active_level_item);
333                proof_length += 1;
334                merged_peaks = false;
335            } else if lowest_active_level == current_target_level {
336                // SAFETY: Method signature guarantees upper bound of the proof length
337                unsafe { proof.get_unchecked_mut(proof_length) }.write(stack[0]);
338                proof_length += 1;
339                merged_peaks = false;
340            } else {
341                // Not collecting proof because of the need to merge peaks of an unbalanced tree
342                merged_peaks = true;
343            }
344
345            // Collect the lowest peak into the proof
346            stack[0] = hash_pair(lowest_active_level_item, &stack[0]);
347
348            position /= 2;
349        }
350
351        Some((stack[0], proof_length))
352    }
353
354    /// Verify a Merkle proof for a leaf at the given index
355    #[inline]
356    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
357    pub fn verify(
358        root: &[u8; OUT_LEN],
359        proof: &[[u8; OUT_LEN]],
360        leaf_index: u64,
361        leaf: [u8; OUT_LEN],
362        num_leaves: u64,
363    ) -> bool {
364        if leaf_index >= num_leaves {
365            return false;
366        }
367
368        let mut current = leaf;
369        let mut position = leaf_index;
370        let mut proof_pos = 0;
371        let mut level_size = num_leaves;
372
373        // Rebuild the path to the root
374        while level_size > 1 {
375            let is_left = position.is_multiple_of(2);
376            let is_last = position == level_size - 1;
377
378            if is_left && !is_last {
379                // Left node with a right sibling
380                if proof_pos >= proof.len() {
381                    // Missing sibling
382                    return false;
383                }
384                current = hash_pair(&current, &proof[proof_pos]);
385                proof_pos += 1;
386            } else if !is_left {
387                // Right node with a left sibling
388                if proof_pos >= proof.len() {
389                    // Missing sibling
390                    return false;
391                }
392                current = hash_pair(&proof[proof_pos], &current);
393                proof_pos += 1;
394            } else {
395                // Last node, no sibling, keep current
396            }
397
398            position /= 2;
399            // Size of next level
400            level_size = level_size.div_ceil(2);
401        }
402
403        // Check if proof is fully used and matches root
404        proof_pos == proof.len() && current == *root
405    }
406}