use alloc::vec;
use alloc::vec::Vec;
use crate::HASH_LEN;
#[cfg(feature = "parallel")]
const PARALLEL_LEAF_BATCH: u32 = 256;
pub(crate) fn treehash_root_and_auth_path<F>(
height: u32,
selected_leaf: u32,
leaf_hash: F,
mut parent: impl FnMut(u32, u64, [u8; HASH_LEN], [u8; HASH_LEN]) -> [u8; HASH_LEN],
) -> ([u8; HASH_LEN], Vec<[u8; HASH_LEN]>)
where
F: Fn(u32) -> [u8; HASH_LEN] + Sync,
{
debug_assert!(height < u32::BITS);
let leaf_count = 1u32 << height;
let mut stack: Vec<([u8; HASH_LEN], u32)> = Vec::with_capacity(height as usize + 1);
let mut auth_path = vec![[0u8; HASH_LEN]; height as usize];
let root = {
let mut fold = FoldLeafState {
selected_leaf,
stack: &mut stack,
auth_path: &mut auth_path,
};
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
let mut start = 0u32;
while start < leaf_count {
let end = start.saturating_add(PARALLEL_LEAF_BATCH).min(leaf_count);
let batch: Vec<[u8; HASH_LEN]> =
(start..end).into_par_iter().map(&leaf_hash).collect();
for (offset, node) in batch.into_iter().enumerate() {
fold_leaf(start + offset as u32, node, &mut fold, &mut parent);
}
start = end;
}
}
#[cfg(not(feature = "parallel"))]
{
for i in 0..leaf_count {
let node = leaf_hash(i);
fold_leaf(i, node, &mut fold, &mut parent);
}
}
match fold.stack.pop() {
Some((node, _)) => node,
None => [0u8; HASH_LEN],
}
};
(root, auth_path)
}
pub(crate) fn root_from_auth_path(
height: u32,
leaf_index: u32,
leaf: [u8; HASH_LEN],
auth_path: &[[u8; HASH_LEN]],
parent_hash: impl Fn(u32, u32, &[u8; HASH_LEN], &[u8; HASH_LEN]) -> [u8; HASH_LEN],
) -> Option<[u8; HASH_LEN]> {
if (auth_path.len() as u32) < height {
return None;
}
let mut node = leaf;
let mut index = leaf_index;
for level in 0..height {
let sibling = auth_path.get(level as usize)?;
let (left, right) = if index & 1 == 0 {
(&node, sibling)
} else {
(sibling, &node)
};
node = parent_hash(level + 1, index >> 1, left, right);
index >>= 1;
}
Some(node)
}
struct FoldLeafState<'a> {
selected_leaf: u32,
stack: &'a mut Vec<([u8; HASH_LEN], u32)>,
auth_path: &'a mut [[u8; HASH_LEN]],
}
#[inline]
fn fold_leaf(
i: u32,
mut node: [u8; HASH_LEN],
state: &mut FoldLeafState<'_>,
parent: &mut impl FnMut(u32, u64, [u8; HASH_LEN], [u8; HASH_LEN]) -> [u8; HASH_LEN],
) {
let mut node_h = 0u32;
record_auth_sibling(state.auth_path, state.selected_leaf, i, node_h, &node);
while state.stack.last().is_some_and(|(_, h)| *h == node_h) {
let Some((left, _)) = state.stack.pop() else {
break;
};
let next_h = node_h + 1;
let parent_index = u64::from(i >> next_h);
node = parent(next_h, parent_index, left, node);
node_h = next_h;
record_auth_sibling(state.auth_path, state.selected_leaf, i, node_h, &node);
}
state.stack.push((node, node_h));
}
#[inline]
fn record_auth_sibling(
auth_path: &mut [[u8; HASH_LEN]],
selected_leaf: u32,
rightmost_leaf: u32,
node_h: u32,
node: &[u8; HASH_LEN],
) {
if node_h >= auth_path.len() as u32 {
return;
}
let path_sibling = (selected_leaf >> node_h) ^ 1;
let node_index = rightmost_leaf >> node_h;
if path_sibling == node_index {
if let Some(slot) = auth_path.get_mut(node_h as usize) {
*slot = *node;
}
}
}
#[cfg(test)]
pub(crate) fn naive_tree_root_and_auth_path(
height: u32,
selected_leaf: u32,
mut leaf_hash: impl FnMut(u32) -> [u8; HASH_LEN],
mut parent: impl FnMut(u32, u64, [u8; HASH_LEN], [u8; HASH_LEN]) -> [u8; HASH_LEN],
) -> ([u8; HASH_LEN], Vec<[u8; HASH_LEN]>) {
let leaf_count = 1usize << height;
let mut level = Vec::with_capacity(leaf_count);
for i in 0..leaf_count as u32 {
level.push(leaf_hash(i));
}
let mut index = selected_leaf as usize;
let mut auth_path = Vec::with_capacity(height as usize);
for node_height in 1..=height {
let sibling = level
.get(index ^ 1)
.copied()
.expect("full power-of-two level has a sibling at index^1");
auth_path.push(sibling);
let mut parents = Vec::with_capacity(level.len() / 2);
for (parent_index, pair) in level.chunks_exact(2).enumerate() {
parents.push(parent(node_height, parent_index as u64, pair[0], pair[1]));
}
level = parents;
index >>= 1;
}
let root = level
.first()
.copied()
.expect("levels collapse to a single root node");
(root, auth_path)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::hash_node;
#[cfg(not(target_arch = "wasm32"))]
use proptest::prelude::*;
fn test_parent(
node_height: u32,
parent_index: u64,
left: [u8; HASH_LEN],
right: [u8; HASH_LEN],
) -> [u8; HASH_LEN] {
hash_node(&[
b"treehash-test-node".as_ref(),
&node_height.to_be_bytes(),
&parent_index.to_be_bytes(),
left.as_ref(),
right.as_ref(),
])
}
fn test_leaf(seed: u64, index: u32) -> [u8; HASH_LEN] {
hash_node(&[
b"treehash-test-leaf".as_ref(),
&seed.to_be_bytes(),
&index.to_be_bytes(),
])
}
#[cfg(not(target_arch = "wasm32"))]
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn streaming_matches_naive_small_trees(
height in 0u32..=8,
seed in any::<u64>(),
selected_raw in any::<u32>(),
) {
let leaf_count = 1u32 << height;
let selected_leaf = selected_raw % leaf_count;
let (stream_root, stream_auth) = treehash_root_and_auth_path(
height,
selected_leaf,
|i| test_leaf(seed, i),
test_parent,
);
let (naive_root, naive_auth) = naive_tree_root_and_auth_path(
height,
selected_leaf,
|i| test_leaf(seed, i),
test_parent,
);
prop_assert_eq!(stream_auth.len(), height as usize);
prop_assert_eq!(stream_root, naive_root);
prop_assert_eq!(stream_auth, naive_auth);
}
}
#[test]
fn height_zero_is_single_leaf() {
let (root, auth) = treehash_root_and_auth_path(0, 0, |_| [0xab; HASH_LEN], test_parent);
assert_eq!(root, [0xab; HASH_LEN]);
assert!(auth.is_empty());
}
#[test]
fn height_one_auth_is_sibling() {
let (root, auth) = treehash_root_and_auth_path(1, 0, |i| test_leaf(1, i), test_parent);
let (naive_root, naive_auth) =
naive_tree_root_and_auth_path(1, 0, |i| test_leaf(1, i), test_parent);
assert_eq!(root, naive_root);
assert_eq!(auth, naive_auth);
assert_eq!(auth.len(), 1);
assert_eq!(auth[0], test_leaf(1, 1));
}
fn test_parent_ref(
node_height: u32,
parent_index: u32,
left: &[u8; HASH_LEN],
right: &[u8; HASH_LEN],
) -> [u8; HASH_LEN] {
test_parent(node_height, u64::from(parent_index), *left, *right)
}
#[test]
fn root_from_auth_path_round_trips_with_treehash() {
let height = 4;
let selected_leaf = 6;
let (root, auth) =
treehash_root_and_auth_path(height, selected_leaf, |i| test_leaf(9, i), test_parent);
let leaf = test_leaf(9, selected_leaf);
let recomputed = root_from_auth_path(height, selected_leaf, leaf, &auth, test_parent_ref)
.expect("a full-length auth path must reconstruct the root");
assert_eq!(recomputed, root);
}
#[test]
fn root_from_auth_path_rejects_a_short_sibling_list() {
let height = 4;
let selected_leaf = 6;
let (_root, auth) =
treehash_root_and_auth_path(height, selected_leaf, |i| test_leaf(9, i), test_parent);
let leaf = test_leaf(9, selected_leaf);
let short_auth = &auth[..auth.len() - 1];
assert_eq!(
root_from_auth_path(height, selected_leaf, leaf, short_auth, test_parent_ref),
None,
"an auth path shorter than height must be rejected, not silently truncated"
);
}
}