1use blake3::Hasher as Blake3Hasher;
8use sourse_core::ObjectId;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
12pub struct Hash256(pub [u8; 32]);
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum HashDomain {
17 Blob,
19 ManifestNode,
21 Commit,
23}
24
25impl HashDomain {
26 pub fn prefix(&self) -> &'static str {
28 match self {
29 Self::Blob => "sourse-blob-v1:",
30 Self::ManifestNode => "sourse-manifest-v1:",
31 Self::Commit => "sourse-commit-v1:",
32 }
33 }
34}
35
36pub struct Hasher {
38 inner: Blake3Hasher,
39}
40
41impl Hasher {
42 pub fn new(domain: HashDomain) -> Self {
44 let mut inner = Blake3Hasher::new();
45 inner.update(domain.prefix().as_bytes());
46 Self { inner }
47 }
48
49 pub fn update(&mut self, data: &[u8]) {
51 self.inner.update(data);
52 }
53
54 pub fn finalize(&self) -> Hash256 {
56 let hash = self.inner.finalize();
57 Hash256(*hash.as_bytes())
58 }
59}
60
61pub fn hash_blob(data: &[u8]) -> Hash256 {
63 let mut h = Hasher::new(HashDomain::Blob);
64 h.update(data);
65 h.finalize()
66}
67
68pub fn hash_manifest_node(data: &[u8]) -> Hash256 {
70 let mut h = Hasher::new(HashDomain::ManifestNode);
71 h.update(data);
72 h.finalize()
73}
74
75pub fn hash_commit(data: &[u8]) -> Hash256 {
77 let mut h = Hasher::new(HashDomain::Commit);
78 h.update(data);
79 h.finalize()
80}
81
82pub fn hash_to_object_id(hash: &Hash256) -> ObjectId {
84 ObjectId(hex::encode(hash.0))
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn domains_produce_distinct_hashes() {
93 let data = b"hello sourse";
94 let blob = hash_blob(data);
95 let manifest = hash_manifest_node(data);
96 let commit = hash_commit(data);
97 assert_ne!(blob, manifest);
98 assert_ne!(blob, commit);
99 assert_ne!(manifest, commit);
100 }
101
102 #[test]
103 fn deterministic_hashing() {
104 let data = b"deterministic test";
105 let h1 = hash_blob(data);
106 let h2 = hash_blob(data);
107 assert_eq!(h1, h2);
108 }
109}