use sha2::{Digest, Sha256};
pub struct CanonicalHasher {
inner: Sha256,
}
impl Default for CanonicalHasher {
fn default() -> Self {
Self::new()
}
}
impl CanonicalHasher {
pub fn new() -> Self {
CanonicalHasher {
inner: Sha256::new(),
}
}
pub fn field(&mut self, label: &str, bytes: &[u8]) -> &mut Self {
self.inner.update((label.len() as u64).to_le_bytes());
self.inner.update(label.as_bytes());
self.inner.update((bytes.len() as u64).to_le_bytes());
self.inner.update(bytes);
self
}
pub fn u64(&mut self, label: &str, v: u64) -> &mut Self {
self.field(label, &v.to_le_bytes())
}
pub fn hash32(&mut self, label: &str, h: &[u8; 32]) -> &mut Self {
self.field(label, h)
}
pub fn finalize(self) -> [u8; 32] {
self.inner.finalize().into()
}
pub fn finalize_hex(self) -> String {
to_hex(&self.finalize())
}
}
pub fn sha256(bytes: &[u8]) -> [u8; 32] {
let mut h = Sha256::new();
h.update(bytes);
h.finalize().into()
}
pub fn to_hex(h: &[u8; 32]) -> String {
let mut s = String::with_capacity(64);
for b in h {
s.push_str(&format!("{b:02x}"));
}
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn field_order_and_length_prefix_prevent_collisions() {
let mut a = CanonicalHasher::new();
a.field("ab", b"c");
let mut b = CanonicalHasher::new();
b.field("a", b"bc");
assert_ne!(a.finalize(), b.finalize());
}
#[test]
fn seal_is_deterministic() {
let mk = || {
let mut h = CanonicalHasher::new();
h.field("schema", b"x")
.u64("n", 3)
.hash32("auth", &sha256(b"policy"));
h.finalize()
};
assert_eq!(mk(), mk());
assert_eq!(to_hex(&mk()).len(), 64);
}
}