use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::chain::AuditChain;
use crate::entry::{constant_time_eq, hash_field};
use crate::hasher::{ChainHasher, HASH_ALGORITHM};
use crate::merkle::MerkleTree;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct WitnessAnchor {
pub id: Uuid,
pub merkle_root: String,
pub entry_count: usize,
pub chain_head: String,
pub hash_algorithm: String,
pub created_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prev_anchor_hash: Option<String>,
pub hash: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct WitnessReceipt {
pub anchor_id: Uuid,
pub backend: String,
pub received_at: DateTime<Utc>,
pub receipt_data: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub receipt_hash: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum AnchorVerification {
Valid,
RootMismatch { expected: String, actual: String },
CountMismatch { expected: usize, actual: usize },
HeadMismatch { expected: String, actual: String },
}
pub trait WitnessBackend: Send + Sync {
fn publish(&self, anchor: &WitnessAnchor) -> crate::Result<WitnessReceipt>;
fn verify_receipt(
&self,
anchor: &WitnessAnchor,
receipt: &WitnessReceipt,
) -> crate::Result<bool>;
fn backend_id(&self) -> &str;
}
impl WitnessAnchor {
#[must_use]
pub fn new(tree: &MerkleTree, chain: &AuditChain) -> Option<Self> {
let chain_head = chain.head_hash()?.to_owned();
Some(Self::from_tree(tree, chain_head))
}
#[must_use]
pub fn from_tree(tree: &MerkleTree, chain_head: impl Into<String>) -> Self {
let mut anchor = Self {
id: Uuid::new_v4(),
merkle_root: tree.root().to_owned(),
entry_count: tree.leaf_count(),
chain_head: chain_head.into(),
hash_algorithm: HASH_ALGORITHM.to_owned(),
created_at: Utc::now(),
prev_anchor_hash: None,
hash: String::new(),
};
anchor.hash = anchor.compute_hash();
anchor
}
#[must_use]
pub fn with_prev_anchor(mut self, prev: &WitnessAnchor) -> Self {
self.prev_anchor_hash = Some(prev.hash.clone());
self.hash = self.compute_hash();
self
}
#[must_use]
pub fn verify_against(&self, tree: &MerkleTree, chain: &AuditChain) -> AnchorVerification {
let actual_head = chain.head_hash().unwrap_or("");
if !constant_time_eq(&self.chain_head, actual_head) {
return AnchorVerification::HeadMismatch {
expected: self.chain_head.clone(),
actual: actual_head.to_owned(),
};
}
self.verify_against_tree(tree)
}
#[must_use]
pub fn verify_against_tree(&self, tree: &MerkleTree) -> AnchorVerification {
if self.entry_count != tree.leaf_count() {
return AnchorVerification::CountMismatch {
expected: self.entry_count,
actual: tree.leaf_count(),
};
}
if !constant_time_eq(&self.merkle_root, tree.root()) {
return AnchorVerification::RootMismatch {
expected: self.merkle_root.clone(),
actual: tree.root().to_owned(),
};
}
AnchorVerification::Valid
}
#[must_use]
pub fn verify_integrity(&self) -> bool {
constant_time_eq(&self.hash, &self.compute_hash())
}
fn compute_hash(&self) -> String {
let mut hasher = ChainHasher::new();
hasher.update(self.id.as_bytes());
hash_field(&mut hasher, self.merkle_root.as_bytes());
hasher.update(&self.entry_count.to_le_bytes());
hash_field(&mut hasher, self.chain_head.as_bytes());
hash_field(&mut hasher, self.hash_algorithm.as_bytes());
hash_field(&mut hasher, self.created_at.to_rfc3339().as_bytes());
hash_field(
&mut hasher,
self.prev_anchor_hash.as_deref().unwrap_or("").as_bytes(),
);
hasher.finalize_hex()
}
}
impl WitnessReceipt {
pub fn new(
anchor_id: Uuid,
backend: impl Into<String>,
receipt_data: serde_json::Value,
) -> Self {
Self {
anchor_id,
backend: backend.into(),
received_at: Utc::now(),
receipt_data,
receipt_hash: None,
}
}
pub fn with_hash(mut self) -> Self {
let mut hasher = ChainHasher::new();
hasher.update(self.anchor_id.as_bytes());
hash_field(&mut hasher, self.backend.as_bytes());
hash_field(
&mut hasher,
serde_json::to_string(&self.receipt_data)
.unwrap_or_default()
.as_bytes(),
);
self.receipt_hash = Some(hasher.finalize_hex());
self
}
}
impl AnchorVerification {
#[inline]
#[must_use]
pub fn is_valid(&self) -> bool {
matches!(self, Self::Valid)
}
}
impl std::fmt::Display for AnchorVerification {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Valid => write!(f, "valid"),
Self::RootMismatch { expected, actual } => {
write!(f, "root mismatch: expected {expected}, got {actual}")
}
Self::CountMismatch { expected, actual } => {
write!(f, "count mismatch: expected {expected}, got {actual}")
}
Self::HeadMismatch { expected, actual } => {
write!(f, "head mismatch: expected {expected}, got {actual}")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::entry::EventSeverity;
fn make_chain(n: usize) -> AuditChain {
let mut chain = AuditChain::new();
for i in 0..n {
chain.append(
EventSeverity::Info,
"s",
format!("e{i}"),
serde_json::json!({}),
);
}
chain
}
#[test]
fn anchor_from_chain() {
let chain = make_chain(5);
let tree = MerkleTree::build(chain.entries()).unwrap();
let anchor = WitnessAnchor::new(&tree, &chain).unwrap();
assert_eq!(anchor.merkle_root, tree.root());
assert_eq!(anchor.entry_count, 5);
assert_eq!(anchor.chain_head, chain.head_hash().unwrap());
assert!(!anchor.hash.is_empty());
assert!(anchor.prev_anchor_hash.is_none());
}
#[test]
fn anchor_none_for_empty_chain() {
let chain = AuditChain::new();
assert!(MerkleTree::build(chain.entries()).is_none());
}
#[test]
fn anchor_from_tree() {
let chain = make_chain(3);
let tree = MerkleTree::build(chain.entries()).unwrap();
let anchor = WitnessAnchor::from_tree(&tree, "explicit_head");
assert_eq!(anchor.chain_head, "explicit_head");
assert_eq!(anchor.merkle_root, tree.root());
}
#[test]
fn anchor_verify_against_valid() {
let chain = make_chain(5);
let tree = MerkleTree::build(chain.entries()).unwrap();
let anchor = WitnessAnchor::new(&tree, &chain).unwrap();
assert!(anchor.verify_against(&tree, &chain).is_valid());
}
#[test]
fn anchor_verify_root_mismatch() {
let chain = make_chain(5);
let tree = MerkleTree::build(chain.entries()).unwrap();
let anchor = WitnessAnchor::new(&tree, &chain).unwrap();
let chain2 = make_chain(5); let tree2 = MerkleTree::build(chain2.entries()).unwrap();
let result = anchor.verify_against_tree(&tree2);
assert!(!result.is_valid());
assert!(matches!(result, AnchorVerification::RootMismatch { .. }));
}
#[test]
fn anchor_verify_count_mismatch() {
let chain = make_chain(5);
let tree = MerkleTree::build(chain.entries()).unwrap();
let anchor = WitnessAnchor::new(&tree, &chain).unwrap();
let chain2 = make_chain(3);
let tree2 = MerkleTree::build(chain2.entries()).unwrap();
let result = anchor.verify_against_tree(&tree2);
assert!(matches!(result, AnchorVerification::CountMismatch { .. }));
}
#[test]
fn anchor_verify_head_mismatch() {
let chain1 = make_chain(5);
let tree = MerkleTree::build(chain1.entries()).unwrap();
let anchor = WitnessAnchor::new(&tree, &chain1).unwrap();
let chain2 = make_chain(5);
let result = anchor.verify_against(&tree, &chain2);
assert!(matches!(result, AnchorVerification::HeadMismatch { .. }));
}
#[test]
fn anchor_verify_empty_chain_head_mismatch() {
let chain = make_chain(3);
let tree = MerkleTree::build(chain.entries()).unwrap();
let anchor = WitnessAnchor::new(&tree, &chain).unwrap();
let empty_chain = AuditChain::new();
let result = anchor.verify_against(&tree, &empty_chain);
assert!(matches!(result, AnchorVerification::HeadMismatch { .. }));
}
#[test]
fn anchor_field_boundary_ambiguity() {
let chain = make_chain(3);
let tree = MerkleTree::build(chain.entries()).unwrap();
let a1 = WitnessAnchor::from_tree(&tree, "ab");
let a2 = WitnessAnchor::from_tree(&tree, "abc");
assert_ne!(a1.hash, a2.hash);
}
#[test]
fn anchor_integrity() {
let chain = make_chain(3);
let tree = MerkleTree::build(chain.entries()).unwrap();
let anchor = WitnessAnchor::new(&tree, &chain).unwrap();
assert!(anchor.verify_integrity());
}
#[test]
fn anchor_integrity_tampered() {
let chain = make_chain(3);
let tree = MerkleTree::build(chain.entries()).unwrap();
let mut anchor = WitnessAnchor::new(&tree, &chain).unwrap();
anchor.merkle_root = "tampered".to_owned();
assert!(!anchor.verify_integrity());
}
#[test]
fn anchor_chaining() {
let chain1 = make_chain(3);
let tree1 = MerkleTree::build(chain1.entries()).unwrap();
let anchor1 = WitnessAnchor::new(&tree1, &chain1).unwrap();
let chain2 = make_chain(5);
let tree2 = MerkleTree::build(chain2.entries()).unwrap();
let anchor2 = WitnessAnchor::new(&tree2, &chain2)
.unwrap()
.with_prev_anchor(&anchor1);
assert_eq!(
anchor2.prev_anchor_hash.as_deref(),
Some(anchor1.hash.as_str())
);
assert!(anchor2.verify_integrity());
assert_ne!(anchor1.hash, anchor2.hash);
}
#[test]
fn receipt_creation() {
let receipt = WitnessReceipt::new(
Uuid::new_v4(),
"test-backend",
serde_json::json!({"tx": "abc123"}),
);
assert_eq!(receipt.backend, "test-backend");
assert!(receipt.receipt_hash.is_none());
}
#[test]
fn receipt_with_hash() {
let receipt = WitnessReceipt::new(
Uuid::new_v4(),
"test-backend",
serde_json::json!({"tx": "abc123"}),
)
.with_hash();
assert!(receipt.receipt_hash.is_some());
}
#[test]
fn serde_roundtrip_anchor() {
let chain = make_chain(3);
let tree = MerkleTree::build(chain.entries()).unwrap();
let anchor = WitnessAnchor::new(&tree, &chain).unwrap();
let json = serde_json::to_string(&anchor).unwrap();
let back: WitnessAnchor = serde_json::from_str(&json).unwrap();
assert_eq!(back.hash, anchor.hash);
assert!(back.verify_integrity());
}
#[test]
fn serde_roundtrip_receipt() {
let receipt = WitnessReceipt::new(Uuid::new_v4(), "test", serde_json::json!({"data": 42}))
.with_hash();
let json = serde_json::to_string(&receipt).unwrap();
let back: WitnessReceipt = serde_json::from_str(&json).unwrap();
assert_eq!(back.receipt_hash, receipt.receipt_hash);
}
#[test]
fn serde_roundtrip_verification() {
let v = AnchorVerification::RootMismatch {
expected: "a".into(),
actual: "b".into(),
};
let json = serde_json::to_string(&v).unwrap();
let back: AnchorVerification = serde_json::from_str(&json).unwrap();
assert_eq!(v, back);
}
#[test]
fn verification_display() {
assert_eq!(AnchorVerification::Valid.to_string(), "valid");
let m = AnchorVerification::CountMismatch {
expected: 5,
actual: 3,
};
assert!(m.to_string().contains("5"));
assert!(m.to_string().contains("3"));
}
}