Skip to main content

nectar_primitives/bmt/
hasher.rs

1//! Binary Merkle Tree hasher implementation
2//!
3//! This module provides an implementation of a BMT hasher that uses Keccak256
4//! for computing content-addressed hashes of arbitrary data.
5
6use alloy_primitives::{B256, Keccak256};
7use bytes::Bytes;
8use digest::{FixedOutput, FixedOutputReset, OutputSizeUser, Reset, Update};
9use hybrid_array::{Array, sizes::U32};
10use std::io::{self, Write};
11use std::sync::LazyLock;
12
13use super::constants::*;
14
15/// Number of zero tree levels for the default body size.
16const ZERO_TREE_LEVELS: usize = zero_tree_levels(DEFAULT_BODY_SIZE);
17
18/// Pre-computed zero hashes for the default body size tree.
19static ZERO_HASHES: LazyLock<[B256; ZERO_TREE_LEVELS]> = LazyLock::new(|| {
20    let mut hashes = [B256::ZERO; ZERO_TREE_LEVELS];
21
22    // Level 0: hash of 64 zero bytes (one segment pair)
23    let mut hasher = Keccak256::new();
24    hasher.update([0u8; SEGMENT_PAIR_LENGTH]);
25    hashes[0] = B256::from_slice(hasher.finalize().as_slice());
26
27    // Each subsequent level: hash of two copies of previous level's hash
28    for i in 1..ZERO_TREE_LEVELS {
29        let mut hasher = Keccak256::new();
30        hasher.update(hashes[i - 1].as_slice());
31        hasher.update(hashes[i - 1].as_slice());
32        hashes[i] = B256::from_slice(hasher.finalize().as_slice());
33    }
34
35    hashes
36});
37
38/// Hash consecutive 64-byte sibling pairs of a tree level, batched across SIMD
39/// lanes.
40///
41/// `pairs` is the flat byte view of the level (`out.len()` pairs of two
42/// 32-byte nodes); each output is `keccak(prefix || left || right)`.
43pub(super) fn hash_pairs(prefix: Option<&[u8]>, pairs: &[u8], out: &mut [[u8; 32]]) {
44    debug_assert_eq!(pairs.len(), out.len() * SEGMENT_PAIR_LENGTH);
45
46    let Some(p) = prefix else {
47        let inputs: Vec<&[u8]> = pairs.chunks_exact(SEGMENT_PAIR_LENGTH).collect();
48        return keccak_batch::keccak256_many_into(&inputs, out);
49    };
50
51    let entry = p.len() + SEGMENT_PAIR_LENGTH;
52    let mut scratch = vec![0u8; entry * out.len()];
53    for (slot, pair) in scratch
54        .chunks_exact_mut(entry)
55        .zip(pairs.chunks_exact(SEGMENT_PAIR_LENGTH))
56    {
57        slot[..p.len()].copy_from_slice(p);
58        slot[p.len()..].copy_from_slice(pair);
59    }
60    let inputs: Vec<&[u8]> = scratch.chunks_exact(entry).collect();
61    keccak_batch::keccak256_many_into(&inputs, out);
62}
63
64/// BMT hasher with configurable body size.
65#[derive(Debug, Clone)]
66pub struct Hasher<const BODY_SIZE: usize = DEFAULT_BODY_SIZE> {
67    span: u64,
68    prefix: Option<Vec<u8>>,
69    buffer: [u8; BODY_SIZE],
70    cursor: usize,
71}
72
73impl<const BODY_SIZE: usize> Default for Hasher<BODY_SIZE> {
74    #[inline]
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80impl<const BODY_SIZE: usize> Hasher<BODY_SIZE> {
81    /// Create a new BMT hasher.
82    #[inline]
83    pub const fn new() -> Self {
84        Self {
85            span: 0,
86            prefix: None,
87            buffer: [0u8; BODY_SIZE],
88            cursor: 0,
89        }
90    }
91
92    /// Set the span of data to be hashed
93    #[inline]
94    pub const fn set_span(&mut self, span: u64) {
95        self.span = span;
96    }
97
98    /// Get the current span
99    #[inline(always)]
100    pub const fn span(&self) -> u64 {
101        self.span
102    }
103
104    /// Add a prefix to the hash calculation.
105    ///
106    /// The prefix is applied to *every* Keccak256 invocation in the tree (leaf
107    /// sections, internal nodes and the final span wrap), matching bee's
108    /// `swarm.NewPrefixHasher` semantics where `Reset()` re-writes the prefix as
109    /// the first bytes before each node hash. This makes the resulting root
110    /// byte-identical to bee's `transformedAddress`.
111    #[inline]
112    pub fn prefix_with(&mut self, prefix: &[u8]) {
113        self.prefix = Some(prefix.to_vec());
114    }
115
116    /// Create a new BMT hasher pre-configured with an anchor `prefix`.
117    ///
118    /// Equivalent to [`Hasher::new`] followed by [`Hasher::prefix_with`]. The
119    /// prefix is mixed into every node hash (see [`Hasher::prefix_with`]), so
120    /// the produced root matches bee's anchor-keyed `transformedAddress`.
121    #[inline]
122    pub fn with_prefix(prefix: &[u8]) -> Self {
123        let mut hasher = Self::new();
124        hasher.prefix_with(prefix);
125        hasher
126    }
127
128    /// Construct a fresh Keccak256, seeded with the prefix when one is set.
129    ///
130    /// Every node in the tree is hashed as `keccak(prefix || data)`; this helper
131    /// centralises that so the prefix can never be forgotten at an individual
132    /// hash site.
133    #[inline(always)]
134    fn node_hasher(prefix: Option<&[u8]>) -> Keccak256 {
135        let mut hasher = Keccak256::new();
136        if let Some(p) = prefix {
137            hasher.update(p);
138        }
139        hasher
140    }
141
142    /// Get the current prefix
143    #[inline(always)]
144    pub fn prefix(&self) -> &[u8] {
145        self.prefix.as_deref().unwrap_or(&[])
146    }
147
148    /// Get the current cursor position
149    #[inline(always)]
150    pub const fn position(&self) -> usize {
151        self.cursor
152    }
153
154    /// Get the amount of data currently in the buffer
155    #[inline(always)]
156    pub const fn len(&self) -> usize {
157        self.cursor
158    }
159
160    /// Check if the buffer is empty
161    #[inline(always)]
162    pub const fn is_empty(&self) -> bool {
163        self.cursor == 0
164    }
165
166    /// Update the hasher with more data (non-destructive)
167    #[inline]
168    pub fn update(&mut self, data: &[u8]) {
169        if data.is_empty() {
170            return;
171        }
172
173        // Calculate how much data we can actually copy
174        let available_space = BODY_SIZE - self.cursor;
175        let bytes_to_copy = data.len().min(available_space);
176
177        if bytes_to_copy > 0 {
178            // Copy data at cursor position
179            self.buffer[self.cursor..self.cursor + bytes_to_copy]
180                .copy_from_slice(&data[..bytes_to_copy]);
181
182            // Update cursor position
183            self.cursor += bytes_to_copy;
184        }
185    }
186
187    /// Compute the BMT hash and write to output buffer.
188    #[allow(clippy::should_implement_trait)] // BMT hash, not std::hash::Hash
189    #[inline]
190    pub fn hash(&self, out: &mut [u8]) {
191        let hash = self.sum();
192        out.copy_from_slice(hash.as_slice());
193    }
194
195    /// Compute the BMT hash and return the result (non-destructive)
196    #[inline]
197    #[must_use]
198    pub fn sum(&self) -> B256 {
199        self.finalize_with_prefix(self.hash_internal())
200    }
201
202    /// Check if a byte slice is all zeros.
203    /// Uses chunk-based iteration which LLVM optimizes to SIMD on supported platforms.
204    #[inline(always)]
205    fn is_all_zeros(data: &[u8]) -> bool {
206        // Fold with bitwise OR - any non-zero byte makes the result non-zero
207        // LLVM vectorizes this pattern into efficient SIMD code
208        data.iter().fold(0u8, |acc, &b| acc | b) == 0
209    }
210
211    /// Hash data using a binary merkle tree (internal implementation)
212    ///
213    /// This uses an optimized algorithm that:
214    /// 1. Finds the smallest power-of-2 subtree containing all data
215    /// 2. Hashes only that subtree
216    /// 3. Iteratively combines with pre-computed zero hashes to reach the root
217    #[inline(always)]
218    fn hash_internal(&self) -> B256 {
219        let prefix = self.prefix.as_deref();
220
221        // Zero fast paths rely on the precomputed prefix-independent ZERO_HASHES
222        // table, which is only valid for plain (unprefixed) hashing. Under a
223        // non-empty prefix every zero section hashes as keccak(prefix||zeros),
224        // so we must compute the zero subtrees with the prefix instead.
225        let zero_hashes = self.zero_hashes(prefix);
226
227        // Special case: no data means entire tree is zeros
228        if self.cursor == 0 {
229            return zero_hashes[ZERO_TREE_LEVELS - 1];
230        }
231
232        // Fast path: if all data is zeros, return the zero tree root.
233        // Valid for both plain and prefixed hashing because `zero_hashes`
234        // already accounts for the prefix.
235        if Self::is_all_zeros(&self.buffer[..self.cursor]) {
236            return zero_hashes[ZERO_TREE_LEVELS - 1];
237        }
238
239        // Find the smallest power-of-2 subtree that contains all data
240        let effective_size = self
241            .cursor
242            .next_power_of_two()
243            .max(SEGMENT_PAIR_LENGTH)
244            .min(BODY_SIZE);
245
246        // Hash only the effective subtree (which contains all actual data).
247        let mut result = self.hash_subtree(&self.buffer[..effective_size], &zero_hashes);
248
249        // Roll up with zero hashes until we reach the full tree size
250        let mut current_size = effective_size;
251        while current_size < BODY_SIZE {
252            // The current result is a left child, combine with zero hash for right sibling
253            let sibling_level = Self::zero_tree_level(current_size);
254            let mut hasher = Self::node_hasher(prefix);
255            hasher.update(result.as_slice());
256            hasher.update(zero_hashes[sibling_level].as_slice());
257            result = B256::from_slice(hasher.finalize().as_slice());
258            current_size *= 2;
259        }
260
261        result
262    }
263
264    /// Return the per-level zero subtree hashes for the current prefix.
265    ///
266    /// With no prefix this returns the shared precomputed [`ZERO_HASHES`]. With
267    /// a prefix set it computes the table on demand so that each level is
268    /// `keccak(prefix || left || right)` (the level-0 entry being
269    /// `keccak(prefix || 64 zero bytes)`), matching bee's per-prefix
270    /// `zerohashes`.
271    #[inline(always)]
272    fn zero_hashes(&self, prefix: Option<&[u8]>) -> [B256; ZERO_TREE_LEVELS] {
273        let Some(p) = prefix else {
274            return *ZERO_HASHES;
275        };
276
277        let mut hashes = [B256::ZERO; ZERO_TREE_LEVELS];
278
279        let mut hasher = Self::node_hasher(Some(p));
280        hasher.update([0u8; SEGMENT_PAIR_LENGTH]);
281        hashes[0] = B256::from_slice(hasher.finalize().as_slice());
282
283        for i in 1..ZERO_TREE_LEVELS {
284            let mut hasher = Self::node_hasher(Some(p));
285            hasher.update(hashes[i - 1].as_slice());
286            hasher.update(hashes[i - 1].as_slice());
287            hashes[i] = B256::from_slice(hasher.finalize().as_slice());
288        }
289
290        hashes
291    }
292
293    /// Hash a power-of-two subtree (>= 64 bytes) level by level, batching each
294    /// level's sibling pairs across SIMD lanes.
295    ///
296    /// Only pairs that overlap live data (the cursor) cost a Keccak; everything
297    /// past them is an all-zero subtree taken from `zero_hashes`, which the
298    /// caller has already made prefix-aware.
299    fn hash_subtree(&self, data: &[u8], zero_hashes: &[B256; ZERO_TREE_LEVELS]) -> B256 {
300        debug_assert!(data.len().is_power_of_two());
301        debug_assert!(data.len() >= SEGMENT_PAIR_LENGTH);
302
303        let prefix = self.prefix.as_deref();
304
305        if data.len() == SEGMENT_PAIR_LENGTH {
306            let mut hasher = Self::node_hasher(prefix);
307            hasher.update(data);
308            return B256::from_slice(hasher.finalize().as_slice());
309        }
310
311        // Level 0: pairs that overlap live data (the caller guarantees
312        // cursor > 0); the rest of the level is zero pairs.
313        let pairs = data.len() / SEGMENT_PAIR_LENGTH;
314        let mut live = self.cursor.div_ceil(SEGMENT_PAIR_LENGTH).min(pairs);
315        let mut level = vec![[0u8; 32]; pairs];
316        hash_pairs(
317            prefix,
318            &data[..live * SEGMENT_PAIR_LENGTH],
319            &mut level[..live],
320        );
321        for slot in &mut level[live..] {
322            slot.copy_from_slice(zero_hashes[0].as_slice());
323        }
324
325        // Combine sibling digests level by level until one root remains.
326        let mut next = vec![[0u8; 32]; pairs / 2];
327        let mut count = pairs;
328        let mut depth = 1;
329        while count > 1 {
330            count /= 2;
331            live = live.div_ceil(2);
332            hash_pairs(prefix, level[..live * 2].as_flattened(), &mut next[..live]);
333            for slot in &mut next[live..count] {
334                slot.copy_from_slice(zero_hashes[depth].as_slice());
335            }
336            std::mem::swap(&mut level, &mut next);
337            depth += 1;
338        }
339
340        B256::from(level[0])
341    }
342
343    /// Calculate the zero-tree level for a given subtree length.
344    /// Length must be a power of 2 between 64 and 4096.
345    #[inline(always)]
346    const fn zero_tree_level(length: usize) -> usize {
347        // length = 64 * 2^level, so level = log2(length) - log2(64) = log2(length) - 6
348        length.trailing_zeros() as usize - 6
349    }
350
351    /// Finalize with span and optional prefix
352    #[inline(always)]
353    fn finalize_with_prefix(&self, intermediate_hash: B256) -> B256 {
354        let mut hasher = Keccak256::new();
355
356        // Add prefix if present
357        if let Some(prefix) = &self.prefix {
358            hasher.update(prefix);
359        }
360
361        // Add span as little-endian bytes
362        hasher.update(self.span.to_le_bytes());
363
364        // Add the intermediate hash
365        hasher.update(intermediate_hash.as_slice());
366
367        // Finalize to get the result
368        B256::from_slice(hasher.finalize().as_slice())
369    }
370
371    /// Reset the hasher's internal state
372    #[inline(always)]
373    const fn reset_internal(&mut self) {
374        // Simply reset cursor - no need to clear the buffer as it will be overwritten
375        self.cursor = 0;
376        self.span = 0;
377        // Don't reset prefix, as it's considered a configuration parameter
378    }
379
380    /// Get the current data as Bytes (immutable reference)
381    #[inline]
382    #[must_use]
383    pub fn data(&self) -> Bytes {
384        if self.cursor == 0 {
385            return Bytes::new();
386        }
387
388        // Create Bytes from slice
389        Bytes::copy_from_slice(&self.buffer[..self.cursor])
390    }
391
392    /// Get segments for the current level of data
393    #[inline]
394    pub fn get_level_segments(&self, data: &[u8]) -> Vec<B256> {
395        let branches = branches_for_body_size(BODY_SIZE);
396
397        #[cfg(not(target_arch = "wasm32"))]
398        {
399            use rayon::prelude::*;
400            (0..branches)
401                .into_par_iter()
402                .map(|i| self.compute_segment_hash(data, i))
403                .collect()
404        }
405
406        #[cfg(target_arch = "wasm32")]
407        {
408            (0..branches)
409                .map(|i| self.compute_segment_hash(data, i))
410                .collect()
411        }
412    }
413
414    /// Compute the hash for a single segment at given index
415    #[inline(always)]
416    fn compute_segment_hash(&self, data: &[u8], i: usize) -> B256 {
417        let start = i << SEGMENT_SIZE_LOG2; // Equivalent to i * SEGMENT_SIZE
418        let mut hasher = Self::node_hasher(self.prefix.as_deref());
419
420        if start < data.len() {
421            let end = (start + SEGMENT_SIZE).min(data.len());
422            let segment_data = &data[start..end];
423
424            // Update with segment data
425            hasher.update(segment_data);
426
427            // If segment is shorter than SEGMENT_SIZE, the remaining bytes are zeros
428            if segment_data.len() < SEGMENT_SIZE {
429                hasher.update(&[0u8; SEGMENT_SIZE][..(SEGMENT_SIZE - segment_data.len())]);
430            }
431        } else {
432            // Empty segment (all zeros)
433            hasher.update([0u8; SEGMENT_SIZE]);
434        }
435
436        B256::from_slice(hasher.finalize().as_slice())
437    }
438}
439
440impl<const BODY_SIZE: usize> Write for Hasher<BODY_SIZE> {
441    #[inline]
442    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
443        let available = BODY_SIZE - self.cursor;
444        let to_write = buf.len().min(available);
445        if to_write > 0 {
446            self.buffer[self.cursor..self.cursor + to_write].copy_from_slice(&buf[..to_write]);
447            self.cursor += to_write;
448        }
449        Ok(to_write)
450    }
451
452    #[inline]
453    fn flush(&mut self) -> io::Result<()> {
454        Ok(())
455    }
456}
457
458impl<const BODY_SIZE: usize> OutputSizeUser for Hasher<BODY_SIZE> {
459    type OutputSize = U32;
460}
461
462impl<const BODY_SIZE: usize> Update for Hasher<BODY_SIZE> {
463    #[inline]
464    fn update(&mut self, data: &[u8]) {
465        self.update(data);
466    }
467}
468
469impl<const BODY_SIZE: usize> Reset for Hasher<BODY_SIZE> {
470    #[inline]
471    fn reset(&mut self) {
472        self.reset_internal();
473    }
474}
475
476impl<const BODY_SIZE: usize> FixedOutput for Hasher<BODY_SIZE> {
477    #[inline]
478    fn finalize_into(self, out: &mut Array<u8, Self::OutputSize>) {
479        let b256 = self.sum();
480        out.copy_from_slice(b256.as_slice());
481    }
482}
483
484impl<const BODY_SIZE: usize> FixedOutputReset for Hasher<BODY_SIZE> {
485    #[inline]
486    fn finalize_into_reset(&mut self, out: &mut Array<u8, Self::OutputSize>) {
487        let b256 = self.sum();
488        out.copy_from_slice(b256.as_slice());
489        self.reset_internal();
490    }
491}
492
493impl<const BODY_SIZE: usize> digest::HashMarker for Hasher<BODY_SIZE> {}
494
495/// Factory for creating BMT hashers.
496#[derive(Debug, Default, Clone)]
497pub struct HasherFactory<const BODY_SIZE: usize = DEFAULT_BODY_SIZE>;
498
499impl<const BODY_SIZE: usize> HasherFactory<BODY_SIZE> {
500    /// Create a new factory.
501    #[inline]
502    pub const fn new() -> Self {
503        Self
504    }
505
506    /// Create a new BMT hasher.
507    #[inline]
508    pub const fn create_hasher(&self) -> Hasher<BODY_SIZE> {
509        Hasher::new()
510    }
511}