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};
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([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 Array for Digest {}
132
133impl From<Hash> for Digest {
134    fn from(value: Hash) -> Self {
135        Self(value.into())
136    }
137}
138
139impl From<[u8; DIGEST_LENGTH]> for Digest {
140    fn from(value: [u8; DIGEST_LENGTH]) -> Self {
141        Self(value)
142    }
143}
144
145impl AsRef<[u8]> for Digest {
146    fn as_ref(&self) -> &[u8] {
147        &self.0
148    }
149}
150
151impl Deref for Digest {
152    type Target = [u8];
153    fn deref(&self) -> &[u8] {
154        &self.0
155    }
156}
157
158impl Debug for Digest {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        write!(f, "{}", hex(&self.0))
161    }
162}
163
164impl Display for Digest {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        write!(f, "{}", hex(&self.0))
167    }
168}
169
170impl crate::Digest for Digest {
171    fn random<R: Rng + CryptoRng>(rng: &mut R) -> Self {
172        let mut array = [0u8; DIGEST_LENGTH];
173        rng.fill_bytes(&mut array);
174        Self(array)
175    }
176}
177
178impl Zeroize for Digest {
179    fn zeroize(&mut self) {
180        self.0.zeroize();
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use commonware_codec::{DecodeExt, Encode};
188    use commonware_utils::hex;
189
190    const HELLO_DIGEST: &str = "d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24";
191
192    #[test]
193    fn test_blake3() {
194        let msg = b"hello world";
195
196        // Generate initial hash
197        let mut hasher = Blake3::new();
198        hasher.update(msg);
199        let digest = hasher.finalize();
200        assert!(Digest::decode(digest.as_ref()).is_ok());
201        assert_eq!(hex(digest.as_ref()), HELLO_DIGEST);
202
203        // Reuse hasher
204        hasher.update(msg);
205        let digest = hasher.finalize();
206        assert!(Digest::decode(digest.as_ref()).is_ok());
207        assert_eq!(hex(digest.as_ref()), HELLO_DIGEST);
208
209        // Test simple hasher
210        let hash = hash(msg);
211        assert_eq!(hex(hash.as_ref()), HELLO_DIGEST);
212    }
213
214    #[test]
215    fn test_blake3_len() {
216        assert_eq!(Digest::SIZE, DIGEST_LENGTH);
217    }
218
219    #[test]
220    fn test_hash_empty() {
221        let digest1 = Blake3::empty();
222        let digest2 = Blake3::empty();
223
224        assert_eq!(digest1, digest2);
225    }
226
227    #[test]
228    fn test_blake3_empty() {
229        let empty_digest = Blake3::empty();
230
231        // BLAKE3 hash of empty string:
232        // af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262
233        let expected_bytes = [
234            0xaf, 0x13, 0x49, 0xb9, 0xf5, 0xf9, 0xa1, 0xa6, 0xa0, 0x40, 0x4d, 0xea, 0x36, 0xdc,
235            0xc9, 0x49, 0x9b, 0xcb, 0x25, 0xc9, 0xad, 0xc1, 0x12, 0xb7, 0xcc, 0x9a, 0x93, 0xca,
236            0xe4, 0x1f, 0x32, 0x62,
237        ];
238        let expected_digest = Digest::from(expected_bytes);
239
240        assert_eq!(empty_digest, expected_digest);
241    }
242
243    #[test]
244    fn test_codec() {
245        let msg = b"hello world";
246        let mut hasher = Blake3::new();
247        hasher.update(msg);
248        let digest = hasher.finalize();
249
250        let encoded = digest.encode();
251        assert_eq!(encoded.len(), DIGEST_LENGTH);
252        assert_eq!(encoded, digest.as_ref());
253
254        let decoded = Digest::decode(encoded).unwrap();
255        assert_eq!(digest, decoded);
256    }
257}