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}