use blake3::Hasher as Blake3Hasher;
use sourse_core::ObjectId;
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Hash256(pub [u8; 32]);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HashDomain {
Blob,
ManifestNode,
Commit,
}
impl HashDomain {
pub fn prefix(&self) -> &'static str {
match self {
Self::Blob => "sourse-blob-v1:",
Self::ManifestNode => "sourse-manifest-v1:",
Self::Commit => "sourse-commit-v1:",
}
}
}
pub struct Hasher {
inner: Blake3Hasher,
}
impl Hasher {
pub fn new(domain: HashDomain) -> Self {
let mut inner = Blake3Hasher::new();
inner.update(domain.prefix().as_bytes());
Self { inner }
}
pub fn update(&mut self, data: &[u8]) {
self.inner.update(data);
}
pub fn finalize(&self) -> Hash256 {
let hash = self.inner.finalize();
Hash256(*hash.as_bytes())
}
}
pub fn hash_blob(data: &[u8]) -> Hash256 {
let mut h = Hasher::new(HashDomain::Blob);
h.update(data);
h.finalize()
}
pub fn hash_manifest_node(data: &[u8]) -> Hash256 {
let mut h = Hasher::new(HashDomain::ManifestNode);
h.update(data);
h.finalize()
}
pub fn hash_commit(data: &[u8]) -> Hash256 {
let mut h = Hasher::new(HashDomain::Commit);
h.update(data);
h.finalize()
}
pub fn hash_to_object_id(hash: &Hash256) -> ObjectId {
ObjectId(hex::encode(hash.0))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn domains_produce_distinct_hashes() {
let data = b"hello sourse";
let blob = hash_blob(data);
let manifest = hash_manifest_node(data);
let commit = hash_commit(data);
assert_ne!(blob, manifest);
assert_ne!(blob, commit);
assert_ne!(manifest, commit);
}
#[test]
fn deterministic_hashing() {
let data = b"deterministic test";
let h1 = hash_blob(data);
let h2 = hash_blob(data);
assert_eq!(h1, h2);
}
}