Skip to main content

qssm_utils/
entropy_audit.rs

1//! Unified entropy audit: hardware-style density screen + χ² / distinct-byte stats.
2
3#![forbid(unsafe_code)]
4
5use thiserror::Error;
6
7use crate::entropy_density::verify_density;
8use crate::entropy_stats::{validate_entropy_distribution, EntropyStatsError};
9
10/// Failure from the combined [`validate_entropy_full`] gate (density heuristics + distribution test).
11#[non_exhaustive]
12#[derive(Debug, Clone, PartialEq, Error)]
13pub enum EntropyAuditError {
14    #[error("hardware-style density screen failed (too short or pathological pattern)")]
15    DensityHeuristic,
16    #[error(transparent)]
17    Stats(#[from] EntropyStatsError),
18}
19
20/// Density screen (≥ [`crate::MIN_RAW_BYTES`]) then Pearson χ² vs uniform when the sample is long enough.
21///
22/// Short slices fail at the density step; for ≥256 bytes, both density and [`validate_entropy_distribution`] apply.
23pub fn validate_entropy_full(bytes: &[u8]) -> Result<(), EntropyAuditError> {
24    if !verify_density(bytes) {
25        return Err(EntropyAuditError::DensityHeuristic);
26    }
27    validate_entropy_distribution(bytes)?;
28    Ok(())
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn short_fails_density() {
37        assert!(matches!(
38            validate_entropy_full(&[1u8; 64]),
39            Err(EntropyAuditError::DensityHeuristic)
40        ));
41    }
42
43    #[test]
44    fn zeros_fail() {
45        let v = vec![0u8; 300];
46        assert!(validate_entropy_full(&v).is_err());
47    }
48
49    #[test]
50    fn uniform_random_passes_smoke() {
51        let v: Vec<u8> = (0u32..400)
52            .map(|i| (i.wrapping_mul(2_654_435_761) >> 8) as u8)
53            .collect();
54        assert!(validate_entropy_full(&v).is_ok());
55    }
56}