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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Timeline Compaction Semantics
//!
//! Provides the mathematical reduction of runtime integrity streams.
//! Compresses historical attestation data into compact bounding proofs
//! while explicitly tracking what sequences were pruned, ensuring that
//! the absence of evidence does not become evidence of absence during an audit.
//!
//! # Audit Loss Prevention (Issue 4 Fix)
//!
//! Before any sequence is pruned, its checkpoint hash is committed into a
//! `history_merkle_root` — a rolling SHA3-256 Merkle accumulation of all
//! checkpoint hashes seen. This provides a verifiable expansion path:
//!
//! - Verifiers can prove any checkpoint was seen without storing its full data.
//! - The `checkpoint_boundary_hashes` list records the hash of each pruned
//! checkpoint boundary, enabling inclusion proofs.
//! - A third-party auditor can reconstruct the root independently given the
//! ordered list of boundary hashes.
use crate::checkpointing::IntegrityCheckpoint;
use alloc::vec::Vec;
use sha3::{Digest, Sha3_256};
/// A mathematically reduced view of a verifier's operational timeline.
/// Maintains cryptographically anchored checkpoints while safely discarding
/// massive volumes of intermediate delta telemetry.
///
/// The `history_merkle_root` provides a single 32-byte commitment to the
/// full operational history, even after compaction.
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CompactedTimeline {
/// The boundary checkpoints that remain cryptographically verifiable.
pub retained_checkpoints: Vec<IntegrityCheckpoint>,
/// An explicit accounting of the sequence bounds that were safely pruned.
/// This prevents malicious "silent drops" of evidence history.
pub pruned_sequences: Vec<u64>,
/// Rolling SHA3-256 Merkle accumulation of all checkpoint rolling hashes,
/// including pruned ones. Provides a verifiable commitment to the full
/// operational history.
pub history_merkle_root: [u8; 32],
/// Ordered list of the `rolling_hash` values of every pruned checkpoint
/// boundary. Combined with `history_merkle_root`, enables inclusion proofs.
pub checkpoint_boundary_hashes: Vec<[u8; 32]>,
}
impl CompactedTimeline {
/// Initializes an empty compacted timeline.
#[must_use]
pub fn new() -> Self {
Self {
retained_checkpoints: Vec::new(),
pruned_sequences: Vec::new(),
history_merkle_root: [0u8; 32],
checkpoint_boundary_hashes: Vec::new(),
}
}
/// Evaluates a timeline array of checkpoints, committing all checkpoint
/// hashes to the Merkle accumulator BEFORE pruning.
///
/// Checkpoints that pass the retention threshold are pruned: their hash
/// is recorded in `checkpoint_boundary_hashes` and their sequence is
/// recorded in `pruned_sequences`. The `history_merkle_root` is updated
/// to include every checkpoint (retained or pruned).
pub fn compact(
&mut self,
mut historical_checkpoints: Vec<IntegrityCheckpoint>,
oldest_retained_sequence: u64,
current_timestamp: u64,
max_age_secs: u64,
) {
// Sort checkpoints by sequence before processing to ensure deterministic
// Merkle accumulation order regardless of insertion order.
historical_checkpoints.sort_by_key(|cp| cp.start_sequence);
let mut pruned_sequences = Vec::new();
let mut boundary_hashes = Vec::new();
let mut retained = Vec::new();
for cp in historical_checkpoints {
// Commit checkpoint hash to Merkle accumulator BEFORE deciding to prune.
// Copy hash bytes first to avoid borrow-checker overlap.
let hash_bytes: Vec<u8> = cp.integrity_root.value.to_vec();
self.accumulate_hash(&hash_bytes);
if cp.is_prunable(oldest_retained_sequence, current_timestamp, max_age_secs) {
// Record the boundary hash and pruned sequence for inclusion proofs.
let mut hash_arr = [0u8; 32];
let len = hash_bytes.len().min(32);
hash_arr[..len].copy_from_slice(&hash_bytes[..len]);
boundary_hashes.push(hash_arr);
pruned_sequences.push(cp.start_sequence);
} else {
retained.push(cp);
}
}
// Merge into existing state
pruned_sequences.sort_unstable();
self.pruned_sequences.extend(pruned_sequences);
self.pruned_sequences.sort_unstable();
self.pruned_sequences.dedup();
self.checkpoint_boundary_hashes.extend(boundary_hashes);
self.retained_checkpoints.extend(retained);
self.retained_checkpoints
.sort_by_key(|cp| cp.start_sequence);
}
/// Asserts that a given sequence number is either actively retained or
/// explicitly known to be pruned. Returns `true` if the sequence is known.
#[must_use]
pub fn verifies_sequence_lineage(&self, sequence: u64) -> bool {
if self.pruned_sequences.binary_search(&sequence).is_ok() {
return true;
}
for cp in &self.retained_checkpoints {
if sequence >= cp.start_sequence && sequence <= cp.end_sequence {
return true;
}
}
false
}
/// Returns the number of known boundary hashes committed to the Merkle accumulator.
#[must_use]
pub fn committed_checkpoint_count(&self) -> usize {
self.checkpoint_boundary_hashes.len()
}
/// Accumulates a checkpoint hash into `history_merkle_root` using a
/// rolling SHA3-256 chain: `root = SHA3-256(root || leaf_hash)`.
fn accumulate_hash(&mut self, hash_value: &[u8]) {
let mut hasher = Sha3_256::new();
hasher.update(self.history_merkle_root);
hasher.update(hash_value);
let result = hasher.finalize();
self.history_merkle_root.copy_from_slice(&result);
}
}
impl Default for CompactedTimeline {
fn default() -> Self {
Self::new()
}
}