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, 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::CryptoRngCore;
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)]
97#[repr(transparent)]
98pub struct Digest(pub [u8; DIGEST_LENGTH]);
99
100#[cfg(feature = "arbitrary")]
101impl<'a> arbitrary::Arbitrary<'a> for Digest {
102    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
103        // Generate random bytes and compute their Blake3 hash
104        let len = u.int_in_range(0..=256)?;
105        let data = u.bytes(len)?;
106        Ok(Blake3::hash(data))
107    }
108}
109
110impl Write for Digest {
111    fn write(&self, buf: &mut impl BufMut) {
112        self.0.write(buf);
113    }
114}
115
116impl Read for Digest {
117    type Cfg = ();
118
119    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
120        let array = <[u8; DIGEST_LENGTH]>::read(buf)?;
121        Ok(Self(array))
122    }
123}
124
125impl FixedSize for Digest {
126    const SIZE: usize = DIGEST_LENGTH;
127}
128
129impl Span for Digest {}
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 core::fmt::Formatter<'_>) -> core::fmt::Result {
160        write!(f, "{}", Hex(&self.0))
161    }
162}
163
164impl Display for Digest {
165    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166        write!(f, "{}", Hex(&self.0))
167    }
168}
169
170impl crate::Digest for Digest {
171    const EMPTY: Self = Self([0u8; DIGEST_LENGTH]);
172}
173
174impl Random for Digest {
175    fn random(mut rng: impl CryptoRngCore) -> Self {
176        let mut array = [0u8; DIGEST_LENGTH];
177        rng.fill_bytes(&mut array);
178        Self(array)
179    }
180}
181
182impl Zeroize for Digest {
183    fn zeroize(&mut self) {
184        self.0.zeroize();
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use commonware_codec::{DecodeExt, Encode};
192
193    const HELLO_DIGEST: [u8; DIGEST_LENGTH] = commonware_formatting::hex!(
194        "d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24"
195    );
196
197    #[test]
198    fn test_blake3() {
199        let msg = b"hello world";
200
201        // Generate initial hash
202        let mut hasher = Blake3::new();
203        hasher.update(msg);
204        let digest = hasher.finalize();
205        assert!(Digest::decode(digest.as_ref()).is_ok());
206        assert_eq!(digest.as_ref(), HELLO_DIGEST);
207
208        // Reuse hasher
209        hasher.update(msg);
210        let digest = hasher.finalize();
211        assert!(Digest::decode(digest.as_ref()).is_ok());
212        assert_eq!(digest.as_ref(), HELLO_DIGEST);
213
214        // Test simple hasher
215        let hash = Blake3::hash(msg);
216        assert_eq!(hash.as_ref(), HELLO_DIGEST);
217    }
218
219    #[test]
220    fn test_blake3_len() {
221        assert_eq!(Digest::SIZE, DIGEST_LENGTH);
222    }
223
224    #[test]
225    fn test_codec() {
226        let msg = b"hello world";
227        let mut hasher = Blake3::new();
228        hasher.update(msg);
229        let digest = hasher.finalize();
230
231        let encoded = digest.encode();
232        assert_eq!(encoded.len(), DIGEST_LENGTH);
233        assert_eq!(encoded, digest.as_ref());
234
235        let decoded = Digest::decode(encoded).unwrap();
236        assert_eq!(digest, decoded);
237    }
238
239    #[cfg(feature = "arbitrary")]
240    mod conformance {
241        use super::*;
242        use commonware_codec::conformance::CodecConformance;
243
244        commonware_conformance::conformance_tests! {
245            CodecConformance<Digest>,
246        }
247    }
248}