pqrascv-hardware 1.0.0-rc.5

Hardware-rooted trust and distributed verifier consensus for PQ-RASCV
Documentation
//! 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()
    }
}