use std::hash::{Hash, Hasher};
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Fingerprint(pub [u64; 4]);
impl Fingerprint {
pub const ZERO: Fingerprint = Fingerprint([0; 4]);
fn from_bytes(bytes: &[u8; 32]) -> Fingerprint {
let mut limbs = [0u64; 4];
for (limb, chunk) in limbs.iter_mut().zip(bytes.chunks_exact(8)) {
*limb = u64::from_le_bytes(chunk.try_into().unwrap());
}
Fingerprint(limbs)
}
#[must_use]
pub fn combine(self, other: Fingerprint) -> Fingerprint {
let mut out = [0u64; 4];
let mut carry = 0u128;
for (o, (&a, &b)) in out.iter_mut().zip(self.0.iter().zip(other.0.iter())) {
let sum = a as u128 + b as u128 + carry;
*o = sum as u64;
carry = sum >> 64;
}
Fingerprint(out)
}
#[must_use]
pub fn remove(self, other: Fingerprint) -> Fingerprint {
let mut out = [0u64; 4];
let mut borrow = 0i128;
for (o, (&a, &b)) in out.iter_mut().zip(self.0.iter().zip(other.0.iter())) {
let diff = a as i128 - b as i128 - borrow;
if diff < 0 {
*o = (diff + (1i128 << 64)) as u64;
borrow = 1;
} else {
*o = diff as u64;
borrow = 0;
}
}
Fingerprint(out)
}
}
impl Add for Fingerprint {
type Output = Fingerprint;
fn add(self, rhs: Fingerprint) -> Fingerprint {
self.combine(rhs)
}
}
impl AddAssign for Fingerprint {
fn add_assign(&mut self, rhs: Fingerprint) {
*self = self.combine(rhs);
}
}
impl Sub for Fingerprint {
type Output = Fingerprint;
fn sub(self, rhs: Fingerprint) -> Fingerprint {
self.remove(rhs)
}
}
impl SubAssign for Fingerprint {
fn sub_assign(&mut self, rhs: Fingerprint) {
*self = self.remove(rhs);
}
}
impl Neg for Fingerprint {
type Output = Fingerprint;
fn neg(self) -> Fingerprint {
Fingerprint::ZERO.remove(self)
}
}
impl std::fmt::Debug for Fingerprint {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Fingerprint({:016x}{:016x}{:016x}{:016x})",
self.0[3], self.0[2], self.0[1], self.0[0]
)
}
}
impl std::fmt::Display for Fingerprint {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{:016x}{:016x}{:016x}{:016x}",
self.0[3], self.0[2], self.0[1], self.0[0]
)
}
}
struct Blake3Hasher(blake3::Hasher);
impl Blake3Hasher {
fn new() -> Blake3Hasher {
Blake3Hasher(blake3::Hasher::new())
}
fn fingerprint(&self) -> Fingerprint {
Fingerprint::from_bytes(self.0.finalize().as_bytes())
}
}
impl Hasher for Blake3Hasher {
fn write(&mut self, bytes: &[u8]) {
self.0.update(bytes);
}
fn write_u8(&mut self, i: u8) {
self.0.update(&[i]);
}
fn write_u16(&mut self, i: u16) {
self.0.update(&i.to_le_bytes());
}
fn write_u32(&mut self, i: u32) {
self.0.update(&i.to_le_bytes());
}
fn write_u64(&mut self, i: u64) {
self.0.update(&i.to_le_bytes());
}
fn write_u128(&mut self, i: u128) {
self.0.update(&i.to_le_bytes());
}
fn write_usize(&mut self, i: usize) {
self.0.update(&(i as u64).to_le_bytes());
}
fn write_i8(&mut self, i: i8) {
self.0.update(&i.to_le_bytes());
}
fn write_i16(&mut self, i: i16) {
self.0.update(&i.to_le_bytes());
}
fn write_i32(&mut self, i: i32) {
self.0.update(&i.to_le_bytes());
}
fn write_i64(&mut self, i: i64) {
self.0.update(&i.to_le_bytes());
}
fn write_i128(&mut self, i: i128) {
self.0.update(&i.to_le_bytes());
}
fn write_isize(&mut self, i: isize) {
self.0.update(&(i as i64).to_le_bytes());
}
fn finish(&self) -> u64 {
let bytes = self.0.finalize();
u64::from_le_bytes(bytes.as_bytes()[..8].try_into().unwrap())
}
}
pub fn hash<K: Hash, V: Hash>(key: &K, value: &V) -> Fingerprint {
let mut hasher = Blake3Hasher::new();
key.hash(&mut hasher);
value.hash(&mut hasher);
hasher.fingerprint()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_is_identity() {
let f = hash(&42u64, &"hello");
assert_eq!(f + Fingerprint::ZERO, f);
assert_eq!(f - Fingerprint::ZERO, f);
assert_eq!(Fingerprint::ZERO + f, f);
}
#[test]
fn add_then_remove_is_identity() {
let a = hash(&1u64, &10u64);
let b = hash(&2u64, &20u64);
let c = hash(&3u64, &30u64);
let combined = a + b + c;
assert_eq!(combined - b - a - c, Fingerprint::ZERO);
assert_eq!(combined - c, a + b);
}
#[test]
fn add_is_commutative_and_associative() {
let a = hash(&1u64, &10u64);
let b = hash(&2u64, &20u64);
let c = hash(&3u64, &30u64);
assert_eq!(a + b, b + a);
assert_eq!((a + b) + c, a + (b + c));
}
#[test]
fn neg_is_additive_inverse() {
let a = hash(&7u64, &"x");
assert_eq!(a + (-a), Fingerprint::ZERO);
assert_eq!(-(-a), a);
}
#[test]
fn add_propagates_carry_across_limbs() {
let all_ones = Fingerprint([u64::MAX; 4]);
assert_eq!(all_ones + Fingerprint([1, 0, 0, 0]), Fingerprint::ZERO);
assert_eq!(
Fingerprint([u64::MAX, 0, 0, 0]) + Fingerprint([1, 0, 0, 0]),
Fingerprint([0, 1, 0, 0])
);
}
#[test]
fn sub_borrows_across_limbs() {
assert_eq!(
Fingerprint::ZERO - Fingerprint([1, 0, 0, 0]),
Fingerprint([u64::MAX; 4])
);
}
#[test]
fn golden_element_hash() {
assert_eq!(
hash(&50u64, &"Hello"),
Fingerprint([
0x3a24_dc5c_8162_625b,
0x8096_8c6a_b597_489b,
0x105e_ba4e_6a69_90c8,
0x4e24_03d1_e7ce_04f7,
])
);
}
#[test]
fn golden_combined_fingerprint() {
let combined =
hash(&25u64, &"World!") + hash(&50u64, &"Hello") + hash(&75u64, &"Everyone!");
assert_eq!(
combined,
Fingerprint([
0x6be8_b71e_bc22_e801,
0x6c53_2dca_19b5_e70c,
0x7422_4b2c_43a0_0032,
0xacf7_6a81_40c9_c730,
])
);
}
}