Skip to main content

merkleforge_hash/
keccak256.rs

1//! # merkle-hash :: keccak256
2//!
3//! Keccak-256 adapter for the [`HashFunction`] trait.
4//!
5//! Keccak-256 is the hash function used throughout the Ethereum Virtual
6//! Machine.  It is *not* identical to the NIST SHA-3 standard (they differ
7//! in the padding), which is why we use the `tiny-keccak` crate rather than
8//! a generic SHA-3 crate.  This adapter is the correct choice for any tree
9//! variant that needs to produce state roots compatible with Ethereum tooling
10//! — most importantly the Merkle Patricia Trie.
11//!
12//! ## Usage
13//! ```rust,ignore
14//! use merkle_hash::Keccak256;
15//! use merkle_core::traits::HashFunction;
16//!
17//! let digest = Keccak256::hash(b"hello merkle");
18//! assert_eq!(digest.len(), 32);
19//! ```
20
21use merkle_core::traits::HashFunction;
22use tiny_keccak::{Hasher, Keccak};
23
24/// Keccak-256 implementation of [`HashFunction`].
25///
26/// Produces 32-byte digests identical to those computed by
27/// `web3.utils.keccak256` and Solidity's `keccak256()` built-in.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct Keccak256;
30
31impl Keccak256 {
32    /// Internal helper that returns a new `Keccak` hasher ready for output.
33    #[inline]
34    fn new_hasher() -> Keccak {
35        Keccak::v256()
36    }
37}
38
39impl HashFunction for Keccak256 {
40    type Digest = [u8; 32];
41
42    /// Leaf hashing: `Keccak-256(0x00 || data)`.
43    #[inline]
44    fn hash(data: &[u8]) -> [u8; 32] {
45        let mut out = [0u8; 32];
46        let mut h = Self::new_hasher();
47        h.update(&[0x00]); // leaf domain separator
48        h.update(data);
49        h.finalize(&mut out);
50        out
51    }
52
53    /// Internal node hashing: `Keccak-256(0x01 || left || right)`.
54    #[inline]
55    fn hash_nodes(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
56        let mut out = [0u8; 32];
57        let mut h = Self::new_hasher();
58        h.update(&[0x01]); // internal-node domain separator
59        h.update(left);
60        h.update(right);
61        h.finalize(&mut out);
62        out
63    }
64
65    fn algorithm_name() -> &'static str {
66        "Keccak-256"
67    }
68
69    fn digest_size() -> usize {
70        32
71    }
72}
73
74// ── Tests ──────────────────────────────────────────────────────────────────
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use merkle_core::traits::HashFunction;
80
81    /// Keccak-256("") =
82    /// c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
83    #[test]
84    fn known_vector_empty_string() {
85        // Raw Keccak-256("") — no domain prefix.
86        let mut out = [0u8; 32];
87        let mut h = Keccak::v256();
88        h.update(b"");
89        h.finalize(&mut out);
90        let expected = [
91            0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7,
92            0x03, 0xc0, 0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04,
93            0x5d, 0x85, 0xa4, 0x70,
94        ];
95        assert_eq!(out, expected);
96    }
97
98    #[test]
99    fn digest_size_is_32() {
100        assert_eq!(Keccak256::digest_size(), 32);
101    }
102
103    #[test]
104    fn algorithm_name() {
105        assert_eq!(Keccak256::algorithm_name(), "Keccak-256");
106    }
107
108    #[test]
109    fn hash_deterministic() {
110        assert_eq!(Keccak256::hash(b"test"), Keccak256::hash(b"test"));
111    }
112
113    #[test]
114    fn hash_nodes_non_commutative() {
115        let a = [0xAAu8; 32];
116        let b = [0xBBu8; 32];
117        assert_ne!(Keccak256::hash_nodes(&a, &b), Keccak256::hash_nodes(&b, &a));
118    }
119
120    #[test]
121    fn hash_and_hash_nodes_differ() {
122        // A leaf hash and an internal-node hash of the same bytes must differ.
123        let data = [0x42u8; 32];
124        let leaf = Keccak256::hash(&data);
125        let node = Keccak256::hash_nodes(&data, &data);
126        assert_ne!(leaf, node);
127    }
128}