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;
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
});
pub(super) fn hash_pairs(prefix: Option<&[u8]>, pairs: &[u8], out: &mut [[u8; 32]]) {
debug_assert_eq!(pairs.len(), out.len() * SEGMENT_PAIR_LENGTH);
let Some(p) = prefix else {
let inputs: Vec<&[u8]> = pairs.chunks_exact(SEGMENT_PAIR_LENGTH).collect();
return keccak_batch::keccak256_many_into(&inputs, out);
};
let entry = p.len() + SEGMENT_PAIR_LENGTH;
let mut scratch = vec![0u8; entry * out.len()];
for (slot, pair) in scratch
.chunks_exact_mut(entry)
.zip(pairs.chunks_exact(SEGMENT_PAIR_LENGTH))
{
slot[..p.len()].copy_from_slice(p);
slot[p.len()..].copy_from_slice(pair);
}
let inputs: Vec<&[u8]> = scratch.chunks_exact(entry).collect();
keccak_batch::keccak256_many_into(&inputs, out);
}
#[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]
pub fn with_prefix(prefix: &[u8]) -> Self {
let mut hasher = Self::new();
hasher.prefix_with(prefix);
hasher
}
#[inline(always)]
fn node_hasher(prefix: Option<&[u8]>) -> Keccak256 {
let mut hasher = Keccak256::new();
if let Some(p) = prefix {
hasher.update(p);
}
hasher
}
#[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 {
let prefix = self.prefix.as_deref();
let zero_hashes = self.zero_hashes(prefix);
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);
let mut result = self.hash_subtree(&self.buffer[..effective_size], &zero_hashes);
let mut current_size = effective_size;
while current_size < BODY_SIZE {
let sibling_level = Self::zero_tree_level(current_size);
let mut hasher = Self::node_hasher(prefix);
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
}
#[inline(always)]
fn zero_hashes(&self, prefix: Option<&[u8]>) -> [B256; ZERO_TREE_LEVELS] {
let Some(p) = prefix else {
return *ZERO_HASHES;
};
let mut hashes = [B256::ZERO; ZERO_TREE_LEVELS];
let mut hasher = Self::node_hasher(Some(p));
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 = Self::node_hasher(Some(p));
hasher.update(hashes[i - 1].as_slice());
hasher.update(hashes[i - 1].as_slice());
hashes[i] = B256::from_slice(hasher.finalize().as_slice());
}
hashes
}
fn hash_subtree(&self, data: &[u8], zero_hashes: &[B256; ZERO_TREE_LEVELS]) -> B256 {
debug_assert!(data.len().is_power_of_two());
debug_assert!(data.len() >= SEGMENT_PAIR_LENGTH);
let prefix = self.prefix.as_deref();
if data.len() == SEGMENT_PAIR_LENGTH {
let mut hasher = Self::node_hasher(prefix);
hasher.update(data);
return B256::from_slice(hasher.finalize().as_slice());
}
let pairs = data.len() / SEGMENT_PAIR_LENGTH;
let mut live = self.cursor.div_ceil(SEGMENT_PAIR_LENGTH).min(pairs);
let mut level = vec![[0u8; 32]; pairs];
hash_pairs(
prefix,
&data[..live * SEGMENT_PAIR_LENGTH],
&mut level[..live],
);
for slot in &mut level[live..] {
slot.copy_from_slice(zero_hashes[0].as_slice());
}
let mut next = vec![[0u8; 32]; pairs / 2];
let mut count = pairs;
let mut depth = 1;
while count > 1 {
count /= 2;
live = live.div_ceil(2);
hash_pairs(prefix, level[..live * 2].as_flattened(), &mut next[..live]);
for slot in &mut next[live..count] {
slot.copy_from_slice(zero_hashes[depth].as_slice());
}
std::mem::swap(&mut level, &mut next);
depth += 1;
}
B256::from(level[0])
}
#[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 = Self::node_hasher(self.prefix.as_deref());
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()
}
}