Skip to main content

merkleforge_hash/
sha256.rs

1//! # merkle-hash :: sha256
2//!
3//! SHA-256 adapter for the [`HashFunction`] trait.
4//!
5//! SHA-256 is the most widely deployed hash function in blockchain systems.
6//! It benefits from hardware acceleration (Intel SHA Extensions, ARM SHA)
7//! which can yield a ~50% speed boost over software implementations on
8//! supported CPUs (Drake, 2019).  This makes it a strong default for
9//! production deployments that run on modern server hardware.
10//!
11//! ## Usage
12//! ```rust,ignore
13//! use merkle_hash::Sha256;
14//! use merkle_core::traits::HashFunction;
15//!
16//! let digest = Sha256::hash(b"hello merkle");
17//! assert_eq!(digest.len(), 32);
18//! println!("SHA-256 name: {}", Sha256::algorithm_name());
19//! ```
20
21use merkle_core::traits::HashFunction;
22use sha2::{Digest as Sha2Digest, Sha256 as Sha2_256};
23
24/// SHA-256 implementation of [`HashFunction`].
25///
26/// Produces 32-byte digests.  Hardware-accelerated on x86-64 CPUs with the
27/// SHA or AVX2 extension (detected at compile time by the `sha2` crate).
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct Sha256;
30
31impl HashFunction for Sha256 {
32    /// 32-byte fixed-size digest.
33    type Digest = [u8; 32];
34
35    /// Domain-separated node hashing: `SHA-256(0x01 || left || right)`.
36    ///
37    /// The `0x01` prefix distinguishes internal nodes from leaf hashes
38    /// (`0x00` prefix), preventing second-preimage attacks where an attacker
39    /// could substitute an internal node for a leaf.
40    #[inline]
41    fn hash_nodes(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
42        let mut hasher = Sha2_256::new();
43        hasher.update([0x01]); // internal-node domain separator
44        hasher.update(left);
45        hasher.update(right);
46        hasher.finalize().into()
47    }
48
49    /// Leaf hashing: `SHA-256(0x00 || data)`.
50    ///
51    /// The `0x00` prefix distinguishes leaf hashes from internal-node hashes.
52    /// Callers that hash leaf data themselves should use this for correctness,
53    /// but the tree implementations call it internally so most users don't
54    /// need to know about it.
55    #[inline]
56    fn hash(data: &[u8]) -> [u8; 32] {
57        let mut hasher = Sha2_256::new();
58        hasher.update([0x00]); // Leaf domain separator
59        hasher.update(data);
60        hasher.finalize().into()
61    }
62
63    #[inline]
64    fn empty() -> [u8; 32] {
65        // Pre-computed: SHA-256(0x00) — the canonical empty-leaf hash.
66        // This avoids a runtime hash call every time we need the sentinel.
67        [
68            0x6e, 0x34, 0x0b, 0x9c, 0xff, 0xb3, 0x7a, 0x98, 0x9c, 0xa5, 0x44, 0xe6, 0xbb, 0x78,
69            0x0a, 0x2c, 0x78, 0x90, 0x1d, 0x3f, 0xb3, 0x37, 0x38, 0x76, 0x85, 0x11, 0xa3, 0x06,
70            0x17, 0xaf, 0xa0, 0x1d,
71        ]
72    }
73
74    fn algorithm_name() -> &'static str {
75        "SHA-256"
76    }
77
78    fn digest_size() -> usize {
79        32
80    }
81}
82
83// ── Tests ──────────────────────────────────────────────────────────────────
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    /// SHA-256(0x00) — The domain-separated empty leaf hash for MerkleForge.
90    const MERKLE_EMPTY_SENTINEL: [u8; 32] = [
91        0x6e, 0x34, 0x0b, 0x9c, 0xff, 0xb3, 0x7a, 0x98, 0x9c, 0xa5, 0x44, 0xe6, 0xbb, 0x78, 0x0a,
92        0x2c, 0x78, 0x90, 0x1d, 0x3f, 0xb3, 0x37, 0x38, 0x76, 0x85, 0x11, 0xa3, 0x06, 0x17, 0xaf,
93        0xa0, 0x1d,
94    ];
95
96    #[test]
97    fn digest_size_is_32() {
98        assert_eq!(Sha256::digest_size(), 32);
99    }
100
101    #[test]
102    fn algorithm_name() {
103        assert_eq!(Sha256::algorithm_name(), "SHA-256");
104    }
105
106    #[test]
107    fn hash_is_deterministic() {
108        let input = b"merkleforge-test";
109        assert_eq!(Sha256::hash(input), Sha256::hash(input));
110    }
111
112    #[test]
113    fn empty_matches_precomputed() {
114        // This ensures the library's precomputed constant matches a fresh runtime hash of the 0x00 prefix.
115        let mut h = Sha2_256::new();
116        h.update([0x00u8]);
117        let manual_compute: [u8; 32] = h.finalize().into();
118
119        assert_eq!(
120            Sha256::empty(),
121            manual_compute,
122            "Precomputed empty() must match SHA-256(0x00)"
123        );
124        assert_eq!(Sha256::empty(), MERKLE_EMPTY_SENTINEL);
125    }
126
127    #[test]
128    fn hash_nodes_non_commutative() {
129        let a = [0x11u8; 32];
130        let b = [0x22u8; 32];
131        assert_ne!(Sha256::hash_nodes(&a, &b), Sha256::hash_nodes(&b, &a));
132    }
133
134    #[test]
135    fn leaf_and_node_domain_separation() {
136        let data = [0xAAu8; 32];
137        let dummy = [0x00u8; 32];
138
139        // hash(data) uses 0x00 prefix; hash_nodes(data, dummy) uses 0x01 prefix.
140        // Even if the input starts similarly, the prefixes ensure the digests differ.
141        let leaf_h = Sha256::hash(&data);
142        let node_h = Sha256::hash_nodes(&data, &dummy);
143
144        assert_ne!(
145            leaf_h, node_h,
146            "Leaf and Internal Node hashes must be domain-separated"
147        );
148    }
149
150    #[test]
151    fn different_inputs_different_hashes() {
152        assert_ne!(Sha256::hash(b"leaf1"), Sha256::hash(b"leaf2"));
153    }
154}