#[derive(Clone, Copy, PartialOrd, Ord)]
pub struct Digest([u8; 32]);
impl std::hash::Hash for Digest {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl PartialEq for Digest {
fn eq(&self, other: &Self) -> bool {
let mut acc = 0u8;
for (a, b) in self.0.iter().zip(other.0.iter()) {
acc |= a ^ b;
}
acc == 0
}
}
impl Eq for Digest {}
impl Digest {
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_hex(&self) -> String {
crate::hex::encode(self.0)
}
pub fn from_hex(s: &str) -> Result<Self, DigestParseError> {
if s.len() != 64 {
return Err(DigestParseError::WrongLength { len: s.len() });
}
let bytes = crate::hex::decode(s).map_err(DigestParseError::Hex)?;
let arr: [u8; 32] = bytes.try_into().expect("64 hex chars = 32 bytes");
Ok(Self(arr))
}
}
#[derive(Debug, Clone)]
pub enum DigestParseError {
WrongLength {
len: usize,
},
Hex(crate::hex::DecodeError),
}
impl core::fmt::Display for DigestParseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::WrongLength { len } => {
write!(f, "digest hex must be 64 characters, got {len}")
}
Self::Hex(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for DigestParseError {}
impl core::fmt::Debug for Digest {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Digest({})", self.to_hex())
}
}
impl core::fmt::Display for Digest {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.to_hex())
}
}
pub fn blake3(data: &[u8]) -> Digest {
Digest(*blake3::hash(data).as_bytes())
}
pub struct Hasher(blake3::Hasher);
impl Hasher {
pub fn new() -> Self {
Self(blake3::Hasher::new())
}
pub fn update(&mut self, data: &[u8]) -> &mut Self {
self.0.update(data);
self
}
pub fn finalize(&self) -> Digest {
Digest(*self.0.finalize().as_bytes())
}
}
impl Default for Hasher {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_input_matches_blake3_spec() {
let d = blake3(b"");
assert_eq!(
d.to_hex(),
"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
);
}
#[test]
fn deterministic_across_calls() {
let a = blake3(b"hello world");
let b = blake3(b"hello world");
assert_eq!(a, b);
}
#[test]
fn different_input_different_digest() {
assert_ne!(blake3(b"a"), blake3(b"b"));
}
#[test]
fn incremental_matches_oneshot() {
let oneshot = blake3(b"hello world");
let mut h = Hasher::new();
h.update(b"hello ");
h.update(b"world");
assert_eq!(h.finalize(), oneshot);
}
#[test]
fn hex_roundtrip() {
let d = blake3(b"test");
let hex = d.to_hex();
let parsed = Digest::from_hex(&hex).unwrap();
assert_eq!(d, parsed);
}
#[test]
fn display_is_hex() {
let d = blake3(b"");
assert_eq!(format!("{d}"), d.to_hex());
}
#[test]
fn from_hex_rejects_wrong_length() {
assert!(Digest::from_hex("abcd").is_err());
}
}