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_math::algebra::Random;
28use commonware_utils::{hex, Array, Span};
29use core::{
30    fmt::{Debug, Display},
31    ops::Deref,
32};
33use rand_core::CryptoRngCore;
34use zeroize::Zeroize;
35
36/// Re-export [blake3::Hasher] as `CoreBlake3` for external use if needed.
37pub type CoreBlake3 = blake3::Hasher;
38
39const DIGEST_LENGTH: usize = blake3::OUT_LEN;
40
41/// BLAKE3 hasher.
42#[cfg_attr(
43    feature = "parallel",
44    doc = "When the input message is larger than 128KiB, `rayon` is used to parallelize hashing."
45)]
46#[derive(Debug, Default)]
47pub struct Blake3 {
48    hasher: CoreBlake3,
49}
50
51impl Clone for Blake3 {
52    fn clone(&self) -> Self {
53        // We manually implement `Clone` to avoid cloning the hasher state.
54        Self::default()
55    }
56}
57
58impl Hasher for Blake3 {
59    type Digest = Digest;
60
61    fn update(&mut self, message: &[u8]) -> &mut Self {
62        #[cfg(not(feature = "parallel"))]
63        self.hasher.update(message);
64
65        #[cfg(feature = "parallel")]
66        {
67            // 128 KiB
68            const PARALLEL_THRESHOLD: usize = 2usize.pow(17);
69
70            // Heuristic defined @ https://docs.rs/blake3/latest/blake3/struct.Hasher.html#method.update_rayon
71            if message.len() >= PARALLEL_THRESHOLD {
72                self.hasher.update_rayon(message);
73            } else {
74                self.hasher.update(message);
75            }
76        }
77
78        self
79    }
80
81    fn finalize(&mut self) -> Self::Digest {
82        let finalized = self.hasher.finalize();
83        self.hasher.reset();
84        let array: [u8; DIGEST_LENGTH] = finalized.into();
85        Self::Digest::from(array)
86    }
87
88    fn reset(&mut self) -> &mut Self {
89        self.hasher = CoreBlake3::new();
90        self
91    }
92}
93
94/// Digest of a BLAKE3 hashing operation.
95#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
96#[repr(transparent)]
97pub struct Digest(pub [u8; DIGEST_LENGTH]);
98
99#[cfg(feature = "arbitrary")]
100impl<'a> arbitrary::Arbitrary<'a> for Digest {
101    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
102        // Generate random bytes and compute their Blake3 hash
103        let len = u.int_in_range(0..=256)?;
104        let data = u.bytes(len)?;
105        Ok(Blake3::hash(data))
106    }
107}
108
109impl Write for Digest {
110    fn write(&self, buf: &mut impl BufMut) {
111        self.0.write(buf);
112    }
113}
114
115impl Read for Digest {
116    type Cfg = ();
117
118    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
119        let array = <[u8; DIGEST_LENGTH]>::read(buf)?;
120        Ok(Self(array))
121    }
122}
123
124impl FixedSize for Digest {
125    const SIZE: usize = DIGEST_LENGTH;
126}
127
128impl Span for Digest {}
129
130impl Array for Digest {}
131
132impl From<Hash> for Digest {
133    fn from(value: Hash) -> Self {
134        Self(value.into())
135    }
136}
137
138impl From<[u8; DIGEST_LENGTH]> for Digest {
139    fn from(value: [u8; DIGEST_LENGTH]) -> Self {
140        Self(value)
141    }
142}
143
144impl AsRef<[u8]> for Digest {
145    fn as_ref(&self) -> &[u8] {
146        &self.0
147    }
148}
149
150impl Deref for Digest {
151    type Target = [u8];
152    fn deref(&self) -> &[u8] {
153        &self.0
154    }
155}
156
157impl Debug for Digest {
158    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
159        write!(f, "{}", hex(&self.0))
160    }
161}
162
163impl Display for Digest {
164    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
165        write!(f, "{}", hex(&self.0))
166    }
167}
168
169impl crate::Digest for Digest {
170    const EMPTY: Self = Self([0u8; DIGEST_LENGTH]);
171}
172
173impl Random for Digest {
174    fn random(mut rng: impl CryptoRngCore) -> Self {
175        let mut array = [0u8; DIGEST_LENGTH];
176        rng.fill_bytes(&mut array);
177        Self(array)
178    }
179}
180
181impl Zeroize for Digest {
182    fn zeroize(&mut self) {
183        self.0.zeroize();
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use commonware_codec::{DecodeExt, Encode};
191    use commonware_utils::hex;
192
193    const HELLO_DIGEST: [u8; DIGEST_LENGTH] =
194        hex!("d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24");
195
196    #[test]
197    fn test_blake3() {
198        let msg = b"hello world";
199
200        // Generate initial hash
201        let mut hasher = Blake3::new();
202        hasher.update(msg);
203        let digest = hasher.finalize();
204        assert!(Digest::decode(digest.as_ref()).is_ok());
205        assert_eq!(digest.as_ref(), HELLO_DIGEST);
206
207        // Reuse hasher
208        hasher.update(msg);
209        let digest = hasher.finalize();
210        assert!(Digest::decode(digest.as_ref()).is_ok());
211        assert_eq!(digest.as_ref(), HELLO_DIGEST);
212
213        // Test simple hasher
214        let hash = Blake3::hash(msg);
215        assert_eq!(hash.as_ref(), HELLO_DIGEST);
216    }
217
218    #[test]
219    fn test_blake3_len() {
220        assert_eq!(Digest::SIZE, DIGEST_LENGTH);
221    }
222
223    #[test]
224    fn test_codec() {
225        let msg = b"hello world";
226        let mut hasher = Blake3::new();
227        hasher.update(msg);
228        let digest = hasher.finalize();
229
230        let encoded = digest.encode();
231        assert_eq!(encoded.len(), DIGEST_LENGTH);
232        assert_eq!(encoded, digest.as_ref());
233
234        let decoded = Digest::decode(encoded).unwrap();
235        assert_eq!(digest, decoded);
236    }
237
238    #[cfg(feature = "arbitrary")]
239    mod conformance {
240        use super::*;
241        use commonware_codec::conformance::CodecConformance;
242
243        commonware_conformance::conformance_tests! {
244            CodecConformance<Digest>,
245        }
246    }
247}