use merkle_core::traits::HashFunction;
const LEAF_CONTEXT: &str = "MerkleForge 2026 leaf v1";
const NODE_CONTEXT: &str = "MerkleForge 2026 internal-node v1";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Blake3;
impl HashFunction for Blake3 {
type Digest = [u8; 32];
#[inline]
fn hash(data: &[u8]) -> [u8; 32] {
blake3::derive_key(LEAF_CONTEXT, data)
}
#[inline]
fn hash_nodes(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
let mut combined = [0u8; 64];
combined[..32].copy_from_slice(left);
combined[32..].copy_from_slice(right);
blake3::derive_key(NODE_CONTEXT, &combined)
}
fn empty() -> [u8; 32] {
blake3::derive_key(LEAF_CONTEXT, b"")
}
fn algorithm_name() -> &'static str {
"BLAKE3"
}
fn digest_size() -> usize {
32
}
}
#[cfg(test)]
mod tests {
use super::*;
use merkle_core::traits::HashFunction;
#[test]
fn digest_size_is_32() {
assert_eq!(Blake3::digest_size(), 32);
}
#[test]
fn algorithm_name() {
assert_eq!(Blake3::algorithm_name(), "BLAKE3");
}
#[test]
fn hash_deterministic() {
assert_eq!(Blake3::hash(b"hello"), Blake3::hash(b"hello"));
}
#[test]
fn hash_nodes_non_commutative() {
let a = [0xAAu8; 32];
let b = [0xBBu8; 32];
assert_ne!(Blake3::hash_nodes(&a, &b), Blake3::hash_nodes(&b, &a));
}
#[test]
fn leaf_and_node_contexts_differ() {
let data = [0x42u8; 32];
let leaf = Blake3::hash(&data);
let node = Blake3::hash_nodes(&data, &data);
assert_ne!(leaf, node);
}
#[test]
fn empty_matches_runtime_computation() {
assert_eq!(Blake3::empty(), blake3::derive_key(LEAF_CONTEXT, b""));
}
#[test]
fn different_inputs_give_different_digests() {
let a = Blake3::hash(b"alice");
let b = Blake3::hash(b"bob");
assert_ne!(a, b);
}
#[test]
fn hash_nodes_avalanche() {
let mut left = [0u8; 32];
let right = [0u8; 32];
let parent_a = Blake3::hash_nodes(&left, &right);
left[0] ^= 1;
let parent_b = Blake3::hash_nodes(&left, &right);
assert_ne!(parent_a, parent_b);
}
}