Skip to main content

crypt_io/hash/
blake3_impl.rs

1//! BLAKE3 backend.
2//!
3//! Thin wrapper over the `blake3` crate. BLAKE3 is the default hash for
4//! `crypt-io`: fastest cryptographic hash on every modern platform, with a
5//! 32-byte default output and an extendable-output (XOF) mode for arbitrary
6//! lengths.
7
8use alloc::vec::Vec;
9
10use super::BLAKE3_OUTPUT_LEN;
11
12/// Compute a 32-byte BLAKE3 digest of `data`.
13///
14/// # Example
15///
16/// ```
17/// # #[cfg(feature = "hash-blake3")] {
18/// use crypt_io::hash;
19/// let d = hash::blake3(b"abc");
20/// assert_eq!(d.len(), 32);
21/// # }
22/// ```
23#[must_use]
24pub fn blake3(data: &[u8]) -> [u8; BLAKE3_OUTPUT_LEN] {
25    *::blake3::hash(data).as_bytes()
26}
27
28/// Compute a BLAKE3 digest of arbitrary length via the extendable-output
29/// function (XOF) mode.
30///
31/// `len` may be any value, including zero. The output is deterministic in
32/// `data` — different inputs produce uncorrelated outputs, identical inputs
33/// produce identical outputs.
34///
35/// For the common 32-byte case prefer [`blake3()`] — it skips the XOF reader
36/// path entirely.
37///
38/// # Example
39///
40/// ```
41/// # #[cfg(feature = "hash-blake3")] {
42/// use crypt_io::hash;
43/// let d = hash::blake3_long(b"abc", 64);
44/// assert_eq!(d.len(), 64);
45/// # }
46/// ```
47#[must_use]
48pub fn blake3_long(data: &[u8], len: usize) -> Vec<u8> {
49    let mut hasher = ::blake3::Hasher::new();
50    let _ = hasher.update(data);
51    let mut out = alloc::vec![0u8; len];
52    let mut reader = hasher.finalize_xof();
53    reader.fill(&mut out);
54    out
55}
56
57/// Streaming BLAKE3 hasher for inputs that don't fit in memory or arrive
58/// in chunks.
59///
60/// Construct with [`Blake3Hasher::new`], feed data with
61/// [`update`](Self::update), and finalise with [`finalize`](Self::finalize)
62/// (returns the default 32-byte digest) or
63/// [`finalize_xof`](Self::finalize_xof) (returns an arbitrary-length
64/// digest).
65///
66/// `update` can be called any number of times; the hasher is consumed by
67/// finalisation.
68///
69/// # Example
70///
71/// ```
72/// # #[cfg(feature = "hash-blake3")] {
73/// use crypt_io::hash::Blake3Hasher;
74///
75/// let mut h = Blake3Hasher::new();
76/// h.update(b"first ");
77/// h.update(b"second");
78/// let d = h.finalize();
79/// assert_eq!(d.len(), 32);
80/// # }
81/// ```
82#[derive(Debug, Clone, Default)]
83pub struct Blake3Hasher {
84    inner: ::blake3::Hasher,
85}
86
87impl Blake3Hasher {
88    /// Construct a fresh hasher.
89    #[must_use]
90    pub fn new() -> Self {
91        Self {
92            inner: ::blake3::Hasher::new(),
93        }
94    }
95
96    /// Absorb `data` into the running hash. Returns `&mut Self` so calls
97    /// can chain.
98    pub fn update(&mut self, data: &[u8]) -> &mut Self {
99        let _ = self.inner.update(data);
100        self
101    }
102
103    /// Finalise the hash and return a 32-byte digest. Consumes the hasher.
104    #[must_use]
105    pub fn finalize(self) -> [u8; BLAKE3_OUTPUT_LEN] {
106        *self.inner.finalize().as_bytes()
107    }
108
109    /// Finalise the hash and return `len` bytes of XOF output. Consumes the
110    /// hasher.
111    #[must_use]
112    pub fn finalize_xof(self, len: usize) -> Vec<u8> {
113        let mut out = alloc::vec![0u8; len];
114        let mut reader = self.inner.finalize_xof();
115        reader.fill(&mut out);
116        out
117    }
118}
119
120#[cfg(test)]
121#[allow(clippy::unwrap_used, clippy::expect_used, unused_results)]
122mod tests {
123    use super::*;
124
125    // BLAKE3 official test vectors. The canonical reference is the
126    // `BLAKE3-team/BLAKE3` repository's `test_vectors.json`. The vectors
127    // below are the unkeyed hashes for the well-known short inputs.
128    //
129    // Empty input — the first vector in every published test set.
130    const KAT_EMPTY: [u8; 32] = [
131        0xaf, 0x13, 0x49, 0xb9, 0xf5, 0xf9, 0xa1, 0xa6, 0xa0, 0x40, 0x4d, 0xea, 0x36, 0xdc, 0xc9,
132        0x49, 0x9b, 0xcb, 0x25, 0xc9, 0xad, 0xc1, 0x12, 0xb7, 0xcc, 0x9a, 0x93, 0xca, 0xe4, 0x1f,
133        0x32, 0x62,
134    ];
135
136    // "IETF" — 4-byte ASCII input. Value computed against the upstream
137    // `blake3` crate and asserted byte-for-byte so we catch any future
138    // wrapper-level mistake.
139    const KAT_IETF: [u8; 32] = [
140        0x83, 0xa2, 0xde, 0x1e, 0xe6, 0xf4, 0xe6, 0xab, 0x68, 0x68, 0x89, 0x24, 0x8f, 0x4e, 0xc0,
141        0xcf, 0x4c, 0xc5, 0x70, 0x94, 0x46, 0xa6, 0x82, 0xff, 0xd1, 0xcb, 0xb4, 0xd6, 0x16, 0x51,
142        0x81, 0xe2,
143    ];
144
145    #[test]
146    fn kat_empty() {
147        assert_eq!(blake3(b""), KAT_EMPTY);
148    }
149
150    #[test]
151    fn kat_ietf() {
152        assert_eq!(blake3(b"IETF"), KAT_IETF);
153    }
154
155    #[test]
156    fn xof_length_matches_request() {
157        for n in [0usize, 1, 16, 32, 64, 128, 1024] {
158            assert_eq!(blake3_long(b"input", n).len(), n);
159        }
160    }
161
162    #[test]
163    fn xof_is_deterministic_in_input() {
164        let a = blake3_long(b"same", 64);
165        let b = blake3_long(b"same", 64);
166        assert_eq!(a, b);
167        let c = blake3_long(b"diff", 64);
168        assert_ne!(a, c);
169    }
170
171    #[test]
172    fn xof_extends_default_output() {
173        // BLAKE3 XOF mode is a superset of the default hash: the first
174        // 32 bytes of `blake3_long(data, 32+N)` match `blake3(data)`.
175        let extended = blake3_long(b"foo bar", 64);
176        let short = blake3(b"foo bar");
177        assert_eq!(&extended[..32], &short[..]);
178    }
179
180    #[test]
181    fn streaming_equals_one_shot() {
182        let data = b"the quick brown fox jumps over the lazy dog";
183        let one_shot = blake3(data);
184        let mut h = Blake3Hasher::new();
185        h.update(&data[..10]);
186        h.update(&data[10..25]);
187        h.update(&data[25..]);
188        let streamed = h.finalize();
189        assert_eq!(one_shot, streamed);
190    }
191
192    #[test]
193    fn streaming_chain_returns_self() {
194        let data = b"chain-friendly";
195        let mut h = Blake3Hasher::new();
196        let _ret: &mut Blake3Hasher = h.update(b"chain").update(b"-friendly");
197        let d = blake3(data);
198        let mut h2 = Blake3Hasher::new();
199        h2.update(b"chain").update(b"-friendly");
200        assert_eq!(h2.finalize(), d);
201    }
202
203    #[test]
204    fn streaming_xof_finalize_matches_one_shot_long() {
205        let data = b"streaming-xof";
206        let one_shot = blake3_long(data, 100);
207        let mut h = Blake3Hasher::new();
208        h.update(data);
209        let streamed = h.finalize_xof(100);
210        assert_eq!(one_shot, streamed);
211    }
212
213    #[test]
214    fn empty_input_through_streaming() {
215        let mut h = Blake3Hasher::new();
216        let _ = h.update(b"");
217        assert_eq!(h.finalize(), KAT_EMPTY);
218    }
219}