use affidavit::chain::{recompute_chain, ChainAssembler, FORMAT_VERSION};
use affidavit::ocel::{build_event, object_ref, SeqCounter};
use affidavit::types::{canonical_bytes, Receipt};
use affidavit::verifier::verify;
fn honest_receipt() -> Receipt {
let mut counter = SeqCounter::new();
let mut asm = ChainAssembler::new();
for (ty, payload) in [
("emit", b"alpha".as_slice()),
("transform", b"beta".as_slice()),
("release", b"gamma".as_slice()),
] {
let ev = build_event(
ty,
vec![object_ref("o1", "artifact")],
payload,
&mut counter,
)
.expect("build event");
asm.append(ev).expect("append event");
}
asm.finalize()
}
fn stage<'a>(
verdict: &'a affidavit::types::Verdict,
name: &str,
) -> &'a affidavit::types::CheckOutcome {
verdict
.outcomes
.iter()
.find(|o| o.stage == name)
.unwrap_or_else(|| panic!("stage {name} present"))
}
#[test]
fn golden_honest_receipt_accepts() {
let receipt = honest_receipt();
assert_eq!(receipt.events.len(), 3);
let verdict = verify(&receipt);
assert!(
verdict.accepted,
"honest receipt must ACCEPT: {}",
verdict.reason
);
assert_eq!(verdict.reason, "all stages passed");
assert!(verdict.outcomes.iter().all(|o| o.passed));
}
#[test]
fn tamper_commitment_rejects_at_chain_integrity() {
let mut receipt = honest_receipt();
assert!(verify(&receipt).accepted);
let hex = receipt.events[1].payload_commitment.as_hex();
let mut chars: Vec<char> = hex.chars().collect();
chars[0] = if chars[0] == 'a' { 'b' } else { 'a' };
let forged: String = chars.into_iter().collect();
receipt.events[1].payload_commitment = affidavit::types::Blake3Hash::from_hex(forged);
let verdict = verify(&receipt);
assert!(!verdict.accepted, "tampered commitment must REJECT");
assert!(
!stage(&verdict, "chain_integrity").passed,
"tamper must be caught at chain_integrity"
);
}
#[test]
fn tamper_reorder_rejects() {
let mut receipt = honest_receipt();
assert!(verify(&receipt).accepted);
receipt.events.swap(0, 1);
let verdict = verify(&receipt);
assert!(!verdict.accepted, "reordered events must REJECT");
let continuity_failed = !stage(&verdict, "continuity").passed;
let chain_failed = !stage(&verdict, "chain_integrity").passed;
assert!(
continuity_failed || chain_failed,
"reorder must trip continuity and/or chain_integrity"
);
}
#[test]
fn tamper_inject_rejects_at_chain_integrity() {
let mut receipt = honest_receipt();
assert!(verify(&receipt).accepted);
let mut counter = SeqCounter::starting_at(receipt.events.len() as u64);
let injected = build_event(
"forged",
vec![object_ref("evil", "artifact")],
b"fabricated",
&mut counter,
)
.expect("build injected event");
receipt.events.push(injected);
let verdict = verify(&receipt);
assert!(!verdict.accepted, "injected event must REJECT");
assert!(
!stage(&verdict, "chain_integrity").passed,
"injection without re-chaining must be caught at chain_integrity"
);
}
#[test]
fn wrong_version_rejects_at_check_format() {
let mut receipt = honest_receipt();
assert_eq!(receipt.format_version, FORMAT_VERSION);
assert!(verify(&receipt).accepted);
receipt.format_version = "99.9.9".to_string();
let verdict = verify(&receipt);
assert!(!verdict.accepted, "wrong format_version must REJECT");
assert!(
!stage(&verdict, "check_format").passed,
"wrong version must be caught at check_format"
);
}
#[test]
fn determinism_identical_verdict_bytes() {
let receipt = honest_receipt();
let v1 = verify(&receipt);
let v2 = verify(&receipt);
let b1 = canonical_bytes(&v1).expect("serialize verdict 1");
let b2 = canonical_bytes(&v2).expect("serialize verdict 2");
assert_eq!(
b1, b2,
"verifying the same receipt must yield identical Verdict bytes"
);
assert_eq!(
recompute_chain(&receipt.events).unwrap(),
recompute_chain(&receipt.events).unwrap()
);
}