use crate::digest::{DigestAlgorithm, TypedDigest};
use alloc::vec::Vec;
use sha3::{Digest, Sha3_256};
#[derive(Debug, Clone)]
pub struct AuditTrace {
pub genesis_root: TypedDigest,
pub latest_root: TypedDigest,
pub events: Vec<TraceEvent>,
pub sequence: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TraceEvent {
PolicyEvaluated {
evaluator: &'static str,
result_hash: TypedDigest,
},
QuorumFormed {
epoch: u64,
quorum_hash: TypedDigest,
},
SnapshotSealed {
snapshot_id: [u8; 32],
snapshot_hash: TypedDigest,
anchor_height: Option<u64>,
confirmation_depth: Option<u64>,
finality_state: alloc::string::String,
},
ReplayApplied {
start_seq: u64,
end_seq: u64,
final_hash: TypedDigest,
},
}
impl TraceEvent {
#[must_use]
pub fn compute_hash(&self) -> TypedDigest {
let mut hasher = Sha3_256::new();
match self {
TraceEvent::PolicyEvaluated {
evaluator,
result_hash,
} => {
hasher.update(b"PolicyEvaluated");
hasher.update(evaluator.as_bytes());
hasher.update(result_hash.value);
}
TraceEvent::QuorumFormed { epoch, quorum_hash } => {
hasher.update(b"QuorumFormed");
hasher.update(epoch.to_be_bytes());
hasher.update(quorum_hash.value);
}
TraceEvent::SnapshotSealed {
snapshot_id,
snapshot_hash,
anchor_height,
confirmation_depth,
finality_state,
} => {
hasher.update(b"SnapshotSealed");
hasher.update(snapshot_id);
hasher.update(snapshot_hash.value);
if let Some(h) = anchor_height {
hasher.update(h.to_be_bytes());
}
if let Some(c) = confirmation_depth {
hasher.update(c.to_be_bytes());
}
hasher.update(finality_state.as_bytes());
}
TraceEvent::ReplayApplied {
start_seq,
end_seq,
final_hash,
} => {
hasher.update(b"ReplayApplied");
hasher.update(start_seq.to_be_bytes());
hasher.update(end_seq.to_be_bytes());
hasher.update(final_hash.value);
}
}
let result: [u8; 32] = hasher.finalize().into();
TypedDigest::new(DigestAlgorithm::Sha3_256, result)
}
}
impl AuditTrace {
#[must_use]
pub fn new(genesis_root: TypedDigest) -> Self {
Self {
genesis_root,
latest_root: genesis_root,
events: Vec::new(),
sequence: 0,
}
}
pub fn append_event(&mut self, event: TraceEvent) {
let event_hash = event.compute_hash();
let mut hasher = Sha3_256::new();
hasher.update(self.latest_root.value);
hasher.update(event_hash.value);
let next_root: [u8; 32] = hasher.finalize().into();
self.latest_root = TypedDigest::new(DigestAlgorithm::Sha3_256, next_root);
self.events.push(event);
self.sequence = self.sequence.saturating_add(1);
}
#[must_use]
pub fn verify_integrity(&self) -> bool {
let mut current_root = self.genesis_root;
for event in &self.events {
let event_hash = event.compute_hash();
let mut hasher = Sha3_256::new();
hasher.update(current_root.value);
hasher.update(event_hash.value);
let next_root: [u8; 32] = hasher.finalize().into();
current_root = TypedDigest::new(DigestAlgorithm::Sha3_256, next_root);
}
current_root == self.latest_root
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_audit_trace_append_and_verify() {
let genesis = TypedDigest::new(DigestAlgorithm::Sha3_256, [1; 32]);
let mut trace = AuditTrace::new(genesis);
trace.append_event(TraceEvent::PolicyEvaluated {
evaluator: "HardwarePolicy",
result_hash: TypedDigest::new(DigestAlgorithm::Sha3_256, [2; 32]),
});
trace.append_event(TraceEvent::QuorumFormed {
epoch: 42,
quorum_hash: TypedDigest::new(DigestAlgorithm::Sha3_256, [3; 32]),
});
assert_eq!(trace.events.len(), 2);
assert_eq!(trace.sequence, 2);
assert!(trace.verify_integrity());
}
#[test]
fn test_audit_trace_tamper_detection() {
let genesis = TypedDigest::new(DigestAlgorithm::Sha3_256, [1; 32]);
let mut trace = AuditTrace::new(genesis);
trace.append_event(TraceEvent::QuorumFormed {
epoch: 42,
quorum_hash: TypedDigest::new(DigestAlgorithm::Sha3_256, [3; 32]),
});
trace.events[0] = TraceEvent::QuorumFormed {
epoch: 99,
quorum_hash: TypedDigest::new(DigestAlgorithm::Sha3_256, [3; 32]),
};
assert!(!trace.verify_integrity());
}
}