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//! // Hash data in a single shot
10//! let digest = Blake3::hash(&[b"hello,", b"world!"]);
11//! println!("digest: {:?}", digest);
12//!
13//! // Or stream data incrementally
14//! let mut hasher = Blake3::default();
15//! hasher.update(b"hello,");
16//! hasher.update(b"world!");
17//! let (_hasher, digest) = hasher.finalize();
18//! println!("digest: {:?}", digest);
19//! ```
20
21use crate::Hasher;
22use blake3::Hash;
23use bytes::{Buf, BufMut};
24use commonware_codec::{Error as CodecError, FixedArray, FixedSize, Read, ReadExt, Write};
25use commonware_formatting::Hex;
26use commonware_math::algebra::Random;
27use commonware_utils::{Array, Span};
28use core::{
29    fmt::{Debug, Display},
30    ops::Deref,
31};
32use rand_core::CryptoRng;
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/// BLAKE3 hasher.
41#[cfg_attr(
42    feature = "blake3-parallel",
43    doc = "When the input message is larger than 128KiB, `rayon` is used to parallelize hashing."
44)]
45#[derive(Debug, Default)]
46pub struct Blake3 {
47    hasher: CoreBlake3,
48}
49
50impl Hasher for Blake3 {
51    type Digest = Digest;
52
53    fn hash(parts: &[&[u8]]) -> Self::Digest {
54        let mut hasher = Self::default();
55        for part in parts {
56            hasher.update(part);
57        }
58        hasher.finalize().1
59    }
60
61    fn hash_pair(left: &[&[u8]], right: &[&[u8]]) -> (Self::Digest, Self::Digest) {
62        (Self::hash(left), Self::hash(right))
63    }
64
65    fn update(&mut self, message: &[u8]) -> &mut Self {
66        #[cfg(not(feature = "blake3-parallel"))]
67        self.hasher.update(message);
68
69        #[cfg(feature = "blake3-parallel")]
70        {
71            // 128 KiB
72            const PARALLEL_THRESHOLD: usize = 2usize.pow(17);
73
74            // Heuristic defined @ https://docs.rs/blake3/latest/blake3/struct.Hasher.html#method.update_rayon
75            if message.len() >= PARALLEL_THRESHOLD {
76                self.hasher.update_rayon(message);
77            } else {
78                self.hasher.update(message);
79            }
80        }
81
82        self
83    }
84
85    fn finalize(mut self) -> (Self, Self::Digest) {
86        let finalized = self.hasher.finalize();
87        self.hasher.reset();
88        let array: [u8; DIGEST_LENGTH] = finalized.into();
89        (self, Self::Digest::from(array))
90    }
91}
92
93/// Digest of a BLAKE3 hashing operation.
94#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, FixedArray)]
95#[fixed_array(infallible)]
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 AsRef<[u8]> for Digest {
139    fn as_ref(&self) -> &[u8] {
140        &self.0
141    }
142}
143
144impl Deref for Digest {
145    type Target = [u8];
146    fn deref(&self) -> &[u8] {
147        &self.0
148    }
149}
150
151impl Debug for Digest {
152    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
153        write!(f, "{}", Hex(&self.0))
154    }
155}
156
157impl Display for Digest {
158    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
159        write!(f, "{}", Hex(&self.0))
160    }
161}
162
163impl crate::Digest for Digest {
164    const EMPTY: Self = Self([0u8; DIGEST_LENGTH]);
165}
166
167impl Random for Digest {
168    fn random(mut rng: impl CryptoRng) -> Self {
169        let mut array = [0u8; DIGEST_LENGTH];
170        rng.fill_bytes(&mut array);
171        Self(array)
172    }
173}
174
175impl Zeroize for Digest {
176    fn zeroize(&mut self) {
177        self.0.zeroize();
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use commonware_codec::{DecodeExt, Encode};
185
186    const HELLO_DIGEST: [u8; DIGEST_LENGTH] = commonware_formatting::hex!(
187        "d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24"
188    );
189
190    #[test]
191    fn test_blake3() {
192        let msg = b"hello world";
193
194        // Generate initial hash
195        let mut hasher = Blake3::default();
196        hasher.update(msg);
197        let (hasher, digest) = hasher.finalize();
198        assert!(Digest::decode(digest.as_ref()).is_ok());
199        assert_eq!(digest.as_ref(), HELLO_DIGEST);
200
201        // Reuse the reset hasher
202        let mut hasher = hasher;
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        // Test one-shot hasher
209        let hash = Blake3::hash(&[msg]);
210        assert_eq!(hash.as_ref(), HELLO_DIGEST);
211
212        // Test multi-part one-shot hasher
213        let hash = Blake3::hash(&[b"hello", b" world"]);
214        assert_eq!(hash.as_ref(), HELLO_DIGEST);
215    }
216
217    #[test]
218    fn test_blake3_len() {
219        assert_eq!(Digest::SIZE, DIGEST_LENGTH);
220    }
221
222    #[test]
223    fn test_codec() {
224        let msg = b"hello world";
225        let mut hasher = Blake3::default();
226        hasher.update(msg);
227        let (_, digest) = hasher.finalize();
228
229        let encoded = digest.encode();
230        assert_eq!(encoded.len(), DIGEST_LENGTH);
231        assert_eq!(encoded, digest.as_ref());
232
233        let decoded = Digest::decode(encoded).unwrap();
234        assert_eq!(digest, decoded);
235    }
236
237    #[cfg(feature = "arbitrary")]
238    mod conformance {
239        use super::*;
240        use commonware_codec::conformance::CodecConformance;
241
242        commonware_conformance::conformance_tests! {
243            CodecConformance<Digest>,
244        }
245    }
246}