use sha2::{Digest, Sha256, Sha512};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HashAlgorithm {
Sha256,
Sha512,
Blake3,
}
pub const ADDRESS_ALGORITHM: HashAlgorithm = HashAlgorithm::Blake3;
pub fn hash_hex(algorithm: HashAlgorithm, bytes: &[u8]) -> String {
fn hex(bytes: &[u8]) -> String {
use std::fmt::Write;
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
write!(s, "{b:02x}").expect("writing to a String is infallible");
}
s
}
match algorithm {
HashAlgorithm::Sha256 => hex(&Sha256::digest(bytes)),
HashAlgorithm::Sha512 => hex(&Sha512::digest(bytes)),
HashAlgorithm::Blake3 => hex(blake3::hash(bytes).as_bytes()),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ContentAddress([u8; 32]);
impl ContentAddress {
pub fn of(canonical_bytes: &[u8]) -> Self {
Self(*blake3::hash(canonical_bytes).as_bytes())
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_hex(&self) -> String {
use std::fmt::Write;
let mut s = String::with_capacity(64);
for b in &self.0 {
write!(s, "{b:02x}").expect("writing to a String is infallible");
}
s
}
pub fn from_hex(hex: &str) -> Option<Self> {
if hex.len() != 64 {
return None;
}
let mut out = [0u8; 32];
for (byte, pair) in out.iter_mut().zip(hex.as_bytes().chunks_exact(2)) {
let hi = (pair[0] as char).to_digit(16)?;
let lo = (pair[1] as char).to_digit(16)?;
*byte = ((hi << 4) | lo) as u8;
}
Some(Self(out))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn address_is_deterministic() {
assert_eq!(ContentAddress::of(b"praxis"), ContentAddress::of(b"praxis"));
}
#[test]
fn different_bytes_yield_different_address() {
assert_ne!(ContentAddress::of(b"a"), ContentAddress::of(b"b"));
}
#[test]
fn hex_round_trips() {
let a = ContentAddress::of(b"the ground");
let hex = a.to_hex();
assert_eq!(hex.len(), 64);
assert_eq!(ContentAddress::from_hex(&hex), Some(a));
}
#[test]
fn from_hex_rejects_malformed() {
assert_eq!(ContentAddress::from_hex("xyz"), None); assert_eq!(ContentAddress::from_hex(&"z".repeat(64)), None); assert_eq!(ContentAddress::from_hex(&"a".repeat(63)), None); }
#[test]
fn matches_blake3_known_answer() {
assert_eq!(
ContentAddress::of(b"").to_hex(),
"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
);
}
}