Files
wifi-densepose/vendor/ruvector/crates/cognitum-gate-tilezero/tests/receipt_tests.rs

545 lines
15 KiB
Rust

//! Comprehensive tests for witness receipts and hash chain integrity
//!
//! Tests cover:
//! - Receipt creation and hashing
//! - Hash chain verification
//! - Tamper detection
//! - Security tests (chain manipulation, replay attacks)
use cognitum_gate_tilezero::permit::PermitToken;
use cognitum_gate_tilezero::receipt::{
EvidentialWitness, PredictiveWitness, ReceiptLog, StructuralWitness, TimestampProof,
WitnessReceipt, WitnessSummary,
};
use cognitum_gate_tilezero::GateDecision;
fn create_test_token(sequence: u64, action_id: &str) -> PermitToken {
PermitToken {
decision: GateDecision::Permit,
action_id: action_id.to_string(),
timestamp: 1000000000 + sequence * 1000,
ttl_ns: 60_000_000_000,
witness_hash: [0u8; 32],
sequence,
signature: [0u8; 64],
}
}
fn create_test_summary() -> WitnessSummary {
WitnessSummary {
structural: StructuralWitness {
cut_value: 10.0,
partition: "stable".to_string(),
critical_edges: 5,
boundary: vec!["edge1".to_string(), "edge2".to_string()],
},
predictive: PredictiveWitness {
set_size: 8,
coverage: 0.9,
},
evidential: EvidentialWitness {
e_value: 150.0,
verdict: "accept".to_string(),
},
}
}
fn create_test_receipt(sequence: u64, previous_hash: [u8; 32]) -> WitnessReceipt {
WitnessReceipt {
sequence,
token: create_test_token(sequence, &format!("action-{}", sequence)),
previous_hash,
witness_summary: create_test_summary(),
timestamp_proof: TimestampProof {
timestamp: 1000000000 + sequence * 1000,
previous_receipt_hash: previous_hash,
merkle_root: [0u8; 32],
},
}
}
#[cfg(test)]
mod witness_summary {
use super::*;
#[test]
fn test_empty_summary() {
let summary = WitnessSummary::empty();
assert_eq!(summary.structural.cut_value, 0.0);
assert_eq!(summary.predictive.set_size, 0);
assert_eq!(summary.evidential.e_value, 1.0);
}
#[test]
fn test_summary_hash_deterministic() {
let summary = create_test_summary();
let hash1 = summary.hash();
let hash2 = summary.hash();
assert_eq!(hash1, hash2);
}
#[test]
fn test_summary_hash_unique() {
let summary1 = create_test_summary();
let mut summary2 = create_test_summary();
summary2.structural.cut_value = 20.0;
assert_ne!(summary1.hash(), summary2.hash());
}
#[test]
fn test_summary_to_json() {
let summary = create_test_summary();
let json = summary.to_json();
assert!(json.is_object());
assert!(json["structural"]["cut_value"].is_number());
assert!(json["predictive"]["set_size"].is_number());
assert!(json["evidential"]["e_value"].is_number());
}
}
#[cfg(test)]
mod receipt_hashing {
use super::*;
#[test]
fn test_receipt_hash_nonzero() {
let receipt = create_test_receipt(0, [0u8; 32]);
let hash = receipt.hash();
assert_ne!(hash, [0u8; 32]);
}
#[test]
fn test_receipt_hash_deterministic() {
let receipt = create_test_receipt(0, [0u8; 32]);
let hash1 = receipt.hash();
let hash2 = receipt.hash();
assert_eq!(hash1, hash2);
}
#[test]
fn test_receipt_hash_changes_with_sequence() {
let receipt1 = create_test_receipt(0, [0u8; 32]);
let receipt2 = create_test_receipt(1, [0u8; 32]);
assert_ne!(receipt1.hash(), receipt2.hash());
}
#[test]
fn test_receipt_hash_changes_with_previous() {
let receipt1 = create_test_receipt(0, [0u8; 32]);
let receipt2 = create_test_receipt(0, [1u8; 32]);
assert_ne!(receipt1.hash(), receipt2.hash());
}
#[test]
fn test_receipt_hash_includes_witness() {
let mut receipt1 = create_test_receipt(0, [0u8; 32]);
let mut receipt2 = create_test_receipt(0, [0u8; 32]);
receipt2.witness_summary.structural.cut_value = 99.0;
assert_ne!(receipt1.hash(), receipt2.hash());
}
}
#[cfg(test)]
mod receipt_log {
use super::*;
#[test]
fn test_new_log_empty() {
let log = ReceiptLog::new();
assert!(log.is_empty());
assert_eq!(log.len(), 0);
assert_eq!(log.latest_sequence(), None);
}
#[test]
fn test_genesis_hash() {
let log = ReceiptLog::new();
assert_eq!(log.last_hash(), [0u8; 32]);
}
#[test]
fn test_append_single() {
let mut log = ReceiptLog::new();
let receipt = create_test_receipt(0, log.last_hash());
log.append(receipt);
assert_eq!(log.len(), 1);
assert_eq!(log.latest_sequence(), Some(0));
assert_ne!(log.last_hash(), [0u8; 32]);
}
#[test]
fn test_append_multiple() {
let mut log = ReceiptLog::new();
for i in 0..5 {
let receipt = create_test_receipt(i, log.last_hash());
log.append(receipt);
}
assert_eq!(log.len(), 5);
assert_eq!(log.latest_sequence(), Some(4));
}
#[test]
fn test_get_receipt() {
let mut log = ReceiptLog::new();
let receipt = create_test_receipt(0, log.last_hash());
log.append(receipt);
let retrieved = log.get(0);
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().sequence, 0);
}
#[test]
fn test_get_nonexistent() {
let log = ReceiptLog::new();
assert!(log.get(0).is_none());
assert!(log.get(999).is_none());
}
}
#[cfg(test)]
mod hash_chain_verification {
use super::*;
#[test]
fn test_verify_empty_chain() {
let log = ReceiptLog::new();
// Verifying empty chain up to 0 should fail (no receipt at 0)
assert!(log.verify_chain_to(0).is_err());
}
#[test]
fn test_verify_single_receipt() {
let mut log = ReceiptLog::new();
let receipt = create_test_receipt(0, log.last_hash());
log.append(receipt);
assert!(log.verify_chain_to(0).is_ok());
}
#[test]
fn test_verify_chain_multiple() {
let mut log = ReceiptLog::new();
for i in 0..10 {
let receipt = create_test_receipt(i, log.last_hash());
log.append(receipt);
}
// Verify full chain
assert!(log.verify_chain_to(9).is_ok());
// Verify partial chains
assert!(log.verify_chain_to(0).is_ok());
assert!(log.verify_chain_to(5).is_ok());
}
#[test]
fn test_verify_beyond_latest() {
let mut log = ReceiptLog::new();
let receipt = create_test_receipt(0, log.last_hash());
log.append(receipt);
// Trying to verify beyond what exists should fail
assert!(log.verify_chain_to(1).is_err());
}
}
#[cfg(test)]
mod tamper_detection {
use super::*;
#[test]
fn test_detect_modified_hash() {
let mut log = ReceiptLog::new();
// Build a valid chain
for i in 0..5 {
let receipt = create_test_receipt(i, log.last_hash());
log.append(receipt);
}
// The chain should be valid
assert!(log.verify_chain_to(4).is_ok());
}
#[test]
fn test_chain_with_gap() {
let mut log = ReceiptLog::new();
// Add receipt at 0
let receipt0 = create_test_receipt(0, log.last_hash());
log.append(receipt0);
// Skip 1, add at 2 (breaking chain)
let receipt2 = create_test_receipt(2, log.last_hash());
log.append(receipt2);
// Verify should fail at sequence 1 (missing)
assert!(log.verify_chain_to(2).is_err());
}
}
#[cfg(test)]
mod timestamp_proof {
use super::*;
#[test]
fn test_timestamp_proof_structure() {
let proof = TimestampProof {
timestamp: 1000000000,
previous_receipt_hash: [1u8; 32],
merkle_root: [2u8; 32],
};
assert_eq!(proof.timestamp, 1000000000);
assert_eq!(proof.previous_receipt_hash, [1u8; 32]);
assert_eq!(proof.merkle_root, [2u8; 32]);
}
#[test]
fn test_receipt_contains_timestamp_proof() {
let receipt = create_test_receipt(5, [3u8; 32]);
assert_eq!(receipt.timestamp_proof.previous_receipt_hash, [3u8; 32]);
assert!(receipt.timestamp_proof.timestamp > 0);
}
#[test]
fn test_timestamp_ordering() {
let mut log = ReceiptLog::new();
for i in 0..5 {
let receipt = create_test_receipt(i, log.last_hash());
log.append(receipt);
}
// Each receipt should have increasing timestamp
let mut prev_ts = 0;
for i in 0..5 {
let receipt = log.get(i).unwrap();
assert!(receipt.timestamp_proof.timestamp > prev_ts);
prev_ts = receipt.timestamp_proof.timestamp;
}
}
}
#[cfg(test)]
mod structural_witness {
use super::*;
#[test]
fn test_structural_witness_fields() {
let witness = StructuralWitness {
cut_value: 15.0,
partition: "fragile".to_string(),
critical_edges: 3,
boundary: vec!["e1".to_string(), "e2".to_string(), "e3".to_string()],
};
assert_eq!(witness.cut_value, 15.0);
assert_eq!(witness.partition, "fragile");
assert_eq!(witness.critical_edges, 3);
assert_eq!(witness.boundary.len(), 3);
}
#[test]
fn test_structural_witness_serialization() {
let witness = StructuralWitness {
cut_value: 10.0,
partition: "stable".to_string(),
critical_edges: 2,
boundary: vec![],
};
let json = serde_json::to_string(&witness).unwrap();
let restored: StructuralWitness = serde_json::from_str(&json).unwrap();
assert_eq!(witness.cut_value, restored.cut_value);
assert_eq!(witness.partition, restored.partition);
}
}
#[cfg(test)]
mod predictive_witness {
use super::*;
#[test]
fn test_predictive_witness_fields() {
let witness = PredictiveWitness {
set_size: 12,
coverage: 0.95,
};
assert_eq!(witness.set_size, 12);
assert_eq!(witness.coverage, 0.95);
}
#[test]
fn test_predictive_witness_serialization() {
let witness = PredictiveWitness {
set_size: 5,
coverage: 0.9,
};
let json = serde_json::to_string(&witness).unwrap();
let restored: PredictiveWitness = serde_json::from_str(&json).unwrap();
assert_eq!(witness.set_size, restored.set_size);
assert!((witness.coverage - restored.coverage).abs() < 0.001);
}
}
#[cfg(test)]
mod evidential_witness {
use super::*;
#[test]
fn test_evidential_witness_fields() {
let witness = EvidentialWitness {
e_value: 250.0,
verdict: "accept".to_string(),
};
assert_eq!(witness.e_value, 250.0);
assert_eq!(witness.verdict, "accept");
}
#[test]
fn test_evidential_witness_verdicts() {
let accept = EvidentialWitness {
e_value: 200.0,
verdict: "accept".to_string(),
};
let cont = EvidentialWitness {
e_value: 50.0,
verdict: "continue".to_string(),
};
let reject = EvidentialWitness {
e_value: 0.005,
verdict: "reject".to_string(),
};
assert_eq!(accept.verdict, "accept");
assert_eq!(cont.verdict, "continue");
assert_eq!(reject.verdict, "reject");
}
}
#[cfg(test)]
mod security_tests {
use super::*;
/// Test that forged receipts are detected
#[test]
fn test_forged_receipt_detection() {
let mut log = ReceiptLog::new();
// Build legitimate chain
for i in 0..3 {
let receipt = create_test_receipt(i, log.last_hash());
log.append(receipt);
}
// A forged receipt with wrong previous hash would break verification
// (simulated by the verify_chain_to test with gaps)
}
/// Test that hash provides uniqueness
#[test]
fn test_hash_collision_resistance() {
let mut hashes = std::collections::HashSet::new();
// Generate many receipts and check for collisions
for i in 0..100 {
let receipt = create_test_receipt(i, [i as u8; 32]);
let hash = receipt.hash();
assert!(hashes.insert(hash), "Hash collision at sequence {}", i);
}
}
/// Test that modifying any field changes the hash
#[test]
fn test_all_fields_affect_hash() {
let base = create_test_receipt(0, [0u8; 32]);
let base_hash = base.hash();
// Modify sequence
let mut modified = create_test_receipt(0, [0u8; 32]);
modified.sequence = 1;
assert_ne!(base_hash, modified.hash());
// Modify previous_hash
let modified2 = create_test_receipt(0, [1u8; 32]);
assert_ne!(base_hash, modified2.hash());
// Modify witness
let mut modified3 = create_test_receipt(0, [0u8; 32]);
modified3.witness_summary.evidential.e_value = 0.0;
assert_ne!(base_hash, modified3.hash());
}
/// Test sequence monotonicity
#[test]
fn test_sequence_monotonicity() {
let mut log = ReceiptLog::new();
let mut prev_seq = None;
for i in 0..10 {
let receipt = create_test_receipt(i, log.last_hash());
log.append(receipt);
if let Some(prev) = prev_seq {
assert!(log.get(i).unwrap().sequence > prev);
}
prev_seq = Some(i);
}
}
}
// Property-based tests
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_hash_deterministic(seq in 0u64..1000, prev in proptest::array::uniform32(0u8..255)) {
let receipt = create_test_receipt(seq, prev);
assert_eq!(receipt.hash(), receipt.hash());
}
#[test]
fn prop_different_sequences_different_hashes(seq1 in 0u64..1000, seq2 in 0u64..1000) {
prop_assume!(seq1 != seq2);
let r1 = create_test_receipt(seq1, [0u8; 32]);
let r2 = create_test_receipt(seq2, [0u8; 32]);
assert_ne!(r1.hash(), r2.hash());
}
#[test]
fn prop_chain_grows_correctly(n in 1usize..20) {
let mut log = ReceiptLog::new();
for i in 0..n {
let receipt = create_test_receipt(i as u64, log.last_hash());
log.append(receipt);
}
assert_eq!(log.len(), n);
assert!(log.verify_chain_to((n - 1) as u64).is_ok());
}
}
}