merkle_core/traits/hash_function.rs
1//! # merkle-core :: traits :: `hash_function`
2//!
3//! The `HashFunction` trait is the **pluggable crypto abstraction** at the
4//! heart of the library. By parameterising every tree type over
5//! `H: HashFunction`, callers can swap SHA-256 for BLAKE3 with zero changes
6//! to tree logic and zero runtime cost (Rust monomorphises the generics).
7//!
8//! ## Implementing `HashFunction`
9//!
10//! ```rust,ignore
11//! use merkle_core::traits::HashFunction;
12//!
13//! pub struct MyHash;
14//!
15//! impl HashFunction for MyHash {
16//! type Digest = [u8; 32];
17//!
18//! fn hash(data: &[u8]) -> Self::Digest { /* ... */ }
19//!
20//! fn hash_nodes(left: &Self::Digest, right: &Self::Digest) -> Self::Digest {
21//! let mut combined = Vec::with_capacity(64);
22//! combined.extend_from_slice(left);
23//! combined.extend_from_slice(right);
24//! Self::hash(&combined)
25//! }
26//!
27//! fn algorithm_name() -> &'static str { "MyHash" }
28//! }
29//! ```
30
31use std::fmt::Debug;
32
33/// Abstraction over a cryptographic hash function used by Merkle trees.
34///
35/// # Type parameter `Digest`
36/// The associated type `Digest` represents a fixed-size hash output. It
37/// must be:
38/// - `Clone` — nodes are cloned when building the tree.
39/// - `PartialEq + Eq` — so roots and proof hashes can be compared.
40/// - `AsRef<[u8]>` — for serialisation and concatenation before hashing.
41/// - `Debug` — for test output and diagnostic messages.
42/// - `Send + Sync` — so trees can be used across thread boundaries.
43///
44/// # Zero-cost abstractions
45/// All methods are `#[inline]` by convention in implementors. Rust's
46/// monomorphisation ensures that no vtable dispatch overhead occurs when
47/// calling through `H: HashFunction` bounds.
48pub trait HashFunction: Send + Sync + 'static {
49 /// The concrete digest type produced by this hash function.
50 type Digest: AsRef<[u8]> + Clone + Debug + PartialEq + Eq + Send + Sync + 'static;
51
52 // ── Core hashing ──────────────────────────────────────────────────────
53
54 /// Hash arbitrary bytes and return the digest.
55 #[must_use]
56 fn hash(data: &[u8]) -> Self::Digest;
57
58 /// Hash two child digests together to produce a parent digest.
59 ///
60 /// The default implementation concatenates `left || right` and calls
61 /// `Self::hash`. Override this to use domain separation or a different
62 /// concatenation strategy.
63 #[must_use]
64 fn hash_nodes(left: &Self::Digest, right: &Self::Digest) -> Self::Digest {
65 let mut combined = Vec::with_capacity(left.as_ref().len() + right.as_ref().len());
66 combined.extend_from_slice(left.as_ref());
67 combined.extend_from_slice(right.as_ref());
68 Self::hash(&combined)
69 }
70
71 /// Return the canonical "empty" digest used to pad trees to a
72 /// power-of-two size, or to represent vacant slots in sparse trees.
73 ///
74 /// The default is the hash of a zero-length byte slice.
75 #[must_use]
76 fn empty() -> Self::Digest {
77 Self::hash(&[])
78 }
79
80 // ── Metadata ──────────────────────────────────────────────────────────
81
82 /// Human-readable name of the algorithm, e.g. `"SHA-256"` or `"BLAKE3"`.
83 #[must_use]
84 fn algorithm_name() -> &'static str;
85
86 /// Output size of the digest in bytes.
87 #[must_use]
88 fn digest_size() -> usize;
89}
90
91// ── Tests ──────────────────────────────────────────────────────────────────
92
93#[cfg(test)]
94mod tests {
95 /// A minimal stub `HashFunction` used only in unit tests so that
96 /// `merkle-core` has no dependency on `merkle-hash` at test time.
97 use super::*;
98
99 struct Xor8;
100
101 impl HashFunction for Xor8 {
102 type Digest = [u8; 1];
103
104 fn hash(data: &[u8]) -> [u8; 1] {
105 [data.iter().fold(0u8, |acc, &b| acc ^ b)]
106 }
107
108 fn algorithm_name() -> &'static str {
109 "XOR8"
110 }
111
112 fn digest_size() -> usize {
113 1
114 }
115 }
116
117 #[test]
118 fn hash_deterministic() {
119 let a = Xor8::hash(b"hello");
120 let b = Xor8::hash(b"hello");
121 assert_eq!(a, b);
122 }
123
124 #[test]
125 fn hash_nodes_uses_concatenation() {
126 let l = Xor8::hash(b"left");
127 let r = Xor8::hash(b"right");
128 let parent = Xor8::hash_nodes(&l, &r);
129 // Manually: xor(l, r) because XOR8(a || b) = XOR(a, b) for single bytes
130 let expected = [l[0] ^ r[0]];
131 assert_eq!(parent, expected);
132 }
133
134 #[test]
135 fn empty_is_hash_of_empty_slice() {
136 assert_eq!(Xor8::empty(), Xor8::hash(&[]));
137 }
138
139 #[test]
140 fn metadata() {
141 assert_eq!(Xor8::algorithm_name(), "XOR8");
142 assert_eq!(Xor8::digest_size(), 1);
143 }
144}