//! BLAKE3 implementation of the [Hasher] trait. //! //! This implementation uses the [blake3] crate to generate BLAKE3 digests. //! //! # Example //! ```rust //! use commonware_cryptography::{Hasher, blake3::Blake3}; //! //! // Hash data in a single shot //! let digest = Blake3::hash(&[b"hello,", b"world!"]); //! println!("digest: {:?}", digest); //! //! // Or stream data incrementally //! let mut hasher = Blake3::default(); //! hasher.update(b"hello,"); //! hasher.update(b"world!"); //! let (_hasher, digest) = hasher.finalize(); //! println!("digest: {:?}", digest); //! ``` use crate::Hasher; use blake3::Hash; use bytes::{Buf, BufMut}; use commonware_codec::{Error as CodecError, FixedArray, FixedSize, Read, ReadExt, Write}; use commonware_formatting::Hex; use commonware_math::algebra::Random; use commonware_utils::{Array, Span}; use core::{ fmt::{Debug, Display}, ops::Deref, }; use rand_core::CryptoRng; use zeroize::Zeroize; /// Re-export [blake3::Hasher] as `CoreBlake3` for external use if needed. pub type CoreBlake3 = blake3::Hasher; const DIGEST_LENGTH: usize = blake3::OUT_LEN; /// BLAKE3 hasher. #[cfg_attr( feature = "blake3-parallel", doc = "When the input message is larger than 128KiB, `rayon` is used to parallelize hashing." )] #[derive(Debug, Default)] pub struct Blake3 { hasher: CoreBlake3, } impl Hasher for Blake3 { type Digest = Digest; fn hash(parts: &[&[u8]]) -> Self::Digest { let mut hasher = Self::default(); for part in parts { hasher.update(part); } hasher.finalize().1 } fn hash_pair(left: &[&[u8]], right: &[&[u8]]) -> (Self::Digest, Self::Digest) { (Self::hash(left), Self::hash(right)) } fn update(&mut self, message: &[u8]) -> &mut Self { #[cfg(not(feature = "blake3-parallel"))] self.hasher.update(message); #[cfg(feature = "blake3-parallel")] { // 128 KiB const PARALLEL_THRESHOLD: usize = 2usize.pow(17); // Heuristic defined @ https://docs.rs/blake3/latest/blake3/struct.Hasher.html#method.update_rayon if message.len() >= PARALLEL_THRESHOLD { self.hasher.update_rayon(message); } else { self.hasher.update(message); } } self } fn finalize(mut self) -> (Self, Self::Digest) { let finalized = self.hasher.finalize(); self.hasher.reset(); let array: [u8; DIGEST_LENGTH] = finalized.into(); (self, Self::Digest::from(array)) } } /// Digest of a BLAKE3 hashing operation. #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, FixedArray)] #[fixed_array(infallible)] #[repr(transparent)] pub struct Digest(pub [u8; DIGEST_LENGTH]); #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for Digest { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { // Generate random bytes and compute their Blake3 hash let len = u.int_in_range(0..=256)?; let data = u.bytes(len)?; Ok(Blake3::hash(&[data])) } } impl Write for Digest { fn write(&self, buf: &mut impl BufMut) { self.0.write(buf); } } impl Read for Digest { type Cfg = (); fn read_cfg(buf: &mut impl Buf, _: &()) -> Result { let array = <[u8; DIGEST_LENGTH]>::read(buf)?; Ok(Self(array)) } } impl FixedSize for Digest { const SIZE: usize = DIGEST_LENGTH; } impl Span for Digest {} impl Array for Digest {} impl From for Digest { fn from(value: Hash) -> Self { Self(value.into()) } } impl AsRef<[u8]> for Digest { fn as_ref(&self) -> &[u8] { &self.0 } } impl Deref for Digest { type Target = [u8]; fn deref(&self) -> &[u8] { &self.0 } } impl Debug for Digest { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", Hex(&self.0)) } } impl Display for Digest { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", Hex(&self.0)) } } impl crate::Digest for Digest { const EMPTY: Self = Self([0u8; DIGEST_LENGTH]); } impl Random for Digest { fn random(mut rng: impl CryptoRng) -> Self { let mut array = [0u8; DIGEST_LENGTH]; rng.fill_bytes(&mut array); Self(array) } } impl Zeroize for Digest { fn zeroize(&mut self) { self.0.zeroize(); } } #[cfg(test)] mod tests { use super::*; use commonware_codec::{DecodeExt, Encode}; const HELLO_DIGEST: [u8; DIGEST_LENGTH] = commonware_formatting::hex!( "d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24" ); #[test] fn test_blake3() { let msg = b"hello world"; // Generate initial hash let mut hasher = Blake3::default(); hasher.update(msg); let (hasher, digest) = hasher.finalize(); assert!(Digest::decode(digest.as_ref()).is_ok()); assert_eq!(digest.as_ref(), HELLO_DIGEST); // Reuse the reset hasher let mut hasher = hasher; hasher.update(msg); let (_, digest) = hasher.finalize(); assert!(Digest::decode(digest.as_ref()).is_ok()); assert_eq!(digest.as_ref(), HELLO_DIGEST); // Test one-shot hasher let hash = Blake3::hash(&[msg]); assert_eq!(hash.as_ref(), HELLO_DIGEST); // Test multi-part one-shot hasher let hash = Blake3::hash(&[b"hello", b" world"]); assert_eq!(hash.as_ref(), HELLO_DIGEST); } #[test] fn test_blake3_len() { assert_eq!(Digest::SIZE, DIGEST_LENGTH); } #[test] fn test_codec() { let msg = b"hello world"; let mut hasher = Blake3::default(); hasher.update(msg); let (_, digest) = hasher.finalize(); let encoded = digest.encode(); assert_eq!(encoded.len(), DIGEST_LENGTH); assert_eq!(encoded, digest.as_ref()); let decoded = Digest::decode(encoded).unwrap(); assert_eq!(digest, decoded); } #[cfg(feature = "arbitrary")] mod conformance { use super::*; use commonware_codec::conformance::CodecConformance; commonware_conformance::conformance_tests! { CodecConformance, } } }