merkleforge_hash/blake3.rs
1//! # merkle-hash :: blake3
2//!
3//! BLAKE3 adapter for the [`HashFunction`] trait.
4//!
5//! BLAKE3 is the fastest secure cryptographic hash available on modern
6//! hardware — benchmarks consistently show it outpacing SHA-256 by 3–10×
7//! on software paths, while also scaling across CPU cores via internal
8//! tree-parallelism. BLAKE3 natively supports keyed hashing and
9//! context strings, making domain separation trivial without extra length
10//! overhead.
11//!
12//! The proposal benchmarking suite (Phase 5) will produce comparative data
13//! between SHA-256, Keccak-256, and BLAKE3 to guide algorithm selection
14//! for specific hardware environments.
15//!
16//! ## Usage
17//! ```rust,ignore
18//! use merkle_hash::Blake3;
19//! use merkle_core::traits::HashFunction;
20//!
21//! let digest = Blake3::hash(b"hello merkle");
22//! assert_eq!(digest.len(), 32);
23//! ```
24
25use merkle_core::traits::HashFunction;
26
27/// Leaf domain-separation context string.
28const LEAF_CONTEXT: &str = "MerkleForge 2026 leaf v1";
29/// Internal node domain-separation context string.
30const NODE_CONTEXT: &str = "MerkleForge 2026 internal-node v1";
31
32/// BLAKE3 implementation of [`HashFunction`].
33///
34/// Uses BLAKE3's built-in **keyed derivation** (`blake3::derive_key`) for
35/// domain separation, which is more efficient and cryptographically cleaner
36/// than prepending a prefix byte.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct Blake3;
39
40impl HashFunction for Blake3 {
41 type Digest = [u8; 32];
42
43 /// Leaf hashing using BLAKE3's derive-key mode with context `"MerkleForge 2026 leaf v1"`.
44 #[inline]
45 fn hash(data: &[u8]) -> [u8; 32] {
46 blake3::derive_key(LEAF_CONTEXT, data)
47 }
48
49 /// Internal node hashing using BLAKE3's derive-key mode with context
50 /// `"MerkleForge 2026 internal-node v1"` applied to `left || right`.
51 #[inline]
52 fn hash_nodes(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
53 let mut combined = [0u8; 64];
54 combined[..32].copy_from_slice(left);
55 combined[32..].copy_from_slice(right);
56 blake3::derive_key(NODE_CONTEXT, &combined)
57 }
58
59 /// The canonical empty-leaf hash: `BLAKE3_leaf("")`.
60 ///
61 /// Pre-computed for the default context string — avoids a function call
62 /// on every empty-slot lookup in sparse trees.
63 fn empty() -> [u8; 32] {
64 blake3::derive_key(LEAF_CONTEXT, b"")
65 }
66
67 fn algorithm_name() -> &'static str {
68 "BLAKE3"
69 }
70
71 fn digest_size() -> usize {
72 32
73 }
74}
75
76// ── Tests ──────────────────────────────────────────────────────────────────
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use merkle_core::traits::HashFunction;
82
83 #[test]
84 fn digest_size_is_32() {
85 assert_eq!(Blake3::digest_size(), 32);
86 }
87
88 #[test]
89 fn algorithm_name() {
90 assert_eq!(Blake3::algorithm_name(), "BLAKE3");
91 }
92
93 #[test]
94 fn hash_deterministic() {
95 assert_eq!(Blake3::hash(b"hello"), Blake3::hash(b"hello"));
96 }
97
98 #[test]
99 fn hash_nodes_non_commutative() {
100 let a = [0xAAu8; 32];
101 let b = [0xBBu8; 32];
102 assert_ne!(Blake3::hash_nodes(&a, &b), Blake3::hash_nodes(&b, &a));
103 }
104
105 #[test]
106 fn leaf_and_node_contexts_differ() {
107 // Even with the same input bytes, domain separation must give different digests.
108 let data = [0x42u8; 32];
109 let leaf = Blake3::hash(&data);
110 let node = Blake3::hash_nodes(&data, &data);
111 assert_ne!(leaf, node);
112 }
113
114 #[test]
115 fn empty_matches_runtime_computation() {
116 assert_eq!(Blake3::empty(), blake3::derive_key(LEAF_CONTEXT, b""));
117 }
118
119 #[test]
120 fn different_inputs_give_different_digests() {
121 let a = Blake3::hash(b"alice");
122 let b = Blake3::hash(b"bob");
123 assert_ne!(a, b);
124 }
125
126 #[test]
127 fn hash_nodes_avalanche() {
128 // A one-bit change in a child must change the parent.
129 let mut left = [0u8; 32];
130 let right = [0u8; 32];
131 let parent_a = Blake3::hash_nodes(&left, &right);
132 left[0] ^= 1;
133 let parent_b = Blake3::hash_nodes(&left, &right);
134 assert_ne!(parent_a, parent_b);
135 }
136}