Skip to main content

openentropy_core/sources/microarch/
denormal_timing.rs

1//! Floating-point denormal timing — data-dependent microcode timing jitter.
2//!
3//! Denormalized floating-point numbers (between 0 and `f64::MIN_POSITIVE`)
4//! can cause data-dependent timing variation due to microcode assist or
5//! hardware handling differences. This source times blocks of denormal
6//! multiply-accumulate operations and extracts timing jitter.
7//!
8
9//! On Apple Silicon, denormal handling is fast (no microcode penalty),
10//! but residual pipeline state and cache effects still create jitter.
11
12use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
13use crate::sources::helpers::{extract_timing_entropy, mach_time};
14
15/// Number of floating-point operations per timing measurement.
16const OPS_PER_SAMPLE: usize = 100;
17
18static DENORMAL_TIMING_INFO: SourceInfo = SourceInfo {
19    name: "denormal_timing",
20    description: "Floating-point denormal multiply-accumulate timing jitter",
21    physics: "Times blocks of floating-point operations on denormalized values \
22              (magnitudes between 0 and f64::MIN_POSITIVE). Denormals may trigger \
23              microcode assists on some architectures, creating data-dependent timing. \
24              Even on Apple Silicon where denormal handling is fast in hardware, \
25              residual timing jitter comes from FPU pipeline state, cache line \
26              alignment, and memory controller arbitration.",
27    category: SourceCategory::Microarch,
28    platform: Platform::Any,
29    requirements: &[],
30    entropy_rate_estimate: 0.5,
31    composite: false,
32    is_fast: false,
33};
34
35/// Entropy source that harvests timing jitter from denormalized float operations.
36pub struct DenormalTimingSource;
37
38impl EntropySource for DenormalTimingSource {
39    fn info(&self) -> &SourceInfo {
40        &DENORMAL_TIMING_INFO
41    }
42
43    fn is_available(&self) -> bool {
44        true
45    }
46
47    fn collect(&self, n_samples: usize) -> Vec<u8> {
48        let raw_count = n_samples * 4 + 64;
49        let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
50
51        // Pre-generate denormal values with varying mantissa patterns.
52        let mut lcg: u64 = mach_time() | 1;
53        let mut denormals = [0.0f64; OPS_PER_SAMPLE];
54        for d in denormals.iter_mut() {
55            lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
56            // Construct denormal: exponent bits = 0, random mantissa
57            let bits = lcg & 0x000F_FFFF_FFFF_FFFF_u64;
58            *d = f64::from_bits(bits);
59        }
60
61        for _ in 0..raw_count {
62            // Rotate denormal array slightly for per-iteration variation.
63            lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
64            let start_idx = (lcg >> 32) as usize % OPS_PER_SAMPLE;
65
66            let mut acc = denormals[start_idx];
67
68            let t0 = mach_time();
69            for i in 0..OPS_PER_SAMPLE {
70                let idx = (start_idx + i) % OPS_PER_SAMPLE;
71                acc *= denormals[idx];
72                acc += denormals[(idx + 1) % OPS_PER_SAMPLE];
73            }
74            let t1 = mach_time();
75
76            // Prevent dead code elimination.
77            std::hint::black_box(acc);
78            timings.push(t1.wrapping_sub(t0));
79        }
80
81        extract_timing_entropy(&timings, n_samples)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn info() {
91        let src = DenormalTimingSource;
92        assert_eq!(src.name(), "denormal_timing");
93        assert_eq!(src.info().category, SourceCategory::Microarch);
94        assert!(!src.info().composite);
95    }
96
97    #[test]
98    #[ignore] // Timing-dependent
99    fn collects_bytes() {
100        let src = DenormalTimingSource;
101        assert!(src.is_available());
102        let data = src.collect(64);
103        assert!(!data.is_empty());
104        assert!(data.len() <= 64);
105    }
106}