openentropy_core/sources/signal/
hash_timing.rs1use 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
22static 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
42pub 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 let raw_count = n_samples * 4 + 64;
58 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
59
60 let mut lcg: u64 = mach_time() | 1;
62
63 for i in 0..raw_count {
64 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 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 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] 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}