1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//! Verifier-side Bounded Evidence Cache
//!
//! Provides an append-only, strictly bounded cache for deduplication and
//! sequence alignment of incoming live evidence. Crucial for detecting
//! replay attacks, attestation flooding, and rollback attempts without
//! risking verifier memory exhaustion.
#![cfg(feature = "live-evidence")]
use crate::digest::TypedDigest;
use alloc::collections::VecDeque;
use alloc::string::String;
pub const MAX_CACHE_ENTRIES: usize = 1000;
/// Represents a cached entry of an observed node's evidence state.
#[derive(Debug, Clone)]
pub struct CacheEntry {
pub node_id: String,
pub timestamp: u64,
pub evidence_hash: TypedDigest,
}
/// A deterministic, bounded evidence cache.
#[derive(Debug, Default)]
pub struct EvidenceCache {
entries: VecDeque<CacheEntry>,
}
impl EvidenceCache {
pub fn new() -> Self {
Self {
entries: VecDeque::with_capacity(MAX_CACHE_ENTRIES),
}
}
/// Appends a new entry to the cache. If the cache is full, deterministically
/// evicts the oldest entry to prevent memory exhaustion.
pub fn push(&mut self, entry: CacheEntry) {
if self.entries.len() >= MAX_CACHE_ENTRIES {
self.entries.pop_front();
}
self.entries.push_back(entry);
}
/// Checks if the exact evidence hash has already been seen (exact replay).
#[must_use]
pub fn contains_hash(&self, hash: &TypedDigest) -> bool {
self.entries
.iter()
.any(|e| e.evidence_hash.value == hash.value)
}
/// Retrieves the most recent timestamp seen for a specific node identity.
/// Used for strictly enforcing monotonic timeline progression.
#[must_use]
pub fn last_seen_timestamp(&self, node_id: &str) -> Option<u64> {
self.entries
.iter()
.rev()
.find(|e| e.node_id == node_id)
.map(|e| e.timestamp)
}
/// Evicts all entries older than the provided timestamp cutoff.
pub fn evict_expired(&mut self, cutoff_timestamp: u64) {
self.entries.retain(|e| e.timestamp > cutoff_timestamp);
}
}