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            if num_leaves >= MAX_N {
69                return None;
70            }
71
72            let mut current = hash.into();
73
74            // Every bit set to `1` corresponds to an active Merkle Tree level
75            let lowest_active_levels = num_leaves.trailing_ones() as usize;
76            for item in stack.iter().take(lowest_active_levels) {
77                current = hash_pair(item, &current);
78            }
79
80            // Place the current hash at the first inactive level
81            stack[lowest_active_levels] = current;
82            num_leaves += 1;
83        }
84
85        if num_leaves == 0 {
86            // If no leaves were provided
87            return None;
88        }
89
90        let mut stack_bits = num_leaves;
91
92        {
93            let lowest_active_level = stack_bits.trailing_zeros() as usize;
94            // Reuse `stack[0]` for resulting value
95            // SAFETY: Active level must have been set successfully before, hence it exists
96            stack[0] = *unsafe { stack.get_unchecked(lowest_active_level) };
97            // Clear lowest active level
98            stack_bits &= !(1 << lowest_active_level);
99        }
100
101        // Hash remaining peaks (if any) of the potentially unbalanced tree together
102        loop {
103            let lowest_active_level = stack_bits.trailing_zeros() as usize;
104
105            if lowest_active_level == u64::BITS as usize {
106                break;
107            }
108
109            // Clear lowest active level for next iteration
110            stack_bits &= !(1 << lowest_active_level);
111
112            // SAFETY: Active level must have been set successfully before, hence it exists
113            let lowest_active_level_item = unsafe { stack.get_unchecked(lowest_active_level) };
114
115            stack[0] = hash_pair(lowest_active_level_item, &stack[0]);
116        }
117
118        Some(stack[0])
119    }
120
121    /// Compute Merkle Tree Root for an array of leaves.
122    ///
123    /// Similar to [`Self::compute_root_only()`], but for inputs that are small arrays where `MAX_N`
124    /// is the same as the array length.
125    #[inline(always)]
126    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
127    pub fn compute_root_only_array<const N: usize, Item>(leaves: &[Item; N]) -> [u8; OUT_LEN]
128    where
129        Item: Into<[u8; OUT_LEN]> + Copy,
130        [(); SUPPORTED_LEAVES_ARRAY_SIZE::<N>]:,
131    {
132        let maybe_root =
133            Self::compute_root_only::<{ USIZE_TO_U64::<N> }, _, _>(leaves.iter().copied());
134        // SAFETY: Fixed array length matching `MAX_N` always succeeds
135        unsafe { maybe_root.unwrap_unchecked() }
136    }
137
138    /// Compute Merkle Tree root and generate a proof for the `leaf` at `target_index`.
139    ///
140    /// Returns `Some((root, proof))` on success, `None` if index is outside of list of leaves.
141    ///
142    /// `MAX_N` generic constant defines the maximum number of elements supported and controls stack
143    /// usage.
144    #[inline]
145    #[cfg(feature = "alloc")]
146    pub fn compute_root_and_proof<'a, const MAX_N: u64, Item, Iter>(
147        leaves: Iter,
148        target_index: usize,
149    ) -> Option<([u8; OUT_LEN], Vec<[u8; OUT_LEN]>)>
150    where
151        Item: Into<[u8; OUT_LEN]>,
152        Iter: IntoIterator<Item = Item> + 'a,
153    {
154        // Stack of intermediate nodes per tree level
155        let mut stack = [[0u8; OUT_LEN]; _];
156        // SAFETY: Inner value is `MaybeUninit`
157        let mut proof =
158            unsafe { Box::<[MaybeUninit<[u8; OUT_LEN]>; _]>::new_uninit().assume_init() };
159
160        let (root, proof_length) = Self::compute_root_and_proof_inner::<MAX_N, _, _>(
161            leaves,
162            target_index,
163            &mut stack,
164            &mut proof,
165        )?;
166
167        let proof_capacity = proof.len();
168        let proof = Box::into_raw(proof);
169        // SAFETY: Points to correctly allocated memory where `proof_length` elements were
170        // initialized
171        let proof = unsafe {
172            Vec::from_raw_parts(proof.cast::<[u8; OUT_LEN]>(), proof_length, proof_capacity)
173        };
174
175        Some((root, proof))
176    }
177
178    /// Compute Merkle Tree root and generate a proof for the `leaf` at `target_index`.
179    ///
180    /// Returns `Some((root, proof))` on success, `None` if index is outside of list of leaves.
181    ///
182    /// `MAX_N` generic constant defines the maximum number of elements supported and controls stack
183    /// usage.
184    #[inline]
185    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
186    pub fn compute_root_and_proof_in<'a, 'proof, const MAX_N: u64, Item, Iter>(
187        leaves: Iter,
188        target_index: usize,
189        proof: &'proof mut [MaybeUninit<[u8; OUT_LEN]>; PROOF_ELEMENTS::<MAX_N>],
190    ) -> Option<([u8; OUT_LEN], &'proof mut [[u8; OUT_LEN]])>
191    where
192        Item: Into<[u8; OUT_LEN]>,
193        Iter: IntoIterator<Item = Item> + 'a,
194    {
195        // Stack of intermediate nodes per tree level
196        let mut stack = [[0u8; OUT_LEN]; _];
197
198        let (root, proof_length) = Self::compute_root_and_proof_inner::<MAX_N, _, _>(
199            leaves,
200            target_index,
201            &mut stack,
202            proof,
203        )?;
204        // SAFETY: Just correctly initialized `proof_length` elements
205        let proof = unsafe { proof.get_unchecked_mut(..proof_length).assume_init_mut() };
206
207        Some((root, proof))
208    }
209
210    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
211    fn compute_root_and_proof_inner<'a, const MAX_N: u64, Item, Iter>(
212        leaves: Iter,
213        target_index: usize,
214        stack: &mut [[u8; OUT_LEN]; STACK_SIZE::<MAX_N>],
215        proof: &mut [MaybeUninit<[u8; OUT_LEN]>; PROOF_ELEMENTS::<MAX_N>],
216    ) -> Option<([u8; OUT_LEN], usize)>
217    where
218        Item: Into<[u8; OUT_LEN]>,
219        Iter: IntoIterator<Item = Item> + 'a,
220    {
221        let mut proof_length = 0;
222        let mut num_leaves = 0u64;
223
224        let mut current_target_level = None;
225        let mut position = target_index;
226
227        for (current_index, hash) in leaves.into_iter().enumerate() {
228            // How many leaves were processed so far. Should have been `num_leaves == MAX_N`, but
229            // `>=` helps compiler with panic safety checks.
230            if num_leaves >= MAX_N {
231                return None;
232            }
233
234            let mut current = hash.into();
235
236            // Every bit set to `1` corresponds to an active Merkle Tree level
237            let lowest_active_levels = num_leaves.trailing_ones() as usize;
238
239            if current_index == target_index {
240                for item in stack.iter().take(lowest_active_levels) {
241                    // If at the target leaf index, need to collect the proof
242                    // SAFETY: Method signature guarantees upper bound of the proof length
243                    unsafe { proof.get_unchecked_mut(proof_length) }.write(*item);
244                    proof_length += 1;
245
246                    current = hash_pair(item, &current);
247
248                    // Move up the tree
249                    position /= 2;
250                }
251
252                current_target_level = Some(lowest_active_levels);
253            } else {
254                for (level, item) in stack.iter().enumerate().take(lowest_active_levels) {
255                    if current_target_level == Some(level) {
256                        // SAFETY: Method signature guarantees upper bound of the proof length
257                        unsafe { proof.get_unchecked_mut(proof_length) }.write(
258                            if position.is_multiple_of(2) {
259                                current
260                            } else {
261                                *item
262                            },
263                        );
264                        proof_length += 1;
265
266                        current_target_level = Some(level + 1);
267
268                        // Move up the tree
269                        position /= 2;
270                    }
271
272                    current = hash_pair(item, &current);
273                }
274            }
275
276            // Place the current hash at the first inactive level
277            stack[lowest_active_levels] = current;
278            num_leaves += 1;
279        }
280
281        // `active_levels` here contains the number of leaves after above loop
282        if target_index >= num_leaves as usize {
283            // If no leaves were provided
284            return None;
285        }
286
287        let Some(current_target_level) = current_target_level else {
288            // Index not found
289            return None;
290        };
291
292        let mut stack_bits = num_leaves;
293
294        {
295            let lowest_active_level = stack_bits.trailing_zeros() as usize;
296            // Reuse `stack[0]` for resulting value
297            // SAFETY: Active level must have been set successfully before, hence it exists
298            stack[0] = *unsafe { stack.get_unchecked(lowest_active_level) };
299            // Clear lowest active level
300            stack_bits &= !(1 << lowest_active_level);
301        }
302
303        // Hash remaining peaks (if any) of the potentially unbalanced tree together and collect
304        // proof hashes
305        let mut merged_peaks = false;
306        loop {
307            let lowest_active_level = stack_bits.trailing_zeros() as usize;
308
309            if lowest_active_level == u64::BITS as usize {
310                break;
311            }
312
313            // Clear lowest active level for next iteration
314            stack_bits &= !(1 << lowest_active_level);
315
316            // SAFETY: Active level must have been set successfully before, hence it exists
317            let lowest_active_level_item = unsafe { stack.get_unchecked(lowest_active_level) };
318
319            if lowest_active_level > current_target_level
320                || (lowest_active_level == current_target_level
321                    && !position.is_multiple_of(2)
322                    && !merged_peaks)
323            {
324                // SAFETY: Method signature guarantees upper bound of the proof length
325                unsafe { proof.get_unchecked_mut(proof_length) }.write(*lowest_active_level_item);
326                proof_length += 1;
327                merged_peaks = false;
328            } else if lowest_active_level == current_target_level {
329                // SAFETY: Method signature guarantees upper bound of the proof length
330                unsafe { proof.get_unchecked_mut(proof_length) }.write(stack[0]);
331                proof_length += 1;
332                merged_peaks = false;
333            } else {
334                // Not collecting proof because of the need to merge peaks of an unbalanced tree
335                merged_peaks = true;
336            }
337
338            // Collect the lowest peak into the proof
339            stack[0] = hash_pair(lowest_active_level_item, &stack[0]);
340
341            position /= 2;
342        }
343
344        Some((stack[0], proof_length))
345    }
346
347    /// Verify a Merkle proof for a leaf at the given index
348    #[inline]
349    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
350    pub fn verify(
351        root: &[u8; OUT_LEN],
352        proof: &[[u8; OUT_LEN]],
353        leaf_index: u64,
354        leaf: [u8; OUT_LEN],
355        num_leaves: u64,
356    ) -> bool {
357        if leaf_index >= num_leaves {
358            return false;
359        }
360
361        let mut current = leaf;
362        let mut position = leaf_index;
363        let mut proof_pos = 0;
364        let mut level_size = num_leaves;
365
366        // Rebuild the path to the root
367        while level_size > 1 {
368            let is_left = position.is_multiple_of(2);
369            let is_last = position == level_size - 1;
370
371            if is_left && !is_last {
372                // Left node with a right sibling
373                if proof_pos >= proof.len() {
374                    // Missing sibling
375                    return false;
376                }
377                current = hash_pair(&current, &proof[proof_pos]);
378                proof_pos += 1;
379            } else if !is_left {
380                // Right node with a left sibling
381                if proof_pos >= proof.len() {
382                    // Missing sibling
383                    return false;
384                }
385                current = hash_pair(&proof[proof_pos], &current);
386                proof_pos += 1;
387            } else {
388                // Last node, no sibling, keep current
389            }
390
391            position /= 2;
392            // Size of next level
393            level_size = level_size.div_ceil(2);
394        }
395
396        // Check if proof is fully used and matches root
397        proof_pos == proof.len() && current == *root
398    }
399}