1use core::fmt;
2
3use sha2::{Digest as _, Sha256};
4
5use crate::json::{Sink, Value, stream};
6
7pub const RAW_EVIDENCE_DOMAIN: &str = "amiss/raw-evidence/v1";
10
11#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct Digest([u8; 32]);
13
14impl Digest {
15 #[must_use]
16 pub const fn as_bytes(&self) -> &[u8; 32] {
17 &self.0
18 }
19
20 #[must_use]
22 pub fn from_wire(raw: &str) -> Option<Self> {
23 let hex = raw.strip_prefix("sha256:")?;
24 if hex.len() != 64 {
25 return None;
26 }
27 let mut out = [0_u8; 32];
28 for (slot, pair) in out.iter_mut().zip(hex.as_bytes().chunks_exact(2)) {
29 let [high, low] = pair else { return None };
30 *slot = hex_value(*high)?.wrapping_shl(4) | hex_value(*low)?;
31 }
32 Some(Self(out))
33 }
34}
35
36fn hex_value(byte: u8) -> Option<u8> {
37 match byte {
38 b'0'..=b'9' => Some(byte.wrapping_sub(b'0')),
39 b'a'..=b'f' => Some(byte.wrapping_sub(b'a').wrapping_add(10)),
40 _ => None,
41 }
42}
43
44impl fmt::Display for Digest {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 f.write_str("sha256:")?;
47 for byte in self.0 {
48 write!(f, "{byte:02x}")?;
49 }
50 Ok(())
51 }
52}
53
54impl fmt::Debug for Digest {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 fmt::Display::fmt(self, f)
57 }
58}
59
60#[must_use]
64pub fn sha256(bytes: &[u8]) -> Digest {
65 let mut hasher = Sha256::new();
66 hasher.update(bytes);
67 Digest(hasher.finalize().into())
68}
69
70#[must_use]
71pub fn hb(domain: &str, bytes: &[u8]) -> Digest {
72 let mut hasher = with_domain(domain);
73 hasher.update(bytes);
74 Digest(hasher.finalize().into())
75}
76
77#[must_use]
78pub fn hj(domain: &str, value: &Value) -> Digest {
79 let mut sink = HashSink(with_domain(domain));
80 stream(value, &mut sink);
81 Digest(sink.0.finalize().into())
82}
83
84struct HashSink(Sha256);
85
86impl Sink for HashSink {
87 fn write(&mut self, piece: &str) {
88 self.0.update(piece.as_bytes());
89 }
90}
91
92fn with_domain(domain: &str) -> Sha256 {
93 let mut hasher = Sha256::new();
94 hasher.update(domain.as_bytes());
95 hasher.update([0_u8]);
96 hasher
97}