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;
pub type CoreBlake3 = blake3::Hasher;
const DIGEST_LENGTH: usize = blake3::OUT_LEN;
#[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")]
{
const PARALLEL_THRESHOLD: usize = 2usize.pow(17);
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))
}
}
#[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<Self> {
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<Self, CodecError> {
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<Hash> 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";
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);
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);
let hash = Blake3::hash(&[msg]);
assert_eq!(hash.as_ref(), HELLO_DIGEST);
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<Digest>,
}
}
}