1use crate::AuditEvent;
4use sha2::{Digest, Sha256};
5use thiserror::Error;
6
7pub const HASH_PREFIX: &str = "sha256:";
9
10#[derive(Debug, Error)]
12pub enum ChainError {
13 #[error("empty chain")]
15 EmptyChain,
16 #[error("genesis event must have prev_event_hash=None (event_id={event_id})")]
18 InvalidGenesis {
19 event_id: String,
21 },
22 #[error("prev_event_hash mismatch at event_id={event_id}: expected {expected:?}, got {got:?}")]
24 PrevHashMismatch {
25 event_id: String,
27 expected: Option<String>,
29 got: Option<String>,
31 },
32 #[error("this_event_hash does not match recomputed (event_id={event_id})")]
34 HashMismatch {
35 event_id: String,
37 },
38 #[error("serialization error: {0}")]
40 Serde(#[from] serde_json::Error),
41}
42
43pub fn canonicalize(value: &serde_json::Value) -> Result<Vec<u8>, serde_json::Error> {
56 let mut out = Vec::new();
57 write_canonical(value, &mut out)?;
58 Ok(out)
59}
60
61fn write_canonical(value: &serde_json::Value, out: &mut Vec<u8>) -> Result<(), serde_json::Error> {
68 match value {
69 serde_json::Value::Object(map) => {
70 let mut keys: Vec<&String> = map.keys().collect();
71 keys.sort_by(|a, b| a.encode_utf16().cmp(b.encode_utf16()));
72 out.push(b'{');
73 for (i, key) in keys.iter().enumerate() {
74 if i > 0 {
75 out.push(b',');
76 }
77 out.extend(serde_jcs::to_vec(key)?);
78 out.push(b':');
79 write_canonical(&map[*key], out)?;
80 }
81 out.push(b'}');
82 }
83 serde_json::Value::Array(items) => {
84 out.push(b'[');
85 for (i, item) in items.iter().enumerate() {
86 if i > 0 {
87 out.push(b',');
88 }
89 write_canonical(item, out)?;
90 }
91 out.push(b']');
92 }
93 primitive => out.extend(serde_jcs::to_vec(primitive)?),
94 }
95 Ok(())
96}
97
98#[must_use]
106pub fn event_value_for_hashing(event: &serde_json::Value) -> serde_json::Value {
107 let mut v = event.clone();
108 if let serde_json::Value::Object(map) = &mut v {
109 map.remove("this_event_hash");
110 if matches!(map.get("signature"), Some(serde_json::Value::Null)) {
111 map.remove("signature");
112 }
113 }
114 v
115}
116
117pub fn compute_event_hash(
124 event_without_hash: &serde_json::Value,
125 prev_event_hash: Option<&str>,
126) -> Result<String, ChainError> {
127 let canonical = canonicalize(event_without_hash)?;
128 let mut hasher = Sha256::new();
129 hasher.update(&canonical);
130 hasher.update(prev_event_hash.unwrap_or("").as_bytes());
131 let digest = hasher.finalize();
132 Ok(format!("{HASH_PREFIX}{digest:x}"))
133}
134
135pub fn append_event(
143 mut event: AuditEvent,
144 prev_event_hash: Option<String>,
145) -> Result<AuditEvent, ChainError> {
146 event.prev_event_hash = prev_event_hash;
147 let value = event_value_for_hashing(&serde_json::to_value(&event)?);
148 let new_hash = compute_event_hash(&value, event.prev_event_hash.as_deref())?;
149 event.this_event_hash = new_hash;
150 Ok(event)
151}
152
153pub fn verify_event(event: &AuditEvent) -> Result<bool, ChainError> {
159 let value = event_value_for_hashing(&serde_json::to_value(event)?);
160 let expected = compute_event_hash(&value, event.prev_event_hash.as_deref())?;
161 Ok(expected == event.this_event_hash)
162}
163
164pub fn append_event_value(
175 event: &serde_json::Value,
176 prev_event_hash: Option<&str>,
177) -> Result<serde_json::Value, ChainError> {
178 let sealed = seal_prev(event, prev_event_hash)?;
179 finish_append(sealed, prev_event_hash)
180}
181
182#[cfg(feature = "signing")]
193pub fn append_event_value_signed(
194 event: &serde_json::Value,
195 prev_event_hash: Option<&str>,
196 signer: &crate::signing::Ed25519EventSigner,
197) -> Result<serde_json::Value, ChainError> {
198 let mut sealed = seal_prev(event, prev_event_hash)?;
199 let data = crate::signing::attestation_bytes(&sealed, prev_event_hash)?;
200 let signature = serde_json::json!({
201 "alg": crate::signing::SIGNATURE_ALG,
202 "key_id": signer.key_id(),
203 "value": signer.sign(&data),
204 });
205 if let Some(obj) = sealed.as_object_mut() {
206 obj.insert("signature".to_string(), signature);
207 }
208 finish_append(sealed, prev_event_hash)
209}
210
211fn seal_prev(
212 event: &serde_json::Value,
213 prev_event_hash: Option<&str>,
214) -> Result<serde_json::Value, ChainError> {
215 let mut sealed = event.clone();
216 let obj = sealed
217 .as_object_mut()
218 .ok_or_else(|| ChainError::Serde(serde::de::Error::custom("event is not a JSON object")))?;
219 obj.insert(
220 "prev_event_hash".to_string(),
221 match prev_event_hash {
222 Some(h) => serde_json::Value::String(h.to_string()),
223 None => serde_json::Value::Null,
224 },
225 );
226 Ok(sealed)
227}
228
229fn finish_append(
230 mut sealed: serde_json::Value,
231 prev_event_hash: Option<&str>,
232) -> Result<serde_json::Value, ChainError> {
233 let view = event_value_for_hashing(&sealed);
234 let hash = compute_event_hash(&view, prev_event_hash)?;
235 if let Some(obj) = sealed.as_object_mut() {
236 obj.insert(
237 "this_event_hash".to_string(),
238 serde_json::Value::String(hash),
239 );
240 }
241 Ok(sealed)
242}
243
244pub fn verify_event_value(event: &serde_json::Value) -> Result<bool, ChainError> {
257 let obj = event
258 .as_object()
259 .ok_or_else(|| ChainError::Serde(serde::de::Error::custom("event is not a JSON object")))?;
260 let this_hash = obj
261 .get("this_event_hash")
262 .and_then(|v| v.as_str())
263 .ok_or_else(|| ChainError::Serde(serde::de::Error::custom("missing this_event_hash")))?;
264 let prev_hash = obj.get("prev_event_hash").and_then(|v| v.as_str());
265 let value = event_value_for_hashing(event);
266 let expected = compute_event_hash(&value, prev_hash)?;
267 Ok(expected == this_hash)
268}
269
270pub fn verify_chain_values(events: &[serde_json::Value]) -> Result<String, ChainError> {
280 let get_id = |e: &serde_json::Value| -> String {
281 e.get("event_id")
282 .and_then(|v| v.as_str())
283 .unwrap_or("<missing event_id>")
284 .to_string()
285 };
286
287 let first = events.first().ok_or(ChainError::EmptyChain)?;
288 if first.get("prev_event_hash").is_some_and(|v| !v.is_null()) {
289 return Err(ChainError::InvalidGenesis {
290 event_id: get_id(first),
291 });
292 }
293
294 let mut prev: Option<String> = None;
295 for ev in events {
296 let ev_prev = ev
297 .get("prev_event_hash")
298 .and_then(|v| v.as_str())
299 .map(str::to_string);
300 if ev_prev != prev {
301 return Err(ChainError::PrevHashMismatch {
302 event_id: get_id(ev),
303 expected: prev.clone(),
304 got: ev_prev,
305 });
306 }
307 if !verify_event_value(ev)? {
308 return Err(ChainError::HashMismatch {
309 event_id: get_id(ev),
310 });
311 }
312 prev = ev
313 .get("this_event_hash")
314 .and_then(|v| v.as_str())
315 .map(str::to_string);
316 }
317
318 prev.ok_or(ChainError::EmptyChain)
319}
320
321pub fn verify_chain(events: &[AuditEvent]) -> Result<String, ChainError> {
330 let first = events.first().ok_or(ChainError::EmptyChain)?;
331 if first.prev_event_hash.is_some() {
332 return Err(ChainError::InvalidGenesis {
333 event_id: first.event_id.clone(),
334 });
335 }
336
337 let mut prev: Option<String> = None;
338 for ev in events {
339 if ev.prev_event_hash != prev {
340 return Err(ChainError::PrevHashMismatch {
341 event_id: ev.event_id.clone(),
342 expected: prev.clone(),
343 got: ev.prev_event_hash.clone(),
344 });
345 }
346 if !verify_event(ev)? {
347 return Err(ChainError::HashMismatch {
348 event_id: ev.event_id.clone(),
349 });
350 }
351 prev = Some(ev.this_event_hash.clone());
352 }
353
354 prev.ok_or(ChainError::EmptyChain)
355}