1use std::sync::{Arc, Mutex};
55use std::time::{SystemTime, UNIX_EPOCH};
56
57pub const VERSION: &str = env!("CARGO_PKG_VERSION");
58
59pub const GENESIS_PREV_HASH: &str =
61 "0000000000000000000000000000000000000000000000000000000000000000";
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct AuditEvent {
66 pub sequence: u64,
68 pub event_id: String,
70 pub timestamp: String,
72 pub event_type: String,
74 pub actor: String,
76 pub payload: String,
78 pub prev_hash: String,
80 pub event_hash: String,
82 pub signature: Option<String>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ChainVerificationReport {
89 pub valid: bool,
91 pub total_events: usize,
93 pub root_hash: String,
95 pub head_hash: String,
97 pub first_broken_sequence: Option<u64>,
99 pub error_detail: Option<String>,
101 pub checked_at: String,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
107pub enum LedgerError {
108 SequenceDiscontinuity(String),
110 HashMismatch(String),
112 LockError(String),
114}
115
116impl std::fmt::Display for LedgerError {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 match self {
119 Self::SequenceDiscontinuity(msg) => write!(f, "Sequence discontinuity: {}", msg),
120 Self::HashMismatch(msg) => write!(f, "Hash mismatch: {}", msg),
121 Self::LockError(msg) => write!(f, "Lock error: {}", msg),
122 }
123 }
124}
125
126impl std::error::Error for LedgerError {}
127
128pub trait AuditLedgerWriter: Send + Sync {
130 fn append(
132 &mut self,
133 event_type: &str,
134 actor: &str,
135 payload: &str,
136 ) -> Result<AuditEvent, LedgerError>;
137}
138
139pub trait AuditLedgerReader: Send + Sync {
141 fn get_event(&self, sequence: u64) -> Result<Option<AuditEvent>, LedgerError>;
143 fn head(&self) -> Result<Option<AuditEvent>, LedgerError>;
145 fn all_events(&self) -> Result<Vec<AuditEvent>, LedgerError>;
147}
148
149pub trait AuditVerifier: Send + Sync {
151 fn verify_chain(&self) -> Result<ChainVerificationReport, LedgerError>;
153}
154
155pub fn sha256_hex(input: &[u8]) -> String {
157 let mut h: [u32; 8] = [
158 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
159 0x5be0cd19,
160 ];
161
162 let k: [u32; 64] = [
163 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
164 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
165 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
166 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
167 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
168 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
169 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
170 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
171 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
172 0xc67178f2,
173 ];
174
175 let bit_len = (input.len() as u64) * 8;
176 let mut msg = input.to_vec();
177 msg.push(0x80);
178 while (msg.len() % 64) != 56 {
179 msg.push(0x00);
180 }
181 msg.extend_from_slice(&bit_len.to_be_bytes());
182
183 for chunk in msg.chunks_exact(64) {
184 let mut w = [0u32; 64];
185 for i in 0..16 {
186 w[i] = u32::from_be_bytes([
187 chunk[i * 4],
188 chunk[i * 4 + 1],
189 chunk[i * 4 + 2],
190 chunk[i * 4 + 3],
191 ]);
192 }
193 for i in 16..64 {
194 let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
195 let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
196 w[i] = w[i - 16]
197 .wrapping_add(s0)
198 .wrapping_add(w[i - 7])
199 .wrapping_add(s1);
200 }
201
202 let mut a = h[0];
203 let mut b = h[1];
204 let mut c = h[2];
205 let mut d = h[3];
206 let mut e = h[4];
207 let mut f = h[5];
208 let mut g = h[6];
209 let mut h_var = h[7];
210
211 for i in 0..64 {
212 let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
213 let ch = (e & f) ^ ((!e) & g);
214 let temp1 = h_var
215 .wrapping_add(s1)
216 .wrapping_add(ch)
217 .wrapping_add(k[i])
218 .wrapping_add(w[i]);
219 let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
220 let maj = (a & b) ^ (a & c) ^ (b & c);
221 let temp2 = s0.wrapping_add(maj);
222
223 h_var = g;
224 g = f;
225 f = e;
226 e = d.wrapping_add(temp1);
227 d = c;
228 c = b;
229 b = a;
230 a = temp1.wrapping_add(temp2);
231 }
232
233 h[0] = h[0].wrapping_add(a);
234 h[1] = h[1].wrapping_add(b);
235 h[2] = h[2].wrapping_add(c);
236 h[3] = h[3].wrapping_add(d);
237 h[4] = h[4].wrapping_add(e);
238 h[5] = h[5].wrapping_add(f);
239 h[6] = h[6].wrapping_add(g);
240 h[7] = h[7].wrapping_add(h_var);
241 }
242
243 let mut result = String::with_capacity(64);
244 for val in h {
245 result.push_str(&format!("{:08x}", val));
246 }
247 result
248}
249
250pub fn compute_event_hash(
252 sequence: u64,
253 event_id: &str,
254 timestamp: &str,
255 event_type: &str,
256 actor: &str,
257 prev_hash: &str,
258 payload: &str,
259) -> String {
260 let header = format!(
261 "{}:{}:{}:{}:{}:{}:{}",
262 sequence, event_id, timestamp, event_type, actor, prev_hash, payload
263 );
264 sha256_hex(header.as_bytes())
265}
266
267#[derive(Default, Clone)]
269pub struct InMemoryLedger {
270 events: Arc<Mutex<Vec<AuditEvent>>>,
271}
272
273impl InMemoryLedger {
274 pub fn new() -> Self {
276 Self::default()
277 }
278}
279
280impl AuditLedgerWriter for InMemoryLedger {
281 fn append(
282 &mut self,
283 event_type: &str,
284 actor: &str,
285 payload: &str,
286 ) -> Result<AuditEvent, LedgerError> {
287 let mut store = self
288 .events
289 .lock()
290 .map_err(|e| LedgerError::LockError(e.to_string()))?;
291 let sequence = (store.len() as u64) + 1;
292 let prev_hash = if let Some(last) = store.last() {
293 last.event_hash.clone()
294 } else {
295 GENESIS_PREV_HASH.to_string()
296 };
297
298 let now_sec = SystemTime::now()
299 .duration_since(UNIX_EPOCH)
300 .unwrap_or_default()
301 .as_secs();
302 let timestamp = format!("{}", now_sec);
303 let event_id = format!("ev_{}_{}", sequence, now_sec);
304
305 let event_hash = compute_event_hash(
306 sequence, &event_id, ×tamp, event_type, actor, &prev_hash, payload,
307 );
308
309 let event = AuditEvent {
310 sequence,
311 event_id,
312 timestamp,
313 event_type: event_type.to_string(),
314 actor: actor.to_string(),
315 payload: payload.to_string(),
316 prev_hash,
317 event_hash,
318 signature: None,
319 };
320
321 store.push(event.clone());
322 Ok(event)
323 }
324}
325
326impl AuditLedgerReader for InMemoryLedger {
327 fn get_event(&self, sequence: u64) -> Result<Option<AuditEvent>, LedgerError> {
328 let store = self
329 .events
330 .lock()
331 .map_err(|e| LedgerError::LockError(e.to_string()))?;
332 if sequence == 0 || sequence > (store.len() as u64) {
333 Ok(None)
334 } else {
335 Ok(Some(store[(sequence - 1) as usize].clone()))
336 }
337 }
338
339 fn head(&self) -> Result<Option<AuditEvent>, LedgerError> {
340 let store = self
341 .events
342 .lock()
343 .map_err(|e| LedgerError::LockError(e.to_string()))?;
344 Ok(store.last().cloned())
345 }
346
347 fn all_events(&self) -> Result<Vec<AuditEvent>, LedgerError> {
348 let store = self
349 .events
350 .lock()
351 .map_err(|e| LedgerError::LockError(e.to_string()))?;
352 Ok(store.clone())
353 }
354}
355
356impl AuditVerifier for InMemoryLedger {
357 fn verify_chain(&self) -> Result<ChainVerificationReport, LedgerError> {
358 let store = self
359 .events
360 .lock()
361 .map_err(|e| LedgerError::LockError(e.to_string()))?;
362 let now_sec = SystemTime::now()
363 .duration_since(UNIX_EPOCH)
364 .unwrap_or_default()
365 .as_secs();
366 let checked_at = format!("{}", now_sec);
367
368 if store.is_empty() {
369 return Ok(ChainVerificationReport {
370 valid: true,
371 total_events: 0,
372 root_hash: String::new(),
373 head_hash: String::new(),
374 first_broken_sequence: None,
375 error_detail: None,
376 checked_at,
377 });
378 }
379
380 let mut expected_prev = GENESIS_PREV_HASH.to_string();
381 for (idx, event) in store.iter().enumerate() {
382 let expected_seq = (idx as u64) + 1;
383 if event.sequence != expected_seq {
384 return Ok(ChainVerificationReport {
385 valid: false,
386 total_events: store.len(),
387 root_hash: store[0].event_hash.clone(),
388 head_hash: store.last().unwrap().event_hash.clone(),
389 first_broken_sequence: Some(event.sequence),
390 error_detail: Some(format!("Discontinuous sequence at index {}", idx)),
391 checked_at,
392 });
393 }
394
395 if event.prev_hash != expected_prev {
396 return Ok(ChainVerificationReport {
397 valid: false,
398 total_events: store.len(),
399 root_hash: store[0].event_hash.clone(),
400 head_hash: store.last().unwrap().event_hash.clone(),
401 first_broken_sequence: Some(event.sequence),
402 error_detail: Some(format!(
403 "Broken prev_hash chain at sequence {}",
404 event.sequence
405 )),
406 checked_at,
407 });
408 }
409
410 let expected_hash = compute_event_hash(
411 event.sequence,
412 &event.event_id,
413 &event.timestamp,
414 &event.event_type,
415 &event.actor,
416 &event.prev_hash,
417 &event.payload,
418 );
419
420 if event.event_hash != expected_hash {
421 return Ok(ChainVerificationReport {
422 valid: false,
423 total_events: store.len(),
424 root_hash: store[0].event_hash.clone(),
425 head_hash: store.last().unwrap().event_hash.clone(),
426 first_broken_sequence: Some(event.sequence),
427 error_detail: Some(format!(
428 "Tampered content hash at sequence {}",
429 event.sequence
430 )),
431 checked_at,
432 });
433 }
434
435 expected_prev = event.event_hash.clone();
436 }
437
438 Ok(ChainVerificationReport {
439 valid: true,
440 total_events: store.len(),
441 root_hash: store[0].event_hash.clone(),
442 head_hash: store.last().unwrap().event_hash.clone(),
443 first_broken_sequence: None,
444 error_detail: None,
445 checked_at,
446 })
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 #[test]
455 fn test_sha256_known_vector() {
456 assert_eq!(
457 sha256_hex(b"abc"),
458 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
459 );
460 }
461
462 #[test]
463 fn test_append_and_verify_chain() {
464 let mut ledger = InMemoryLedger::new();
465 let ev1 = ledger
466 .append("agent.init", "hermes", "{\"state\":\"ready\"}")
467 .unwrap();
468 assert_eq!(ev1.sequence, 1);
469 assert_eq!(ev1.prev_hash, GENESIS_PREV_HASH);
470
471 let ev2 = ledger
472 .append("tool.call", "hermes", "{\"tool\":\"kibisis\"}")
473 .unwrap();
474 assert_eq!(ev2.sequence, 2);
475 assert_eq!(ev2.prev_hash, ev1.event_hash);
476
477 let report = ledger.verify_chain().unwrap();
478 assert!(report.valid);
479 assert_eq!(report.total_events, 2);
480 assert_eq!(report.head_hash, ev2.event_hash);
481 }
482}