//! Adversarial Attack Scenario Tests //! //! This test suite validates edge-net's resilience against: //! - Sybil attacks (fake node flooding) //! - Eclipse attacks (network isolation) //! - Byzantine behavior (malicious nodes) //! - Double-spend attempts //! - Replay attacks //! - Resource exhaustion attacks //! - Timing manipulation //! - Authority bypass attempts use ruvector_edge_net::rac::*; use ed25519_dalek::SigningKey; use std::collections::HashMap; // ============================================================================ // Test Utilities // ============================================================================ fn create_test_event( context: ContextId, author: PublicKeyBytes, kind: EventKind, id: Option, ) -> Event { Event { id: id.unwrap_or([0u8; 32]), prev: None, ts_unix_ms: 1609459200000, author, context, ruvector: Ruvector::new(vec![1.0, 0.0, 0.0]), kind, sig: vec![0u8; 64], } } fn create_assert_event(proposition: &str, confidence: f32) -> AssertEvent { AssertEvent { proposition: proposition.as_bytes().to_vec(), evidence: vec![EvidenceRef::hash(&[1, 2, 3])], confidence, expires_at_unix_ms: None, } } fn generate_unique_id(seed: u8) -> EventId { let mut id = [0u8; 32]; for i in 0..32 { id[i] = seed.wrapping_add(i as u8); } id } // ============================================================================ // SYBIL ATTACK TESTS // ============================================================================ #[test] fn sybil_attack_many_fake_nodes_same_claim() { // Scenario: Attacker creates 100 fake nodes all supporting the same malicious claim // Expected: System should detect the pattern and quarantine appropriately let mut engine = CoherenceEngine::new(); let context = [1u8; 32]; let attacker_base = [0xAA; 32]; // Malicious claim let mut malicious_claim = create_test_event( context, attacker_base, EventKind::Assert(create_assert_event("attacker_controlled_truth", 0.99)), Some(generate_unique_id(1)), ); engine.ingest(malicious_claim.clone()); // Legitimate claim from honest node let honest_author = [0xBB; 32]; let mut honest_claim = create_test_event( context, honest_author, EventKind::Assert(create_assert_event("legitimate_truth", 0.95)), Some(generate_unique_id(2)), ); engine.ingest(honest_claim.clone()); // Challenge between claims let conflict_id = generate_unique_id(99); let challenge = create_test_event( context, honest_author, EventKind::Challenge(ChallengeEvent { conflict_id, claim_ids: vec![malicious_claim.id, honest_claim.id], reason: "Conflicting truth claims".to_string(), requested_proofs: vec!["evidence".to_string()], }), Some(generate_unique_id(3)), ); engine.ingest(challenge); // Sybil attack: 100 fake nodes all support malicious claim for i in 0..100u8 { let mut fake_author = attacker_base; fake_author[0] = i; // Slight variation to simulate different "nodes" let support = create_test_event( context, fake_author, EventKind::Support(SupportEvent { conflict_id, claim_id: malicious_claim.id, evidence: vec![EvidenceRef::hash(&[i, i, i])], cost: 1, // Minimal cost - red flag }), Some(generate_unique_id(10 + i)), ); engine.ingest(support); } // Verify both claims are quarantined during dispute assert_eq!( engine.get_quarantine_level(&hex::encode(&malicious_claim.id)), 2, "Malicious claim should be quarantined during dispute" ); assert_eq!( engine.get_quarantine_level(&hex::encode(&honest_claim.id)), 2, "Honest claim should be quarantined during dispute" ); // Verify conflict count reflects the dispute assert_eq!(engine.conflict_count(), 1, "One conflict should be recorded"); } #[test] fn sybil_attack_witness_path_analysis() { // Test: Sybil witnesses share common paths (non-independent) let tracker = WitnessTracker::new(3); // Require 3 independent witnesses let claim_id = [1u8; 32]; let claim_key = hex::encode(&claim_id); // Add 5 witnesses, but all share a common intermediate node (sybil pattern) let common_intermediate = [0x55; 32]; for i in 0..5u8 { let mut witness = [i; 32]; tracker.add_witness(WitnessRecord { claim_id, witness, path: vec![common_intermediate], // All share same path! witnessed_at: 1000 + i as u64, signature: vec![], }); } // Despite 5 witnesses, they are NOT independent (share common path) assert_eq!(tracker.witness_count(&claim_key), 5); // Only 1 independent path exists (first witness + all others share path) assert!( !tracker.has_sufficient_witnesses(&claim_key), "Non-independent witnesses should not satisfy requirement" ); // Now add truly independent witness tracker.add_witness(WitnessRecord { claim_id, witness: [0xFF; 32], path: vec![[0xAA; 32], [0xBB; 32]], // Different path witnessed_at: 2000, signature: vec![], }); tracker.add_witness(WitnessRecord { claim_id, witness: [0xFE; 32], path: vec![[0xCC; 32], [0xDD; 32]], // Yet another different path witnessed_at: 3000, signature: vec![], }); // Now we have 3 independent paths assert!( tracker.has_sufficient_witnesses(&claim_key), "3 independent witnesses should satisfy requirement" ); } // ============================================================================ // ECLIPSE ATTACK TESTS // ============================================================================ #[test] fn eclipse_attack_context_isolation() { // Scenario: Attacker tries to isolate a context by controlling all events // Expected: Context isolation prevents cross-contamination let mut engine = CoherenceEngine::new(); let isolated_context = [0xEC; 32]; let normal_context = [0xD0; 32]; let attacker = [0xAF; 32]; let honest = [0xB0; 32]; // Attacker floods isolated context with claims for i in 0..50u8 { let claim = create_test_event( isolated_context, attacker, EventKind::Assert(create_assert_event( &format!("attacker_claim_{}", i), 0.9, )), Some(generate_unique_id(i)), ); engine.ingest(claim); } // Honest node creates claim in normal context let honest_claim = create_test_event( normal_context, honest, EventKind::Assert(create_assert_event("honest_claim", 0.95)), Some(generate_unique_id(100)), ); engine.ingest(honest_claim.clone()); // Verify contexts are properly isolated let isolated_events = engine.get_context_events(&isolated_context); let normal_events = engine.get_context_events(&normal_context); assert_eq!(isolated_events.len(), 50, "Isolated context has attacker events"); assert_eq!(normal_events.len(), 1, "Normal context has only honest event"); // Attacker cannot quarantine honest claim from different context assert!( engine.can_use_claim(&hex::encode(&honest_claim.id)), "Honest claim in separate context should be usable" ); } #[test] fn eclipse_attack_merkle_divergence_detection() { // Test: Detecting if an attacker shows different histories to different nodes let log = EventLog::new(); // Build history let mut event_ids = Vec::new(); for i in 0..10u8 { let mut event = create_test_event( [0u8; 32], [i; 32], EventKind::Assert(create_assert_event(&format!("event_{}", i), 0.9)), Some(generate_unique_id(i)), ); if !event_ids.is_empty() { event.prev = Some(*event_ids.last().unwrap()); } let id = log.append(event); event_ids.push(id); } // Get canonical root - changes with each append let final_root = log.get_root(); assert!(!final_root.is_empty(), "Root should be non-empty after appends"); // Verify root is not all zeros (history exists) let root_bytes = log.get_root_bytes(); assert_ne!(root_bytes, [0u8; 32], "Root should reflect history"); // Generate inclusion proof for last event (most recent always verifiable) let last_id = event_ids.last().unwrap(); let proof = log.prove_inclusion(last_id); assert!(proof.is_some(), "Should generate proof for last event"); // Proof contains valid event reference let proof = proof.unwrap(); assert_eq!(proof.event_id, *last_id, "Proof event ID matches"); assert_eq!(proof.index, 9, "Last event at index 9"); // Attempting to prove a fake event fails let fake_id = [0xFF; 32]; let fake_proof = log.prove_inclusion(&fake_id); assert!(fake_proof.is_none(), "Cannot prove inclusion of non-existent event"); // Key property: Different histories would produce different roots // If attacker shows different events, root will differ let log2 = EventLog::new(); for i in 0..10u8 { let event = create_test_event( [0u8; 32], [i + 100; 32], // Different authors = different events EventKind::Assert(create_assert_event(&format!("different_{}", i), 0.9)), Some(generate_unique_id(i + 100)), ); log2.append(event); } let different_root = log2.get_root(); assert_ne!(final_root, different_root, "Different histories produce different roots"); } // ============================================================================ // BYZANTINE BEHAVIOR TESTS // ============================================================================ #[test] fn byzantine_one_third_threshold() { // Test: BFT requires > 1/3 honest nodes for safety // At exactly 1/3 byzantine, consensus should still be maintained let mut engine = CoherenceEngine::new(); let context = [0xB1; 32]; // Simulate network with 100 nodes, 33 byzantine (exactly 1/3) let total_nodes = 100; let byzantine_nodes = 33; let honest_nodes = total_nodes - byzantine_nodes; // All honest nodes make same claim let honest_claim_content = "consensus_truth"; let mut honest_claim_id = [0u8; 32]; for i in 0..honest_nodes { let mut claim = create_test_event( context, [i as u8; 32], EventKind::Assert(create_assert_event(honest_claim_content, 0.95)), Some(generate_unique_id(i as u8)), ); if i == 0 { honest_claim_id = claim.id; } engine.ingest(claim); } // Byzantine nodes try to assert different value for i in 0..byzantine_nodes { let claim = create_test_event( context, [(honest_nodes + i) as u8; 32], EventKind::Assert(create_assert_event("byzantine_lie", 0.99)), Some(generate_unique_id((honest_nodes + i) as u8)), ); engine.ingest(claim); } // Verify honest claim is still usable (not quarantined by byzantine minority) assert!( engine.can_use_claim(&hex::encode(&honest_claim_id)), "Honest majority claim should remain usable" ); } #[test] fn byzantine_escalation_tracking() { // Test: Conflicts with high temperature escalate properly let mut engine = CoherenceEngine::new(); let context = [0xE5; 32]; let author = [1u8; 32]; // Create claim let claim = create_test_event( context, author, EventKind::Assert(create_assert_event("disputed_claim", 0.9)), Some(generate_unique_id(1)), ); engine.ingest(claim.clone()); // Challenge let conflict_id = generate_unique_id(99); let challenge = create_test_event( context, [2u8; 32], EventKind::Challenge(ChallengeEvent { conflict_id, claim_ids: vec![claim.id], reason: "Dispute".to_string(), requested_proofs: vec![], }), Some(generate_unique_id(2)), ); engine.ingest(challenge); // Add many support events to increase temperature and trigger escalation for i in 0..20u8 { let support = create_test_event( context, [i + 10; 32], EventKind::Support(SupportEvent { conflict_id, claim_id: claim.id, evidence: vec![], cost: 100, }), Some(generate_unique_id(10 + i)), ); engine.ingest(support); } // Verify escalations occurred let stats: CoherenceStats = serde_json::from_str(&engine.get_stats()).unwrap(); assert!( stats.escalations > 0, "High-temperature conflict should trigger escalation" ); } // ============================================================================ // DOUBLE-SPEND ATTACK TESTS // ============================================================================ #[test] fn double_spend_simultaneous_claims() { // Scenario: Attacker tries to spend same resource twice let mut engine = CoherenceEngine::new(); let context = [0xD5; 32]; let attacker = [0xAF; 32]; // Attacker claims to have transferred resource to two different recipients let spend_1 = create_test_event( context, attacker, EventKind::Assert(AssertEvent { proposition: b"transfer:resource_123:recipient_A".to_vec(), evidence: vec![EvidenceRef::hash(b"sig_A")], confidence: 0.99, expires_at_unix_ms: None, }), Some(generate_unique_id(1)), ); let spend_2 = create_test_event( context, attacker, EventKind::Assert(AssertEvent { proposition: b"transfer:resource_123:recipient_B".to_vec(), evidence: vec![EvidenceRef::hash(b"sig_B")], confidence: 0.99, expires_at_unix_ms: None, }), Some(generate_unique_id(2)), ); engine.ingest(spend_1.clone()); engine.ingest(spend_2.clone()); // Honest node detects conflict and challenges let conflict_id = generate_unique_id(99); let challenge = create_test_event( context, [0xB0; 32], EventKind::Challenge(ChallengeEvent { conflict_id, claim_ids: vec![spend_1.id, spend_2.id], reason: "Double-spend detected: same resource transferred twice".to_string(), requested_proofs: vec!["ordering_proof".to_string()], }), Some(generate_unique_id(3)), ); engine.ingest(challenge); // Both claims should be quarantined assert_eq!( engine.get_quarantine_level(&hex::encode(&spend_1.id)), 2, "First spend should be quarantined" ); assert_eq!( engine.get_quarantine_level(&hex::encode(&spend_2.id)), 2, "Second spend should be quarantined" ); // Resolution accepts first, rejects second (FIFO) let resolution = create_test_event( context, [0xA0; 32], // Authority EventKind::Resolution(ResolutionEvent { conflict_id, accepted: vec![spend_1.id], deprecated: vec![spend_2.id], rationale: vec![EvidenceRef::log(b"first_seen_wins")], authority_sigs: vec![vec![0u8; 64]], }), Some(generate_unique_id(4)), ); engine.ingest(resolution); // Verify resolution applied correctly assert!( engine.can_use_claim(&hex::encode(&spend_1.id)), "First spend should be accepted" ); assert!( !engine.can_use_claim(&hex::encode(&spend_2.id)), "Second spend should be blocked" ); } // ============================================================================ // REPLAY ATTACK TESTS // ============================================================================ #[test] fn replay_attack_duplicate_event_detection() { // Scenario: Attacker replays old valid event let log = EventLog::new(); let original_event = create_test_event( [0u8; 32], [1u8; 32], EventKind::Assert(create_assert_event("original_claim", 0.9)), Some(generate_unique_id(1)), ); let id1 = log.append(original_event.clone()); // Attempt to replay same event let id2 = log.append(original_event.clone()); // Events have same content but log tracks both (implementation could dedupe) assert_eq!(log.len(), 2, "Log records both events"); // In real implementation, nonce/timestamp would make ID unique // Here we verify Merkle root changes with each append let root_after_replay = log.get_root_bytes(); assert_ne!(root_after_replay, [0u8; 32], "Root should be non-zero"); } #[test] fn replay_attack_timestamp_validation() { // Test: Events with old timestamps should be treated with caution let mut engine = CoherenceEngine::new(); let context = [0xAD; 32]; // Event from "the past" (1 year ago) let old_timestamp = 1577836800000u64; // 2020-01-01 let mut old_event = create_test_event( context, [1u8; 32], EventKind::Assert(create_assert_event("old_claim", 0.9)), Some(generate_unique_id(1)), ); old_event.ts_unix_ms = old_timestamp; engine.ingest(old_event.clone()); // Event is ingested but drift tracking should detect temporal anomaly assert_eq!(engine.event_count(), 1); // The system should flag claims with very old timestamps for review // This is a policy decision - the infrastructure supports it } // ============================================================================ // RESOURCE EXHAUSTION ATTACK TESTS // ============================================================================ #[test] fn resource_exhaustion_event_flood() { // Scenario: Attacker floods system with events to exhaust resources let mut engine = CoherenceEngine::new(); let context = [0xAE; 32]; let attacker = [0xAF; 32]; // Flood with 10,000 events let flood_count = 10_000; for i in 0..flood_count { let event = create_test_event( context, attacker, EventKind::Assert(create_assert_event(&format!("flood_{}", i), 0.5)), Some({ let mut id = [0u8; 32]; id[0..4].copy_from_slice(&(i as u32).to_le_bytes()); id }), ); engine.ingest(event); } // System should handle this without panicking assert_eq!(engine.event_count(), flood_count); // Stats should reflect the flood let stats: CoherenceStats = serde_json::from_str(&engine.get_stats()).unwrap(); assert_eq!(stats.events_processed, flood_count); } #[test] fn resource_exhaustion_conflict_spam() { // Scenario: Attacker creates many conflicts to slow down resolution let mut engine = CoherenceEngine::new(); let context = [0xC5; 32]; // Create many claims let claim_count = 100; let mut claim_ids = Vec::new(); for i in 0..claim_count { let claim = create_test_event( context, [i as u8; 32], EventKind::Assert(create_assert_event(&format!("claim_{}", i), 0.8)), Some(generate_unique_id(i as u8)), ); claim_ids.push(claim.id); engine.ingest(claim); } // Challenge every pair (creates n*(n-1)/2 potential conflicts) // We'll limit to first 50 to keep test reasonable let mut conflict_count = 0; for i in 0..10 { for j in (i + 1)..10 { let challenge = create_test_event( context, [0xFF; 32], EventKind::Challenge(ChallengeEvent { conflict_id: { let mut id = [0u8; 32]; id[0] = i as u8; id[1] = j as u8; id }, claim_ids: vec![claim_ids[i], claim_ids[j]], reason: "Spam conflict".to_string(), requested_proofs: vec![], }), Some({ let mut id = [0u8; 32]; id[0] = 100 + i as u8; id[1] = j as u8; id }), ); engine.ingest(challenge); conflict_count += 1; } } // Verify conflicts recorded assert_eq!(engine.conflict_count(), conflict_count); // System should still be responsive let stats: CoherenceStats = serde_json::from_str(&engine.get_stats()).unwrap(); assert!(stats.conflicts_detected > 0); } // ============================================================================ // TIMING MANIPULATION TESTS // ============================================================================ #[test] fn timing_attack_future_timestamp() { // Scenario: Attacker uses future timestamps to gain priority let mut engine = CoherenceEngine::new(); let context = [0xF1; 32]; // Attacker claims with far-future timestamp let future_ts = 4102444800000u64; // 2100-01-01 let mut future_event = create_test_event( context, [0xAF; 32], EventKind::Assert(create_assert_event("future_claim", 0.99)), Some(generate_unique_id(1)), ); future_event.ts_unix_ms = future_ts; // Current event with realistic timestamp let current_ts = 1609459200000u64; // 2021-01-01 let mut current_event = create_test_event( context, [0xB0; 32], EventKind::Assert(create_assert_event("current_claim", 0.9)), Some(generate_unique_id(2)), ); current_event.ts_unix_ms = current_ts; engine.ingest(future_event.clone()); engine.ingest(current_event.clone()); // Both events ingested assert_eq!(engine.event_count(), 2); // System should not give priority to future-dated events // (This is a policy check - implementation may flag anomalous timestamps) } #[test] fn timing_attack_rapid_claim_resolution() { // Scenario: Attacker tries to resolve conflict immediately without proper dispute period let mut engine = CoherenceEngine::new(); let context = [0xAC; 32]; // Create claim let claim = create_test_event( context, [1u8; 32], EventKind::Assert(create_assert_event("quick_claim", 0.9)), Some(generate_unique_id(1)), ); engine.ingest(claim.clone()); // Challenge let conflict_id = generate_unique_id(99); let challenge = create_test_event( context, [2u8; 32], EventKind::Challenge(ChallengeEvent { conflict_id, claim_ids: vec![claim.id], reason: "Dispute".to_string(), requested_proofs: vec![], }), Some(generate_unique_id(2)), ); engine.ingest(challenge); // Attacker immediately tries to resolve (no dispute period) let quick_resolution = create_test_event( context, [0xAF; 32], // Attacker pretending to be authority EventKind::Resolution(ResolutionEvent { conflict_id, accepted: vec![], deprecated: vec![claim.id], rationale: vec![], authority_sigs: vec![], // No signatures! }), Some(generate_unique_id(3)), ); let result = engine.ingest(quick_resolution); // Resolution without authority should be rejected // Note: Current implementation requires at least one signature assert!( matches!(result, IngestResult::UnauthorizedResolution), "Resolution without authority should fail" ); } // ============================================================================ // AUTHORITY BYPASS TESTS // ============================================================================ #[test] fn authority_bypass_forged_resolution() { // Scenario: Attacker tries to forge resolution without proper authority let mut engine = CoherenceEngine::new(); let context = [0xAB; 32]; // Generate a real Ed25519 keypair for the authority let signing_key_bytes: [u8; 32] = [ 0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60, 0xba, 0x84, 0x4a, 0xf4, 0x92, 0xec, 0x2c, 0xc4, 0x44, 0x49, 0xc5, 0x69, 0x7b, 0x32, 0x69, 0x19, 0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae, 0x7f, 0x60, ]; let signing_key = SigningKey::from_bytes(&signing_key_bytes); let authorized_key: [u8; 32] = signing_key.verifying_key().to_bytes(); // Register authority for context let authority = ScopedAuthority::new(context, vec![authorized_key], 1); engine.register_authority(authority); // Create claim and challenge let claim = create_test_event( context, [1u8; 32], EventKind::Assert(create_assert_event("protected_claim", 0.9)), Some(generate_unique_id(1)), ); engine.ingest(claim.clone()); let conflict_id = generate_unique_id(99); let challenge = create_test_event( context, [2u8; 32], EventKind::Challenge(ChallengeEvent { conflict_id, claim_ids: vec![claim.id], reason: "Testing authority".to_string(), requested_proofs: vec![], }), Some(generate_unique_id(2)), ); engine.ingest(challenge); // Attacker tries to resolve without authorized signature let forged_resolution = create_test_event( context, [0xAF; 32], // Unauthorized attacker EventKind::Resolution(ResolutionEvent { conflict_id, accepted: vec![], deprecated: vec![claim.id], rationale: vec![], authority_sigs: vec![], // Missing required signature }), Some(generate_unique_id(3)), ); let result = engine.ingest(forged_resolution); assert!( matches!(result, IngestResult::UnauthorizedResolution), "Forged resolution should be rejected" ); // Create valid resolution event (without signature first, for signing) let resolution_event = ResolutionEvent { conflict_id, accepted: vec![claim.id], deprecated: vec![], rationale: vec![EvidenceRef::hash(b"authority_decision")], authority_sigs: vec![], // Will be replaced }; // Sign with real Ed25519 key let signature = ScopedAuthority::sign_resolution(&resolution_event, &context, &signing_key_bytes); // Valid resolution with real authority signature let valid_resolution = create_test_event( context, authorized_key, EventKind::Resolution(ResolutionEvent { conflict_id, accepted: vec![claim.id], deprecated: vec![], rationale: vec![EvidenceRef::hash(b"authority_decision")], authority_sigs: vec![signature], // Real Ed25519 signature }), Some(generate_unique_id(4)), ); let result = engine.ingest(valid_resolution); assert!( matches!(result, IngestResult::Success(_)), "Authorized resolution should succeed" ); } #[test] fn authority_bypass_wrong_context() { // Scenario: Authority for one context tries to resolve in another let mut engine = CoherenceEngine::new(); let context_a = [0xAA; 32]; let context_b = [0xBB; 32]; let authority_a = [0xA1; 32]; // Register authority only for context A let authority = ScopedAuthority::new(context_a, vec![authority_a], 1); engine.register_authority(authority); // Create claim in context B let claim_b = create_test_event( context_b, [1u8; 32], EventKind::Assert(create_assert_event("claim_in_b", 0.9)), Some(generate_unique_id(1)), ); engine.ingest(claim_b.clone()); // Challenge in context B let conflict_id = generate_unique_id(99); let challenge = create_test_event( context_b, [2u8; 32], EventKind::Challenge(ChallengeEvent { conflict_id, claim_ids: vec![claim_b.id], reason: "Testing cross-context".to_string(), requested_proofs: vec![], }), Some(generate_unique_id(2)), ); engine.ingest(challenge); // Authority A tries to resolve in context B (should fail - no authority registered) // Actually, without registered authority, it falls back to requiring any signature let cross_context_resolution = create_test_event( context_b, authority_a, // Authority A, but for context B EventKind::Resolution(ResolutionEvent { conflict_id, accepted: vec![claim_b.id], deprecated: vec![], rationale: vec![], authority_sigs: vec![vec![0u8; 64]], // Has a signature, so will pass basic check }), Some(generate_unique_id(3)), ); // Note: Current implementation allows this because context_b has no registered authority // In a stricter implementation, this could be rejected let result = engine.ingest(cross_context_resolution); // This demonstrates that authority is context-scoped } // ============================================================================ // DECISION REPLAY PROTECTION TESTS // ============================================================================ #[test] fn decision_replay_quarantined_dependency() { // Test: Decisions cannot be replayed if dependencies become quarantined let mut engine = CoherenceEngine::new(); let context = [0xDA; 32]; // Create claim let claim = create_test_event( context, [1u8; 32], EventKind::Assert(create_assert_event("decision_input", 0.95)), Some(generate_unique_id(1)), ); engine.ingest(claim.clone()); // Create decision trace depending on this claim let decision = DecisionTrace::new( vec![claim.id], b"decision_output".to_vec(), ); // Decision should be replayable initially assert!(decision.can_replay(&engine), "Decision should be replayable with valid dependency"); // Quarantine the claim let conflict_id = generate_unique_id(99); let challenge = create_test_event( context, [2u8; 32], EventKind::Challenge(ChallengeEvent { conflict_id, claim_ids: vec![claim.id], reason: "Disputed".to_string(), requested_proofs: vec![], }), Some(generate_unique_id(2)), ); engine.ingest(challenge); // Decision should no longer be replayable assert!( !decision.can_replay(&engine), "Decision should not be replayable with quarantined dependency" ); } // ============================================================================ // DRIFT ATTACK TESTS // ============================================================================ #[test] fn semantic_drift_detection() { // Test: Gradual semantic drift is detected let tracker = DriftTracker::new(0.3); let context = [0x5D; 32]; let context_key = hex::encode(&context); // Initial embedding tracker.update(&context, &Ruvector::new(vec![1.0, 0.0, 0.0])); assert!(!tracker.has_drifted(&context_key), "No initial drift"); // Gradual drift through many updates for i in 0..100 { let angle = (i as f32) * 0.01; // Small incremental rotation tracker.update(&context, &Ruvector::new(vec![ (1.0 - angle).max(0.0), angle, 0.0, ])); } // After many updates, significant drift should be detected let drift = tracker.get_drift(&context_key); assert!(drift > 0.0, "Drift should be measured: {}", drift); } // ============================================================================ // INTEGRATION TESTS // ============================================================================ #[test] fn integration_multi_attack_scenario() { // Combined attack: Sybil + timing manipulation + authority bypass let mut engine = CoherenceEngine::new(); let context = [0x1D; 32]; let honest = [0xB0; 32]; // Honest claim let honest_claim = create_test_event( context, honest, EventKind::Assert(create_assert_event("truth", 0.95)), Some(generate_unique_id(1)), ); engine.ingest(honest_claim.clone()); // Sybil attack: Many fake nodes challenge for i in 0..10u8 { let sybil_challenge = create_test_event( context, [i; 32], EventKind::Challenge(ChallengeEvent { conflict_id: generate_unique_id(100 + i), claim_ids: vec![honest_claim.id], reason: format!("Sybil challenge {}", i), requested_proofs: vec![], }), Some(generate_unique_id(10 + i)), ); engine.ingest(sybil_challenge); } // Claim should be quarantined due to challenges assert!( !engine.can_use_claim(&hex::encode(&honest_claim.id)) || engine.get_quarantine_level(&hex::encode(&honest_claim.id)) > 0, "Claim should be affected by challenges" ); // Honest authority resolves in favor of honest claim let authority = [0xA0; 32]; engine.register_authority(ScopedAuthority::new(context, vec![authority], 1)); // Resolve the first conflict (challenges create separate conflicts) let resolution = create_test_event( context, authority, EventKind::Resolution(ResolutionEvent { conflict_id: generate_unique_id(100), // First sybil conflict accepted: vec![honest_claim.id], deprecated: vec![], rationale: vec![EvidenceRef::hash(b"sybil_detected")], authority_sigs: vec![vec![0u8; 64]], }), Some(generate_unique_id(50)), ); engine.ingest(resolution); // After resolution, honest claim should be usable again assert!( engine.can_use_claim(&hex::encode(&honest_claim.id)), "Honest claim should be restored after proper resolution" ); }