Skip to main content

libssz_merkle/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6#[cfg(feature = "alloc")]
7use alloc::{vec, vec::Vec};
8
9#[cfg(feature = "ethereum_types")]
10mod ethereum_types;
11
12use libssz::SszEncode;
13
14/// A 32-byte Merkle tree node.
15pub type Node = [u8; 32];
16
17include!(concat!(env!("OUT_DIR"), "/zero_hashes.rs"));
18
19/// Trait for pluggable SHA-256 implementations.
20///
21/// Instance method (`&self`) enables passing `&impl Sha256Hasher` through the
22/// call chain. The hasher struct is typically zero-sized so `&self` compiles away.
23pub trait Sha256Hasher {
24    fn hash(&self, data: &[u8]) -> [u8; 32];
25}
26
27/// Default SHA-256 hasher using the `sha2` crate.
28#[cfg(feature = "sha2-backend")]
29pub struct Sha2Hasher;
30
31#[cfg(feature = "sha2-backend")]
32impl Sha256Hasher for Sha2Hasher {
33    fn hash(&self, data: &[u8]) -> [u8; 32] {
34        use sha2::{Digest, Sha256};
35        Sha256::digest(data).into()
36    }
37}
38
39/// Hash two nodes together: SHA256(a || b).
40#[inline]
41pub fn hash_nodes(hasher: &impl Sha256Hasher, a: &Node, b: &Node) -> Node {
42    let mut buf = [0u8; 64];
43    buf[..32].copy_from_slice(a);
44    buf[32..].copy_from_slice(b);
45    hasher.hash(&buf)
46}
47
48/// Pack serialized bytes into 32-byte chunks, zero-padding the last chunk if needed.
49#[cfg(feature = "alloc")]
50pub fn pack(values: &[u8]) -> Vec<Node> {
51    if values.is_empty() {
52        return Vec::new();
53    }
54    let num_chunks = values.len().div_ceil(32);
55    let mut chunks = Vec::with_capacity(num_chunks);
56    for i in 0..num_chunks {
57        let start = i * 32;
58        let end = core::cmp::min(start + 32, values.len());
59        let mut chunk = [0u8; 32];
60        chunk[..end - start].copy_from_slice(&values[start..end]);
61        chunks.push(chunk);
62    }
63    chunks
64}
65
66/// Pack a bitfield into 32-byte chunks.
67/// `bits` is the raw byte buffer, `num_bits` is the number of valid bits.
68#[cfg(feature = "alloc")]
69pub fn pack_bits(bits: &[u8], num_bits: usize) -> Vec<Node> {
70    let num_bytes = num_bits.div_ceil(8);
71    let effective = &bits[..core::cmp::min(num_bytes, bits.len())];
72    pack(effective)
73}
74
75/// Compute the Merkle root of a list of chunks.
76///
77/// If `limit` is `Some(n)`, the tree is padded as if there were `n` leaf chunks.
78/// The padding is virtual: any subtree lying entirely beyond `chunks.len()` is
79/// all zeros, so its root is read from [`ZERO_HASHES`] instead of being hashed.
80///
81/// Cost is `O(chunks.len())` plus one hash per virtual level above the real subtree.
82/// Two buffers are allocated and reused (≈ `chunks.len()` and `chunks.len().div_ceil(2)` nodes).
83#[cfg(feature = "alloc")]
84pub fn merkleize(hasher: &impl Sha256Hasher, chunks: &[Node], limit: Option<usize>) -> Node {
85    let count = match limit {
86        Some(l) => {
87            assert!(
88                chunks.len() <= l,
89                "bug: chunk count {} exceeds limit {} — bounded type invariant violated",
90                chunks.len(),
91                l
92            );
93            l
94        }
95        None => chunks.len(),
96    };
97
98    if count == 0 {
99        return ZERO_HASHES[0];
100    }
101
102    let depth = count.next_power_of_two().trailing_zeros() as usize;
103
104    // With no real chunks the whole tree is zeros, so the root is precomputed.
105    if chunks.is_empty() {
106        return ZERO_HASHES[depth];
107    }
108
109    // Depth of the smallest subtree covering all real chunks. Every level above
110    // it pairs the running root with an all-zero sibling.
111    let real_depth = chunks.len().next_power_of_two().trailing_zeros() as usize;
112
113    // Fold the real chunks one level per iteration, swapping between two buffers
114    // that are allocated once and reused, so no level allocates.
115    //
116    // Only full pairs of real nodes are hashed. A level with an odd node count
117    // ends on the frontier, where the missing right sibling roots an all-zero
118    // subtree, so `ZERO_HASHES[level]` stands in for it rather than being hashed.
119    let mut layer: Vec<Node> = chunks.to_vec();
120    let mut next: Vec<Node> = Vec::with_capacity(layer.len().div_ceil(2));
121
122    for zero_hash in ZERO_HASHES.iter().take(real_depth) {
123        next.clear();
124        let mut pairs = layer.chunks_exact(2);
125        for pair in &mut pairs {
126            next.push(hash_nodes(hasher, &pair[0], &pair[1]));
127        }
128        if let [odd] = pairs.remainder() {
129            next.push(hash_nodes(hasher, odd, zero_hash));
130        }
131        core::mem::swap(&mut layer, &mut next);
132    }
133
134    // `layer[0]` now roots the real-data subtree. Merge in the virtual levels.
135    let mut root = layer[0];
136    for zero_hash in ZERO_HASHES.iter().take(depth).skip(real_depth) {
137        root = hash_nodes(hasher, &root, zero_hash);
138    }
139
140    root
141}
142
143/// Progressive merkleization: a chain of progressively larger binary subtrees.
144///
145/// Creates a 0-terminated sequence of binary subtrees with increasing leaf count:
146/// `hash(merkleize(chunks[..1], 1), hash(merkleize(chunks[1..5], 4), hash(merkleize(chunks[5..21], 16), ...)))`
147///
148/// Leaf counts grow as: 1, 4, 16, 64, 256, ... (each 4x the previous).
149///
150/// Reference: EIP-7916 / consensus-specs simple-serialize.md
151#[cfg(feature = "alloc")]
152pub fn merkleize_progressive(hasher: &impl Sha256Hasher, chunks: &[Node]) -> Node {
153    merkleize_progressive_inner(hasher, chunks, 1)
154}
155
156#[cfg(feature = "alloc")]
157fn merkleize_progressive_inner(
158    hasher: &impl Sha256Hasher,
159    chunks: &[Node],
160    num_leaves: usize,
161) -> Node {
162    if chunks.is_empty() {
163        return [0u8; 32];
164    }
165
166    let take = core::cmp::min(num_leaves, chunks.len());
167    let subtree = merkleize(hasher, &chunks[..take], Some(num_leaves));
168    let rest = merkleize_progressive_inner(hasher, &chunks[take..], num_leaves * 4);
169    // EIP-7916 computes `hash(a, b)` with `a` the fixed-size subtree over the
170    // first `num_leaves` chunks and `b` the recursive remainder, so the subtree
171    // is the left child and the remainder the right one.
172    hash_nodes(hasher, &subtree, &rest)
173}
174
175/// Mix in an active_fields bitvector: hash_nodes(root, pack_bits(active_fields)).
176#[cfg(feature = "alloc")]
177pub fn mix_in_active_fields(
178    hasher: &impl Sha256Hasher,
179    root: &Node,
180    active_fields: &[bool],
181) -> Node {
182    // active_fields is at most 256 bits = 1 chunk
183    let mut bits_bytes = vec![0u8; active_fields.len().div_ceil(8)];
184    for (i, &active) in active_fields.iter().enumerate() {
185        if active {
186            bits_bytes[i / 8] |= 1 << (i % 8);
187        }
188    }
189    let chunks = pack_bits(&bits_bytes, active_fields.len());
190    let af_node = chunks.first().copied().unwrap_or([0u8; 32]);
191    hash_nodes(hasher, root, &af_node)
192}
193
194/// Mix in a length value: hash_nodes(root, length_as_le_bytes_node).
195#[inline]
196pub fn mix_in_length(hasher: &impl Sha256Hasher, root: &Node, length: usize) -> Node {
197    let mut length_node = [0u8; 32];
198    length_node[..8].copy_from_slice(&(length as u64).to_le_bytes());
199    hash_nodes(hasher, root, &length_node)
200}
201
202/// Mix in a selector value: hash_nodes(root, selector_as_le_bytes_node).
203#[inline]
204pub fn mix_in_selector(hasher: &impl Sha256Hasher, root: &Node, selector: u8) -> Node {
205    let mut selector_node = [0u8; 32];
206    selector_node[0] = selector;
207    hash_nodes(hasher, root, &selector_node)
208}
209
210/// Trait for types that can compute their SSZ hash tree root.
211pub trait HashTreeRoot {
212    fn hash_tree_root(&self, hasher: &impl Sha256Hasher) -> Node;
213
214    /// Returns `true` for SSZ basic types (bool, uN, byte arrays).
215    /// Composite types (containers, vectors, lists) return `false`.
216    /// Used by Vector/List HTR to decide between packing and per-element hashing.
217    fn is_basic_type() -> bool
218    where
219        Self: Sized,
220    {
221        false
222    }
223}
224
225// ── bool ──
226
227impl HashTreeRoot for bool {
228    #[inline(always)]
229    fn hash_tree_root(&self, _hasher: &impl Sha256Hasher) -> Node {
230        let mut node = [0u8; 32];
231        node[0] = *self as u8;
232        node
233    }
234
235    fn is_basic_type() -> bool {
236        true
237    }
238}
239
240// ── Unsigned integers ──
241
242impl HashTreeRoot for u8 {
243    #[inline(always)]
244    fn hash_tree_root(&self, _hasher: &impl Sha256Hasher) -> Node {
245        let mut node = [0u8; 32];
246        let bytes = self.to_le_bytes();
247        node[..bytes.len()].copy_from_slice(&bytes);
248        node
249    }
250
251    fn is_basic_type() -> bool {
252        true
253    }
254}
255
256impl HashTreeRoot for u16 {
257    #[inline(always)]
258    fn hash_tree_root(&self, _hasher: &impl Sha256Hasher) -> Node {
259        let mut node = [0u8; 32];
260        let bytes = self.to_le_bytes();
261        node[..bytes.len()].copy_from_slice(&bytes);
262        node
263    }
264
265    fn is_basic_type() -> bool {
266        true
267    }
268}
269
270impl HashTreeRoot for u32 {
271    #[inline(always)]
272    fn hash_tree_root(&self, _hasher: &impl Sha256Hasher) -> Node {
273        let mut node = [0u8; 32];
274        let bytes = self.to_le_bytes();
275        node[..bytes.len()].copy_from_slice(&bytes);
276        node
277    }
278
279    fn is_basic_type() -> bool {
280        true
281    }
282}
283
284impl HashTreeRoot for u64 {
285    #[inline(always)]
286    fn hash_tree_root(&self, _hasher: &impl Sha256Hasher) -> Node {
287        let mut node = [0u8; 32];
288        let bytes = self.to_le_bytes();
289        node[..bytes.len()].copy_from_slice(&bytes);
290        node
291    }
292
293    fn is_basic_type() -> bool {
294        true
295    }
296}
297
298impl HashTreeRoot for u128 {
299    #[inline(always)]
300    fn hash_tree_root(&self, _hasher: &impl Sha256Hasher) -> Node {
301        let mut node = [0u8; 32];
302        let bytes = self.to_le_bytes();
303        node[..bytes.len()].copy_from_slice(&bytes);
304        node
305    }
306
307    fn is_basic_type() -> bool {
308        true
309    }
310}
311
312// ── [u8; N] ──
313
314#[cfg(feature = "alloc")]
315impl<const N: usize> HashTreeRoot for [u8; N] {
316    fn hash_tree_root(&self, hasher: &impl Sha256Hasher) -> Node {
317        if N <= 32 {
318            let mut node = [0u8; 32];
319            node[..N].copy_from_slice(self);
320            node
321        } else {
322            merkleize(hasher, &pack(self), None)
323        }
324    }
325
326    fn is_basic_type() -> bool {
327        N <= 32
328    }
329}
330
331// ── Vec<T> for basic types ──
332
333#[cfg(feature = "alloc")]
334impl<T: HashTreeRoot + SszEncode> HashTreeRoot for Vec<T> {
335    fn hash_tree_root(&self, hasher: &impl Sha256Hasher) -> Node {
336        let chunks: Vec<Node> = if T::is_basic_type() {
337            let mut serialized = Vec::new();
338            for item in self {
339                item.ssz_append(&mut serialized);
340            }
341            pack(&serialized)
342        } else {
343            self.iter()
344                .map(|item| item.hash_tree_root(hasher))
345                .collect()
346        };
347        let root = if chunks.is_empty() {
348            merkleize(hasher, &[ZERO_HASHES[0]], None)
349        } else {
350            merkleize(hasher, &chunks, None)
351        };
352        mix_in_length(hasher, &root, self.len())
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359    use alloc::vec;
360    use alloc::vec::Vec;
361
362    #[cfg(feature = "sha2-backend")]
363    use crate::Sha2Hasher;
364
365    // ── hash_nodes tests ──
366
367    #[cfg(feature = "sha2-backend")]
368    #[test]
369    fn hash_nodes_of_two_zero_nodes() {
370        let zero = [0u8; 32];
371        let result = hash_nodes(&Sha2Hasher, &zero, &zero);
372        use sha2::{Digest, Sha256};
373        let mut sha = Sha256::new();
374        sha.update([0u8; 64]);
375        let expected: [u8; 32] = sha.finalize().into();
376        assert_eq!(result, expected);
377    }
378
379    #[cfg(feature = "sha2-backend")]
380    #[test]
381    fn hash_nodes_is_not_commutative() {
382        let a = [1u8; 32];
383        let b = [2u8; 32];
384        assert_ne!(
385            hash_nodes(&Sha2Hasher, &a, &b),
386            hash_nodes(&Sha2Hasher, &b, &a)
387        );
388    }
389
390    // ── Zero hashes tests ──
391
392    #[test]
393    fn zero_hash_level_0_is_all_zeros() {
394        assert_eq!(ZERO_HASHES[0], [0u8; 32]);
395    }
396
397    #[cfg(feature = "sha2-backend")]
398    #[test]
399    fn zero_hash_level_1_is_hash_of_two_zeros() {
400        let expected = hash_nodes(&Sha2Hasher, &[0u8; 32], &[0u8; 32]);
401        assert_eq!(ZERO_HASHES[1], expected);
402    }
403
404    #[cfg(feature = "sha2-backend")]
405    #[test]
406    fn zero_hash_level_2_is_hash_of_two_level_1() {
407        let expected = hash_nodes(&Sha2Hasher, &ZERO_HASHES[1], &ZERO_HASHES[1]);
408        assert_eq!(ZERO_HASHES[2], expected);
409    }
410
411    // ── pack tests ──
412
413    #[test]
414    fn pack_empty_returns_empty() {
415        let result = pack(&[]);
416        assert!(result.is_empty());
417    }
418
419    #[test]
420    fn pack_single_byte() {
421        let result = pack(&[0x42]);
422        assert_eq!(result.len(), 1);
423        let mut expected = [0u8; 32];
424        expected[0] = 0x42;
425        assert_eq!(result[0], expected);
426    }
427
428    #[test]
429    fn pack_exactly_32_bytes() {
430        let input = [0xAB; 32];
431        let result = pack(&input);
432        assert_eq!(result.len(), 1);
433        assert_eq!(result[0], input);
434    }
435
436    #[test]
437    fn pack_33_bytes_produces_two_chunks() {
438        let input = [0xFF; 33];
439        let result = pack(&input);
440        assert_eq!(result.len(), 2);
441        assert_eq!(result[0], [0xFF; 32]);
442        let mut expected_second = [0u8; 32];
443        expected_second[0] = 0xFF;
444        assert_eq!(result[1], expected_second);
445    }
446
447    #[test]
448    fn pack_two_u64_values() {
449        let val1: u64 = 1;
450        let val2: u64 = 2;
451        let mut serialized = Vec::new();
452        serialized.extend_from_slice(&val1.to_le_bytes());
453        serialized.extend_from_slice(&val2.to_le_bytes());
454        let result = pack(&serialized);
455        assert_eq!(result.len(), 1); // 16 bytes fits in one 32-byte chunk
456        let mut expected = [0u8; 32];
457        expected[..8].copy_from_slice(&1u64.to_le_bytes());
458        expected[8..16].copy_from_slice(&2u64.to_le_bytes());
459        assert_eq!(result[0], expected);
460    }
461
462    // ── pack_bits tests ──
463
464    #[test]
465    fn pack_bits_7_bits() {
466        let bits = [0b0111_1111u8]; // 7 bits set
467        let result = pack_bits(&bits, 7);
468        assert_eq!(result.len(), 1);
469        let mut expected = [0u8; 32];
470        expected[0] = 0b0111_1111;
471        assert_eq!(result[0], expected);
472    }
473
474    #[test]
475    fn pack_bits_8_bits() {
476        let bits = [0xFF];
477        let result = pack_bits(&bits, 8);
478        assert_eq!(result.len(), 1);
479        let mut expected = [0u8; 32];
480        expected[0] = 0xFF;
481        assert_eq!(result[0], expected);
482    }
483
484    #[test]
485    fn pack_bits_9_bits() {
486        let bits = [0xFF, 0x01]; // 9 bits
487        let result = pack_bits(&bits, 9);
488        assert_eq!(result.len(), 1);
489        let mut expected = [0u8; 32];
490        expected[0] = 0xFF;
491        expected[1] = 0x01;
492        assert_eq!(result[0], expected);
493    }
494
495    // ── merkleize tests ──
496
497    #[cfg(feature = "sha2-backend")]
498    #[test]
499    fn merkleize_empty_returns_zero_hash() {
500        let result = merkleize(&Sha2Hasher, &[], None);
501        assert_eq!(result, ZERO_HASHES[0]);
502    }
503
504    #[cfg(feature = "sha2-backend")]
505    #[test]
506    fn merkleize_one_chunk_is_identity() {
507        let chunk = [0x42u8; 32];
508        let result = merkleize(&Sha2Hasher, &[chunk], None);
509        assert_eq!(result, chunk);
510    }
511
512    #[cfg(feature = "sha2-backend")]
513    #[test]
514    fn merkleize_two_chunks() {
515        let a = [1u8; 32];
516        let b = [2u8; 32];
517        let result = merkleize(&Sha2Hasher, &[a, b], None);
518        assert_eq!(result, hash_nodes(&Sha2Hasher, &a, &b));
519    }
520
521    #[cfg(feature = "sha2-backend")]
522    #[test]
523    fn merkleize_three_chunks_pads_to_four() {
524        let a = [1u8; 32];
525        let b = [2u8; 32];
526        let c = [3u8; 32];
527        let result = merkleize(&Sha2Hasher, &[a, b, c], None);
528        // Tree: hash(hash(a,b), hash(c, zero))
529        let left = hash_nodes(&Sha2Hasher, &a, &b);
530        let right = hash_nodes(&Sha2Hasher, &c, &ZERO_HASHES[0]);
531        assert_eq!(result, hash_nodes(&Sha2Hasher, &left, &right));
532    }
533
534    #[cfg(feature = "sha2-backend")]
535    #[test]
536    fn merkleize_with_limit_pads_with_zero_hashes() {
537        let a = [1u8; 32];
538        // With limit=4, single chunk should be padded to 4 leaves
539        let result = merkleize(&Sha2Hasher, &[a], Some(4));
540        // Tree: hash(hash(a, zero), hash(zero, zero))
541        let left = hash_nodes(&Sha2Hasher, &a, &ZERO_HASHES[0]);
542        let right = ZERO_HASHES[1]; // hash(zero, zero)
543        assert_eq!(result, hash_nodes(&Sha2Hasher, &left, &right));
544    }
545
546    #[cfg(feature = "sha2-backend")]
547    #[test]
548    fn merkleize_with_limit_equal_to_count() {
549        let a = [1u8; 32];
550        let b = [2u8; 32];
551        // limit=2 with 2 chunks should give same result as no limit
552        assert_eq!(
553            merkleize(&Sha2Hasher, &[a, b], Some(2)),
554            merkleize(&Sha2Hasher, &[a, b], None)
555        );
556    }
557
558    #[cfg(feature = "sha2-backend")]
559    #[test]
560    #[should_panic(expected = "chunk count")]
561    fn merkleize_panics_if_count_exceeds_limit() {
562        let chunks = [[0u8; 32]; 3];
563        merkleize(&Sha2Hasher, &chunks, Some(2));
564    }
565
566    // ── merkleize: zero-subtree elision ──
567
568    /// Naive merkleization: materializes every leaf up to `limit`, including the
569    /// all-zero ones. Reference oracle for [`merkleize`], which must produce
570    /// identical roots while skipping the zero subtrees.
571    #[cfg(feature = "sha2-backend")]
572    fn merkleize_fully_materialized(chunks: &[Node], limit: Option<usize>) -> Node {
573        let count = core::cmp::max(limit.unwrap_or(chunks.len()), chunks.len());
574        if count == 0 {
575            return ZERO_HASHES[0];
576        }
577        let mut layer = chunks.to_vec();
578        layer.resize(count.next_power_of_two(), ZERO_HASHES[0]);
579        while layer.len() > 1 {
580            layer = layer
581                .chunks(2)
582                .map(|pair| hash_nodes(&Sha2Hasher, &pair[0], &pair[1]))
583                .collect();
584        }
585        layer[0]
586    }
587
588    /// Counts `hash_nodes` invocations while delegating to the real hasher, so
589    /// tree shape is unaffected.
590    #[cfg(feature = "sha2-backend")]
591    struct CountingHasher {
592        calls: core::cell::Cell<usize>,
593    }
594
595    #[cfg(feature = "sha2-backend")]
596    impl CountingHasher {
597        fn new() -> Self {
598            Self {
599                calls: core::cell::Cell::new(0),
600            }
601        }
602    }
603
604    #[cfg(feature = "sha2-backend")]
605    impl Sha256Hasher for CountingHasher {
606        fn hash(&self, data: &[u8]) -> [u8; 32] {
607            self.calls.set(self.calls.get() + 1);
608            Sha2Hasher.hash(data)
609        }
610    }
611
612    /// Hash count for a merkleization that treats every subtree beyond
613    /// `chunk_count` as a precomputed zero hash: one hash per node on the real
614    /// frontier, plus one per virtual level above it.
615    fn zero_aware_hash_count(chunk_count: usize, limit: Option<usize>) -> usize {
616        let count = limit.unwrap_or(chunk_count);
617        if count == 0 || chunk_count == 0 {
618            return 0;
619        }
620        let depth = count.next_power_of_two().trailing_zeros() as usize;
621        let real_depth = chunk_count.next_power_of_two().trailing_zeros() as usize;
622
623        let mut total = depth - real_depth;
624        let mut len = chunk_count;
625        for _ in 0..real_depth {
626            len = len.div_ceil(2);
627            total += len;
628        }
629        total
630    }
631
632    #[cfg(feature = "sha2-backend")]
633    #[test]
634    fn merkleize_matches_fully_materialized_oracle() {
635        for n in 0..=70 {
636            let chunks: Vec<Node> = (0..n).map(|i| [i as u8 + 1; 32]).collect();
637            assert_eq!(
638                merkleize(&Sha2Hasher, &chunks, None),
639                merkleize_fully_materialized(&chunks, None),
640                "no limit, {n} chunks"
641            );
642            for limit in [n, n + 1, n + 7, 64, 128, 1024] {
643                if limit < n {
644                    continue;
645                }
646                assert_eq!(
647                    merkleize(&Sha2Hasher, &chunks, Some(limit)),
648                    merkleize_fully_materialized(&chunks, Some(limit)),
649                    "limit {limit}, {n} chunks"
650                );
651            }
652        }
653    }
654
655    #[cfg(feature = "sha2-backend")]
656    #[test]
657    fn merkleize_does_not_hash_zero_subtrees() {
658        for n in [1usize, 2, 3, 5, 9, 17, 33, 65, 100, 1025] {
659            let chunks: Vec<Node> = (0..n).map(|i| [i as u8; 32]).collect();
660            for limit in [
661                None,
662                Some(n),
663                Some(n.next_power_of_two() * 4),
664                Some(1 << 18),
665            ] {
666                if limit.is_some_and(|l| l < n) {
667                    continue;
668                }
669                let hasher = CountingHasher::new();
670                merkleize(&hasher, &chunks, limit);
671                assert_eq!(
672                    hasher.calls.get(),
673                    zero_aware_hash_count(n, limit),
674                    "{n} chunks, limit {limit:?}"
675                );
676            }
677        }
678    }
679
680    /// The regression this guards: crossing a power-of-two boundary used to
681    /// double the hash count, because the padding leaves were materialized and
682    /// hashed as if they held data.
683    #[cfg(feature = "sha2-backend")]
684    #[test]
685    fn merkleize_cost_is_linear_across_power_of_two_boundary() {
686        let count_for = |n: usize| {
687            let chunks: Vec<Node> = (0..n).map(|i| [i as u8; 32]).collect();
688            let hasher = CountingHasher::new();
689            merkleize(&hasher, &chunks, Some(1 << 18));
690            hasher.calls.get()
691        };
692
693        let below = count_for(1024);
694        let above = count_for(1025);
695        assert!(
696            above < below + 20,
697            "one extra chunk added {} hashes (1024 -> {below}, 1025 -> {above})",
698            above - below
699        );
700    }
701
702    #[cfg(feature = "sha2-backend")]
703    #[test]
704    fn merkleize_empty_with_limit_is_the_precomputed_zero_hash() {
705        for (depth, zero_hash) in ZERO_HASHES.iter().enumerate().take(8) {
706            let limit = 1usize << depth;
707            assert_eq!(merkleize(&Sha2Hasher, &[], Some(limit)), *zero_hash);
708        }
709        // Non-power-of-two limits round up to the enclosing subtree.
710        assert_eq!(merkleize(&Sha2Hasher, &[], Some(3)), ZERO_HASHES[2]);
711        assert_eq!(merkleize(&Sha2Hasher, &[], Some(100)), ZERO_HASHES[7]);
712    }
713
714    // ── merkleize_progressive tests ──
715
716    /// Decode a 64-character hex string into a `Node`.
717    #[cfg(feature = "sha2-backend")]
718    fn node_from_hex(s: &str) -> Node {
719        let bytes = s.as_bytes();
720        assert_eq!(bytes.len(), 64, "expected 64 hex characters");
721        let mut out = [0u8; 32];
722        for (i, byte) in out.iter_mut().enumerate() {
723            let hi = char::from(bytes[i * 2]).to_digit(16).expect("hex digit");
724            let lo = char::from(bytes[i * 2 + 1])
725                .to_digit(16)
726                .expect("hex digit");
727            *byte = ((hi << 4) | lo) as u8;
728        }
729        out
730    }
731
732    /// `hash_tree_root` of a `ProgressiveList[uint64]` holding `values`.
733    #[cfg(feature = "sha2-backend")]
734    fn progressive_u64_list_root(values: &[u64]) -> Node {
735        let mut serialized = Vec::with_capacity(values.len() * 8);
736        for value in values {
737            serialized.extend_from_slice(&value.to_le_bytes());
738        }
739        let root = merkleize_progressive(&Sha2Hasher, &pack(&serialized));
740        mix_in_length(&Sha2Hasher, &root, values.len())
741    }
742
743    #[cfg(feature = "sha2-backend")]
744    #[test]
745    fn merkleize_progressive_empty_is_zero_node() {
746        assert_eq!(merkleize_progressive(&Sha2Hasher, &[]), [0u8; 32]);
747    }
748
749    /// EIP-7916 puts the fixed-size subtree on the left and the recursive
750    /// remainder on the right, so a lone chunk hashes as `hash(chunk, zero)`.
751    #[cfg(feature = "sha2-backend")]
752    #[test]
753    fn merkleize_progressive_puts_subtree_on_the_left() {
754        let chunk = [0x11u8; 32];
755        assert_eq!(
756            merkleize_progressive(&Sha2Hasher, &[chunk]),
757            hash_nodes(&Sha2Hasher, &chunk, &[0u8; 32])
758        );
759    }
760
761    /// Roots of `ProgressiveList[uint64]([1, ..., n])` as computed by
762    /// ethereum/remerkleable. Four `uint64`s pack into one chunk, so these
763    /// sizes pack into 0, 1, 1, 2 and 6 chunks: `n = 5` spans the first two
764    /// subtrees (leaf counts 1 then 4) and `n = 21` reaches the third (16),
765    /// so the larger two exercise the recursion rather than the base case.
766    #[cfg(feature = "sha2-backend")]
767    #[test]
768    fn merkleize_progressive_matches_reference_roots() {
769        let cases: [(u64, &str); 5] = [
770            (
771                0,
772                "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
773            ),
774            (
775                1,
776                "905efb51c2764c2c7a4efb0548e372569df06db82115c3b1896c186632f3fe5b",
777            ),
778            (
779                2,
780                "4250789d7838bee417a2b0d7639d928b05e8b75f1fc59588a4301b6e8f70ba58",
781            ),
782            (
783                5,
784                "29918e0447260511bc5be0f7dbb9817201e16e30c56af228b9cb931a16e8799d",
785            ),
786            (
787                21,
788                "ed360c03ecbdfbb6f4b1cf5d9cbf6887038423e31121700797de968a9969aaed",
789            ),
790        ];
791        for (n, expected) in cases {
792            let values: Vec<u64> = (1..=n).collect();
793            assert_eq!(
794                progressive_u64_list_root(&values),
795                node_from_hex(expected),
796                "ProgressiveList[uint64] of 1..={n}"
797            );
798        }
799    }
800
801    /// Roots pinned from the consensus-specs `basic_progressive_list` vectors
802    /// (v1.7.0-alpha.13, `tests/general/phase0/ssz_generic`).
803    #[cfg(feature = "sha2-backend")]
804    #[test]
805    fn merkleize_progressive_matches_consensus_spec_vectors() {
806        let cases: [(usize, u64, &str, &str); 5] = [
807            (
808                1,
809                u64::MAX,
810                "7bea313629252f757f27d17a1ce515715beedeff41a3f7f6c9ac47bfddcf447c",
811                "proglist_uint64_max_1",
812            ),
813            (
814                4,
815                u64::MAX,
816                "06e8318500bbdb0b6e9b55e63e819cbed45668f6481c24c67bd1d8eaf8703ca8",
817                "proglist_uint64_max_20",
818            ),
819            (
820                21,
821                u64::MAX,
822                "d444bef5f61103078f3ca9bdb2857a9b5e883d738b213a1607a9f3a770a332d2",
823                "proglist_uint64_max_22",
824            ),
825            (
826                7,
827                0,
828                "13a56a67f02ea52c0841043aba35de738bec8519fee07860658d13e2d0442782",
829                "proglist_uint64_zero_8",
830            ),
831            (
832                53,
833                0,
834                "2935884058446007e2592494fe88884e83ee80ecd2041779447c30642fe63ac8",
835                "proglist_uint64_zero_86",
836            ),
837        ];
838        for (count, value, expected, case_name) in cases {
839            let values = vec![value; count];
840            assert_eq!(
841                progressive_u64_list_root(&values),
842                node_from_hex(expected),
843                "{case_name}"
844            );
845        }
846    }
847
848    // ── mix_in_length tests ──
849
850    #[cfg(feature = "sha2-backend")]
851    #[test]
852    fn mix_in_length_known_value() {
853        let root = [0u8; 32];
854        let result = mix_in_length(&Sha2Hasher, &root, 5);
855        let mut len_node = [0u8; 32];
856        len_node[..8].copy_from_slice(&5u64.to_le_bytes());
857        assert_eq!(result, hash_nodes(&Sha2Hasher, &root, &len_node));
858    }
859
860    #[cfg(feature = "sha2-backend")]
861    #[test]
862    fn mix_in_length_zero() {
863        let root = [0xAB; 32];
864        let result = mix_in_length(&Sha2Hasher, &root, 0);
865        // length=0 means length_node is all zeros
866        assert_eq!(result, hash_nodes(&Sha2Hasher, &root, &[0u8; 32]));
867    }
868
869    // ── mix_in_selector tests ──
870
871    #[cfg(feature = "sha2-backend")]
872    #[test]
873    fn mix_in_selector_known_value() {
874        let root = [0u8; 32];
875        let result = mix_in_selector(&Sha2Hasher, &root, 3);
876        let mut sel_node = [0u8; 32];
877        sel_node[0] = 3;
878        assert_eq!(result, hash_nodes(&Sha2Hasher, &root, &sel_node));
879    }
880
881    // ── HashTreeRoot tests ──
882
883    #[cfg(feature = "sha2-backend")]
884    #[test]
885    fn hash_tree_root_bool_false() {
886        let result = false.hash_tree_root(&Sha2Hasher);
887        assert_eq!(result, [0u8; 32]);
888    }
889
890    #[cfg(feature = "sha2-backend")]
891    #[test]
892    fn hash_tree_root_bool_true() {
893        let result = true.hash_tree_root(&Sha2Hasher);
894        let mut expected = [0u8; 32];
895        expected[0] = 1;
896        assert_eq!(result, expected);
897    }
898
899    #[cfg(feature = "sha2-backend")]
900    #[test]
901    fn hash_tree_root_u64() {
902        let val: u64 = 0x0102030405060708;
903        let result = val.hash_tree_root(&Sha2Hasher);
904        let mut expected = [0u8; 32];
905        expected[..8].copy_from_slice(&val.to_le_bytes());
906        assert_eq!(result, expected);
907    }
908
909    #[cfg(feature = "sha2-backend")]
910    #[test]
911    fn hash_tree_root_u8() {
912        let val: u8 = 42;
913        let result = val.hash_tree_root(&Sha2Hasher);
914        let mut expected = [0u8; 32];
915        expected[0] = 42;
916        assert_eq!(result, expected);
917    }
918
919    #[cfg(feature = "sha2-backend")]
920    #[test]
921    fn hash_tree_root_u128() {
922        let val: u128 = 1;
923        let result = val.hash_tree_root(&Sha2Hasher);
924        let mut expected = [0u8; 32];
925        expected[..16].copy_from_slice(&val.to_le_bytes());
926        assert_eq!(result, expected);
927    }
928
929    #[cfg(feature = "sha2-backend")]
930    #[test]
931    fn hash_tree_root_byte_array_32_is_identity() {
932        let arr = [0xab_u8; 32];
933        assert_eq!(arr.hash_tree_root(&Sha2Hasher), arr);
934        assert!(<[u8; 32]>::is_basic_type());
935    }
936
937    #[cfg(feature = "sha2-backend")]
938    #[test]
939    fn hash_tree_root_byte_array_20_is_padded() {
940        let arr = [0xcd_u8; 20];
941        let root = arr.hash_tree_root(&Sha2Hasher);
942        assert_eq!(&root[..20], &arr[..]);
943        assert_eq!(&root[20..], &[0u8; 12]);
944        assert!(<[u8; 20]>::is_basic_type());
945    }
946
947    #[cfg(feature = "sha2-backend")]
948    #[test]
949    fn hash_tree_root_byte_array_4_is_padded() {
950        let arr = [0x01, 0x02, 0x03, 0x04];
951        let root = arr.hash_tree_root(&Sha2Hasher);
952        assert_eq!(&root[..4], &arr[..]);
953        assert_eq!(&root[4..], &[0u8; 28]);
954        assert!(<[u8; 4]>::is_basic_type());
955    }
956
957    #[cfg(feature = "sha2-backend")]
958    #[test]
959    fn hash_tree_root_byte_array_7_is_padded() {
960        let arr = [0xff_u8; 7];
961        let root = arr.hash_tree_root(&Sha2Hasher);
962        assert_eq!(&root[..7], &arr[..]);
963        assert_eq!(&root[7..], &[0u8; 25]);
964        assert!(<[u8; 7]>::is_basic_type());
965    }
966
967    #[cfg(feature = "sha2-backend")]
968    #[test]
969    fn hash_tree_root_byte_array_48_is_merkleized() {
970        let arr = [0xab_u8; 48];
971        let root = arr.hash_tree_root(&Sha2Hasher);
972        // 48 bytes = 2 chunks (32 + 16 zero-padded), merkleize
973        let expected = merkleize(&Sha2Hasher, &pack(&arr), None);
974        assert_eq!(root, expected);
975        assert!(!<[u8; 48]>::is_basic_type());
976    }
977
978    #[cfg(feature = "sha2-backend")]
979    #[test]
980    fn hash_tree_root_byte_array_52_is_merkleized() {
981        let arr = [0xcd_u8; 52];
982        let root = arr.hash_tree_root(&Sha2Hasher);
983        let expected = merkleize(&Sha2Hasher, &pack(&arr), None);
984        assert_eq!(root, expected);
985        assert!(!<[u8; 52]>::is_basic_type());
986    }
987
988    #[cfg(feature = "sha2-backend")]
989    #[test]
990    fn hash_tree_root_byte_array_96_is_merkleized() {
991        let arr = [0xff_u8; 96];
992        let root = arr.hash_tree_root(&Sha2Hasher);
993        let expected = merkleize(&Sha2Hasher, &pack(&arr), None);
994        assert_eq!(root, expected);
995        assert!(!<[u8; 96]>::is_basic_type());
996    }
997
998    #[cfg(feature = "sha2-backend")]
999    #[test]
1000    fn hash_tree_root_vec_u64_empty() {
1001        let val: Vec<u64> = vec![];
1002        let result = val.hash_tree_root(&Sha2Hasher);
1003        // Empty list: merkleize([zero]) then mix_in_length with 0
1004        let root = merkleize(&Sha2Hasher, &[ZERO_HASHES[0]], None);
1005        assert_eq!(result, mix_in_length(&Sha2Hasher, &root, 0));
1006    }
1007
1008    #[cfg(feature = "sha2-backend")]
1009    #[test]
1010    fn hash_tree_root_vec_u64() {
1011        let val: Vec<u64> = vec![1, 2, 3, 4];
1012        let result = val.hash_tree_root(&Sha2Hasher);
1013
1014        // 4 u64s = 32 bytes = 1 chunk
1015        let mut serialized = Vec::new();
1016        for v in &val {
1017            serialized.extend_from_slice(&v.to_le_bytes());
1018        }
1019        let chunks = pack(&serialized);
1020        let root = merkleize(&Sha2Hasher, &chunks, None);
1021        assert_eq!(result, mix_in_length(&Sha2Hasher, &root, 4));
1022    }
1023
1024    #[cfg(feature = "sha2-backend")]
1025    #[test]
1026    fn hash_tree_root_vec_u64_five_elements() {
1027        let val: Vec<u64> = vec![1, 2, 3, 4, 5];
1028        let result = val.hash_tree_root(&Sha2Hasher);
1029
1030        // 5 u64s = 40 bytes = 2 chunks
1031        let mut serialized = Vec::new();
1032        for v in &val {
1033            serialized.extend_from_slice(&v.to_le_bytes());
1034        }
1035        let chunks = pack(&serialized);
1036        assert_eq!(chunks.len(), 2);
1037        let root = merkleize(&Sha2Hasher, &chunks, None);
1038        assert_eq!(result, mix_in_length(&Sha2Hasher, &root, 5));
1039    }
1040
1041    #[cfg(feature = "sha2-backend")]
1042    #[test]
1043    fn custom_hasher_works() {
1044        struct MockHasher;
1045        impl Sha256Hasher for MockHasher {
1046            fn hash(&self, data: &[u8]) -> [u8; 32] {
1047                let mut out = [0u8; 32];
1048                for (i, &b) in data.iter().enumerate() {
1049                    out[i % 32] ^= b;
1050                }
1051                out
1052            }
1053        }
1054        let val: u64 = 42;
1055        let root_sha2 = val.hash_tree_root(&Sha2Hasher);
1056        let root_mock = val.hash_tree_root(&MockHasher);
1057        // u64 HTR doesn't use hashing (just pads to 32 bytes), so both should be equal
1058        assert_eq!(root_sha2, root_mock);
1059
1060        // For a type that DOES hash, they should differ
1061        let arr = [0xab_u8; 48];
1062        let root_sha2 = arr.hash_tree_root(&Sha2Hasher);
1063        let root_mock = arr.hash_tree_root(&MockHasher);
1064        assert_ne!(root_sha2, root_mock);
1065    }
1066}