Skip to main content

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