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, ¤t);
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, ¤t);
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, ¤t);
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(¤t, &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], ¤t);
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}