commonware_cryptography/blake3/
mod.rs

1//! BLAKE3 implementation of the [Hasher] trait.
2//!
3//! This implementation uses the [blake3] crate to generate BLAKE3 digests.
4//!
5//! # Example
6//! ```rust
7//! use commonware_cryptography::{Hasher, blake3::Blake3};
8//!
9//! // Create a new BLAKE3 hasher
10//! let mut hasher = Blake3::new();
11//!
12//! // Update the hasher with some messages
13//! hasher.update(b"hello,");
14//! hasher.update(b"world!");
15//!
16//! // Finalize the hasher to get the digest
17//! let digest = hasher.finalize();
18//!
19//! // Print the digest
20//! println!("digest: {:?}", digest);
21//! ```
22
23use crate::Hasher;
24use blake3::Hash;
25use bytes::{Buf, BufMut};
26use commonware_codec::{Error as CodecError, FixedSize, Read, ReadExt, Write};
27use commonware_utils::{hex, Array, Span};
28use rand::{CryptoRng, Rng};
29use std::{
30    fmt::{Debug, Display},
31    ops::Deref,
32};
33use zeroize::Zeroize;
34
35/// Re-export [blake3::Hasher] as `CoreBlake3` for external use if needed.
36pub type CoreBlake3 = blake3::Hasher;
37
38const DIGEST_LENGTH: usize = blake3::OUT_LEN;
39
40/// Generate a BLAKE3 [Digest] from a message.
41pub fn hash(message: &[u8]) -> Digest {
42    let mut hasher = Blake3::new();
43    hasher.update(message);
44    hasher.finalize()
45}
46
47/// BLAKE3 hasher.
48#[cfg_attr(
49    feature = "parallel-blake3",
50    doc = "When the input message is larger than 128KiB, `rayon` is used to parallelize hashing."
51)]
52#[derive(Debug, Default)]
53pub struct Blake3 {
54    hasher: CoreBlake3,
55}
56
57impl Clone for Blake3 {
58    fn clone(&self) -> Self {
59        // We manually implement `Clone` to avoid cloning the hasher state.
60        Self::default()
61    }
62}
63
64impl Hasher for Blake3 {
65    type Digest = Digest;
66
67    fn new() -> Self {
68        Self {
69            hasher: CoreBlake3::new(),
70        }
71    }
72
73    fn update(&mut self, message: &[u8]) {
74        #[cfg(not(feature = "parallel-blake3"))]
75        self.hasher.update(message);
76
77        #[cfg(feature = "parallel-blake3")]
78        {
79            // 128 KiB
80            const PARALLEL_THRESHOLD: usize = 2usize.pow(17);
81
82            // Heuristic defined @ https://docs.rs/blake3/latest/blake3/struct.Hasher.html#method.update_rayon
83            if message.len() >= PARALLEL_THRESHOLD {
84                self.hasher.update_rayon(message);
85            } else {
86                self.hasher.update(message);
87            }
88        }
89    }
90
91    fn finalize(&mut self) -> Self::Digest {
92        let finalized = self.hasher.finalize();
93        self.hasher.reset();
94        let array: [u8; DIGEST_LENGTH] = finalized.into();
95        Self::Digest::from(array)
96    }
97
98    fn reset(&mut self) {
99        self.hasher = CoreBlake3::new();
100    }
101
102    fn empty() -> Self::Digest {
103        Self::default().finalize()
104    }
105}
106
107/// Digest of a BLAKE3 hashing operation.
108#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
109#[repr(transparent)]
110pub struct Digest(pub [u8; DIGEST_LENGTH]);
111
112impl Write for Digest {
113    fn write(&self, buf: &mut impl BufMut) {
114        self.0.write(buf);
115    }
116}
117
118impl Read for Digest {
119    type Cfg = ();
120
121    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
122        let array = <[u8; DIGEST_LENGTH]>::read(buf)?;
123        Ok(Self(array))
124    }
125}
126
127impl FixedSize for Digest {
128    const SIZE: usize = DIGEST_LENGTH;
129}
130
131impl Span for Digest {}
132
133impl Array for Digest {}
134
135impl From<Hash> for Digest {
136    fn from(value: Hash) -> Self {
137        Self(value.into())
138    }
139}
140
141impl From<[u8; DIGEST_LENGTH]> for Digest {
142    fn from(value: [u8; DIGEST_LENGTH]) -> Self {
143        Self(value)
144    }
145}
146
147impl AsRef<[u8]> for Digest {
148    fn as_ref(&self) -> &[u8] {
149        &self.0
150    }
151}
152
153impl Deref for Digest {
154    type Target = [u8];
155    fn deref(&self) -> &[u8] {
156        &self.0
157    }
158}
159
160impl Debug for Digest {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        write!(f, "{}", hex(&self.0))
163    }
164}
165
166impl Display for Digest {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        write!(f, "{}", hex(&self.0))
169    }
170}
171
172impl crate::Digest for Digest {
173    fn random<R: Rng + CryptoRng>(rng: &mut R) -> Self {
174        let mut array = [0u8; DIGEST_LENGTH];
175        rng.fill_bytes(&mut array);
176        Self(array)
177    }
178}
179
180impl Zeroize for Digest {
181    fn zeroize(&mut self) {
182        self.0.zeroize();
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use commonware_codec::{DecodeExt, Encode};
190    use commonware_utils::hex;
191
192    const HELLO_DIGEST: &str = "d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24";
193
194    #[test]
195    fn test_blake3() {
196        let msg = b"hello world";
197
198        // Generate initial hash
199        let mut hasher = Blake3::new();
200        hasher.update(msg);
201        let digest = hasher.finalize();
202        assert!(Digest::decode(digest.as_ref()).is_ok());
203        assert_eq!(hex(digest.as_ref()), HELLO_DIGEST);
204
205        // Reuse hasher
206        hasher.update(msg);
207        let digest = hasher.finalize();
208        assert!(Digest::decode(digest.as_ref()).is_ok());
209        assert_eq!(hex(digest.as_ref()), HELLO_DIGEST);
210
211        // Test simple hasher
212        let hash = hash(msg);
213        assert_eq!(hex(hash.as_ref()), HELLO_DIGEST);
214    }
215
216    #[test]
217    fn test_blake3_len() {
218        assert_eq!(Digest::SIZE, DIGEST_LENGTH);
219    }
220
221    #[test]
222    fn test_hash_empty() {
223        let digest1 = Blake3::empty();
224        let digest2 = Blake3::empty();
225
226        assert_eq!(digest1, digest2);
227    }
228
229    #[test]
230    fn test_blake3_empty() {
231        let empty_digest = Blake3::empty();
232
233        // BLAKE3 hash of empty string:
234        // af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262
235        let expected_bytes = [
236            0xaf, 0x13, 0x49, 0xb9, 0xf5, 0xf9, 0xa1, 0xa6, 0xa0, 0x40, 0x4d, 0xea, 0x36, 0xdc,
237            0xc9, 0x49, 0x9b, 0xcb, 0x25, 0xc9, 0xad, 0xc1, 0x12, 0xb7, 0xcc, 0x9a, 0x93, 0xca,
238            0xe4, 0x1f, 0x32, 0x62,
239        ];
240        let expected_digest = Digest::from(expected_bytes);
241
242        assert_eq!(empty_digest, expected_digest);
243    }
244
245    #[test]
246    fn test_codec() {
247        let msg = b"hello world";
248        let mut hasher = Blake3::new();
249        hasher.update(msg);
250        let digest = hasher.finalize();
251
252        let encoded = digest.encode();
253        assert_eq!(encoded.len(), DIGEST_LENGTH);
254        assert_eq!(encoded, digest.as_ref());
255
256        let decoded = Digest::decode(encoded).unwrap();
257        assert_eq!(digest, decoded);
258    }
259}