Skip to main content

openentropy_core/sources/signal/
compression_timing.rs

1//! Compression timing entropy source.
2//!
3//! Exploits data-dependent branch prediction behaviour and micro-architectural
4//! side-effects to extract timing entropy from zlib compression operations.
5//!
6//! **Raw output characteristics:** XOR-folded timing deltas between successive
7//! operations. The timing jitter is driven by branch predictor state,
8//! cache contention, and pipeline hazards.
9
10use std::io::Write;
11use std::time::Instant;
12
13use flate2::Compression;
14use flate2::write::ZlibEncoder;
15
16use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
17
18use crate::sources::helpers::{extract_timing_entropy, mach_time};
19
20// ---------------------------------------------------------------------------
21// CompressionTimingSource
22// ---------------------------------------------------------------------------
23
24static COMPRESSION_TIMING_INFO: SourceInfo = SourceInfo {
25    name: "compression_timing",
26    description: "Zlib compression timing jitter from data-dependent branch prediction",
27    physics: "Compresses varying data with zlib and measures per-operation timing. \
28              Compression algorithms have heavily data-dependent branches (Huffman tree \
29              traversal, LZ77 match finding). The CPU\u{2019}s branch predictor state from \
30              ALL running code affects prediction accuracy for these branches. Pipeline \
31              stalls from mispredictions create timing variation.",
32    category: SourceCategory::Signal,
33    platform: Platform::Any,
34    requirements: &[],
35    entropy_rate_estimate: 2.0,
36    composite: false,
37    is_fast: true,
38};
39
40/// Entropy source that harvests timing jitter from zlib compression.
41pub struct CompressionTimingSource;
42
43impl EntropySource for CompressionTimingSource {
44    fn info(&self) -> &SourceInfo {
45        &COMPRESSION_TIMING_INFO
46    }
47
48    fn is_available(&self) -> bool {
49        true
50    }
51
52    fn collect(&self, n_samples: usize) -> Vec<u8> {
53        // 4x oversampling for better XOR-fold quality.
54        let raw_count = n_samples * 4 + 64;
55        let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
56
57        // Seed from high-resolution timer for per-call variation.
58        let mut lcg: u64 = mach_time() | 1;
59
60        for i in 0..raw_count {
61            // Vary data size (128-512 bytes) to create more timing diversity.
62            let data_len = 128 + (lcg as usize % 385);
63            let mut data = vec![0u8; data_len];
64
65            // First third: pseudo-random
66            let third = data_len / 3;
67            for byte in data[..third].iter_mut() {
68                lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
69                *byte = (lcg >> 32) as u8;
70            }
71
72            // Middle third: repeating pattern (highly compressible)
73            for (j, byte) in data[third..third * 2].iter_mut().enumerate() {
74                *byte = (j % 4) as u8;
75            }
76
77            // Last third: more pseudo-random
78            for byte in data[third * 2..].iter_mut() {
79                lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(i as u64);
80                *byte = (lcg >> 32) as u8;
81            }
82
83            let t0 = Instant::now();
84            let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
85            let _ = encoder.write_all(&data);
86            let _ = encoder.finish();
87            let elapsed_ns = t0.elapsed().as_nanos() as u64;
88            timings.push(elapsed_ns);
89        }
90
91        extract_timing_entropy(&timings, n_samples)
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn compression_timing_info() {
101        let src = CompressionTimingSource;
102        assert_eq!(src.name(), "compression_timing");
103        assert_eq!(src.info().category, SourceCategory::Signal);
104        assert!(!src.info().composite);
105    }
106
107    #[test]
108    #[ignore] // Run with: cargo test -- --ignored
109    fn compression_timing_collects_bytes() {
110        let src = CompressionTimingSource;
111        assert!(src.is_available());
112        let data = src.collect(64);
113        assert!(!data.is_empty());
114    }
115}