Skip to main content

openentropy_core/sources/signal/
hash_timing.rs

1//! Hash timing entropy source.
2//!
3//! Exploits micro-architectural side-effects to extract timing entropy from
4//! SHA-256 hashing 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//!
10//! Note: HashTimingSource uses SHA-256 as its *workload* (the thing being
11//! timed) — this is NOT conditioning. The entropy comes from the timing
12//! variation, not from the hash output.
13
14use std::time::Instant;
15
16use sha2::{Digest, Sha256};
17
18use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
19
20use crate::sources::helpers::{extract_timing_entropy, mach_time};
21
22// ---------------------------------------------------------------------------
23// HashTimingSource
24// ---------------------------------------------------------------------------
25
26static HASH_TIMING_INFO: SourceInfo = SourceInfo {
27    name: "hash_timing",
28    description: "SHA-256 hashing timing jitter from micro-architectural side effects",
29    physics: "SHA-256 hashes data of varying sizes and measures timing. While SHA-256 is \
30              algorithmically constant-time, the actual execution time varies due to: \
31              memory access patterns for the message schedule, cache line alignment, TLB \
32              state, and CPU frequency scaling. The timing also captures micro-architectural \
33              side effects from other processes.",
34    category: SourceCategory::Signal,
35    platform: Platform::Any,
36    requirements: &[],
37    entropy_rate_estimate: 2.5,
38    composite: false,
39    is_fast: true,
40};
41
42/// Entropy source that harvests timing jitter from SHA-256 hashing.
43/// Note: SHA-256 is used as the *workload* being timed, not for conditioning.
44pub struct HashTimingSource;
45
46impl EntropySource for HashTimingSource {
47    fn info(&self) -> &SourceInfo {
48        &HASH_TIMING_INFO
49    }
50
51    fn is_available(&self) -> bool {
52        true
53    }
54
55    fn collect(&self, n_samples: usize) -> Vec<u8> {
56        // 4x oversampling for better XOR-fold quality after delta computation.
57        let raw_count = n_samples * 4 + 64;
58        let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
59
60        // Seed from high-resolution timer for per-call variation.
61        let mut lcg: u64 = mach_time() | 1;
62
63        for i in 0..raw_count {
64            // Wider range of sizes (32-2048 bytes) to create more timing diversity.
65            let size = 32 + (lcg as usize % 2017);
66            let mut data = Vec::with_capacity(size);
67            for _ in 0..size {
68                lcg = lcg.wrapping_mul(6364136223846793005).wrapping_add(1);
69                data.push((lcg >> 32) as u8);
70            }
71
72            // SHA-256 is the WORKLOAD being timed — not conditioning.
73            // Hash multiple rounds for smaller inputs to amplify timing variation.
74            let rounds = if size < 256 { 3 } else { 1 };
75            let t0 = Instant::now();
76            for _ in 0..rounds {
77                let mut hasher = Sha256::new();
78                hasher.update(&data);
79                let digest = hasher.finalize();
80                std::hint::black_box(&digest);
81                // Feed digest back as additional data to prevent loop elision
82                if let Some(b) = data.last_mut() {
83                    *b ^= digest[i % 32];
84                }
85            }
86            let elapsed_ns = t0.elapsed().as_nanos() as u64;
87            timings.push(elapsed_ns);
88        }
89
90        extract_timing_entropy(&timings, n_samples)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn hash_timing_info() {
100        let src = HashTimingSource;
101        assert_eq!(src.name(), "hash_timing");
102        assert_eq!(src.info().category, SourceCategory::Signal);
103        assert!(!src.info().composite);
104    }
105
106    #[test]
107    #[ignore] // Run with: cargo test -- --ignored
108    fn hash_timing_collects_bytes() {
109        let src = HashTimingSource;
110        assert!(src.is_available());
111        let data = src.collect(64);
112        assert!(!data.is_empty());
113    }
114}