use alloy_primitives::{B256, Keccak256};
use bytes::Bytes;
use digest::{FixedOutput, FixedOutputReset, OutputSizeUser, Reset, Update};
use hybrid_array::{Array, sizes::U32};
use std::io::{self, Write};
use std::sync::LazyLock;
#[cfg(not(target_arch = "wasm32"))]
use rayon;
use super::constants::*;
const ZERO_TREE_LEVELS: usize = zero_tree_levels(DEFAULT_BODY_SIZE);
static ZERO_HASHES: LazyLock<[B256; ZERO_TREE_LEVELS]> = LazyLock::new(|| {
let mut hashes = [B256::ZERO; ZERO_TREE_LEVELS];
let mut hasher = Keccak256::new();
hasher.update([0u8; SEGMENT_PAIR_LENGTH]);
hashes[0] = B256::from_slice(hasher.finalize().as_slice());
for i in 1..ZERO_TREE_LEVELS {
let mut hasher = Keccak256::new();
hasher.update(hashes[i - 1].as_slice());
hasher.update(hashes[i - 1].as_slice());
hashes[i] = B256::from_slice(hasher.finalize().as_slice());
}
hashes
});
#[derive(Debug, Clone)]
pub struct Hasher<const BODY_SIZE: usize = DEFAULT_BODY_SIZE> {
span: u64,
prefix: Option<Vec<u8>>,
buffer: [u8; BODY_SIZE],
cursor: usize,
}
impl<const BODY_SIZE: usize> Default for Hasher<BODY_SIZE> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<const BODY_SIZE: usize> Hasher<BODY_SIZE> {
#[inline]
pub const fn new() -> Self {
Self {
span: 0,
prefix: None,
buffer: [0u8; BODY_SIZE],
cursor: 0,
}
}
#[inline]
pub const fn set_span(&mut self, span: u64) {
self.span = span;
}
#[inline(always)]
pub const fn span(&self) -> u64 {
self.span
}
#[inline]
pub fn prefix_with(&mut self, prefix: &[u8]) {
self.prefix = Some(prefix.to_vec());
}
#[inline(always)]
pub fn prefix(&self) -> &[u8] {
self.prefix.as_deref().unwrap_or(&[])
}
#[inline(always)]
pub const fn position(&self) -> usize {
self.cursor
}
#[inline(always)]
pub const fn len(&self) -> usize {
self.cursor
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
self.cursor == 0
}
#[inline]
pub fn update(&mut self, data: &[u8]) {
if data.is_empty() {
return;
}
let available_space = BODY_SIZE - self.cursor;
let bytes_to_copy = data.len().min(available_space);
if bytes_to_copy > 0 {
self.buffer[self.cursor..self.cursor + bytes_to_copy]
.copy_from_slice(&data[..bytes_to_copy]);
self.cursor += bytes_to_copy;
}
}
#[allow(clippy::should_implement_trait)] #[inline]
pub fn hash(&self, out: &mut [u8]) {
let hash = self.sum();
out.copy_from_slice(hash.as_slice());
}
#[inline]
#[must_use]
pub fn sum(&self) -> B256 {
self.finalize_with_prefix(self.hash_internal())
}
#[inline(always)]
fn is_all_zeros(data: &[u8]) -> bool {
data.iter().fold(0u8, |acc, &b| acc | b) == 0
}
#[inline(always)]
fn hash_internal(&self) -> B256 {
if self.cursor == 0 {
return ZERO_HASHES[ZERO_TREE_LEVELS - 1];
}
if Self::is_all_zeros(&self.buffer[..self.cursor]) {
return ZERO_HASHES[ZERO_TREE_LEVELS - 1];
}
let effective_size = self
.cursor
.next_power_of_two()
.max(SEGMENT_PAIR_LENGTH)
.min(BODY_SIZE);
#[cfg(not(target_arch = "wasm32"))]
let mut result = self.hash_subtree_parallel(&self.buffer[..effective_size], effective_size);
#[cfg(target_arch = "wasm32")]
let mut result =
self.hash_subtree_sequential(&self.buffer[..effective_size], effective_size);
let mut current_size = effective_size;
while current_size < BODY_SIZE {
let sibling_level = Self::zero_tree_level(current_size);
let mut hasher = Keccak256::new();
hasher.update(result.as_slice());
hasher.update(ZERO_HASHES[sibling_level].as_slice());
result = B256::from_slice(hasher.finalize().as_slice());
current_size *= 2;
}
result
}
#[cfg(not(target_arch = "wasm32"))]
#[inline(always)]
fn hash_subtree_parallel(&self, data: &[u8], length: usize) -> B256 {
debug_assert!(length.is_power_of_two());
debug_assert!(length >= SEGMENT_PAIR_LENGTH);
if length < BODY_SIZE {
return self.hash_subtree_sequential(data, length);
}
Self::hash_subtree_recursive_parallel_inner(data, length, self.cursor)
}
#[cfg(not(target_arch = "wasm32"))]
#[inline(always)]
fn hash_subtree_recursive_parallel_inner(data: &[u8], length: usize, cursor: usize) -> B256 {
debug_assert!(length.is_power_of_two());
debug_assert!(length >= SEGMENT_PAIR_LENGTH);
if length == SEGMENT_PAIR_LENGTH {
let mut hasher = Keccak256::new();
hasher.update(data);
return B256::from_slice(hasher.finalize().as_slice());
}
let half = length / 2;
let (left, right) = data.split_at(half);
let (left_hash, right_hash) = if half >= cursor {
let left_hash = Self::hash_subtree_recursive_parallel_inner(left, half, cursor);
let right_hash = ZERO_HASHES[Self::zero_tree_level(half)];
(left_hash, right_hash)
} else {
rayon::join(
|| Self::hash_subtree_recursive_parallel_inner(left, half, half),
|| Self::hash_subtree_recursive_parallel_inner(right, half, cursor - half),
)
};
let mut hasher = Keccak256::new();
hasher.update(left_hash.as_slice());
hasher.update(right_hash.as_slice());
B256::from_slice(hasher.finalize().as_slice())
}
#[inline(always)]
fn hash_subtree_sequential(&self, data: &[u8], length: usize) -> B256 {
debug_assert!(length.is_power_of_two());
debug_assert!(length >= SEGMENT_PAIR_LENGTH);
if length == SEGMENT_PAIR_LENGTH {
let mut hasher = Keccak256::new();
hasher.update(data);
return B256::from_slice(hasher.finalize().as_slice());
}
let half = length / 2;
let (left, right) = data.split_at(half);
let (left_hash, right_hash) = if half >= self.cursor {
let left_hash = self.hash_subtree_sequential(left, half);
let right_hash = ZERO_HASHES[Self::zero_tree_level(half)];
(left_hash, right_hash)
} else {
let left_hash = self.hash_subtree_sequential(left, half);
let right_hash = self.hash_subtree_sequential(right, half);
(left_hash, right_hash)
};
let mut hasher = Keccak256::new();
hasher.update(left_hash.as_slice());
hasher.update(right_hash.as_slice());
B256::from_slice(hasher.finalize().as_slice())
}
#[inline(always)]
const fn zero_tree_level(length: usize) -> usize {
length.trailing_zeros() as usize - 6
}
#[inline(always)]
fn finalize_with_prefix(&self, intermediate_hash: B256) -> B256 {
let mut hasher = Keccak256::new();
if let Some(prefix) = &self.prefix {
hasher.update(prefix);
}
hasher.update(self.span.to_le_bytes());
hasher.update(intermediate_hash.as_slice());
B256::from_slice(hasher.finalize().as_slice())
}
#[inline(always)]
const fn reset_internal(&mut self) {
self.cursor = 0;
self.span = 0;
}
#[inline]
#[must_use]
pub fn data(&self) -> Bytes {
if self.cursor == 0 {
return Bytes::new();
}
Bytes::copy_from_slice(&self.buffer[..self.cursor])
}
#[inline]
pub fn get_level_segments(&self, data: &[u8]) -> Vec<B256> {
let branches = branches_for_body_size(BODY_SIZE);
#[cfg(not(target_arch = "wasm32"))]
{
use rayon::prelude::*;
(0..branches)
.into_par_iter()
.map(|i| self.compute_segment_hash(data, i))
.collect()
}
#[cfg(target_arch = "wasm32")]
{
(0..branches)
.map(|i| self.compute_segment_hash(data, i))
.collect()
}
}
#[inline(always)]
fn compute_segment_hash(&self, data: &[u8], i: usize) -> B256 {
let start = i << SEGMENT_SIZE_LOG2; let mut hasher = Keccak256::new();
if start < data.len() {
let end = (start + SEGMENT_SIZE).min(data.len());
let segment_data = &data[start..end];
hasher.update(segment_data);
if segment_data.len() < SEGMENT_SIZE {
hasher.update(&[0u8; SEGMENT_SIZE][..(SEGMENT_SIZE - segment_data.len())]);
}
} else {
hasher.update([0u8; SEGMENT_SIZE]);
}
B256::from_slice(hasher.finalize().as_slice())
}
}
impl<const BODY_SIZE: usize> Write for Hasher<BODY_SIZE> {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let available = BODY_SIZE - self.cursor;
let to_write = buf.len().min(available);
if to_write > 0 {
self.buffer[self.cursor..self.cursor + to_write].copy_from_slice(&buf[..to_write]);
self.cursor += to_write;
}
Ok(to_write)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl<const BODY_SIZE: usize> OutputSizeUser for Hasher<BODY_SIZE> {
type OutputSize = U32;
}
impl<const BODY_SIZE: usize> Update for Hasher<BODY_SIZE> {
#[inline]
fn update(&mut self, data: &[u8]) {
self.update(data);
}
}
impl<const BODY_SIZE: usize> Reset for Hasher<BODY_SIZE> {
#[inline]
fn reset(&mut self) {
self.reset_internal();
}
}
impl<const BODY_SIZE: usize> FixedOutput for Hasher<BODY_SIZE> {
#[inline]
fn finalize_into(self, out: &mut Array<u8, Self::OutputSize>) {
let b256 = self.sum();
out.copy_from_slice(b256.as_slice());
}
}
impl<const BODY_SIZE: usize> FixedOutputReset for Hasher<BODY_SIZE> {
#[inline]
fn finalize_into_reset(&mut self, out: &mut Array<u8, Self::OutputSize>) {
let b256 = self.sum();
out.copy_from_slice(b256.as_slice());
self.reset_internal();
}
}
impl<const BODY_SIZE: usize> digest::HashMarker for Hasher<BODY_SIZE> {}
#[derive(Debug, Default, Clone)]
pub struct HasherFactory<const BODY_SIZE: usize = DEFAULT_BODY_SIZE>;
impl<const BODY_SIZE: usize> HasherFactory<BODY_SIZE> {
#[inline]
pub const fn new() -> Self {
Self
}
#[inline]
pub const fn create_hasher(&self) -> Hasher<BODY_SIZE> {
Hasher::new()
}
}