Skip to main content

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}