openentropy_core/sources/microarch/
speculative_execution.rs1use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
4use crate::sources::helpers::{extract_timing_entropy, mach_time};
5
6pub struct SpeculativeExecutionSource;
13
14static SPECULATIVE_EXECUTION_INFO: SourceInfo = SourceInfo {
15 name: "speculative_execution",
16 description: "Branch predictor state timing via data-dependent branches",
17 physics: "Measures timing variations from the CPU's speculative execution engine. \
18 The branch predictor maintains per-address history that depends on ALL \
19 previously executed code. Mispredictions cause pipeline flushes (~15 cycle \
20 penalty on M4). By running data-dependent branches and measuring timing, \
21 we capture the predictor's internal state.",
22 category: SourceCategory::Microarch,
23 platform: Platform::Any,
24 requirements: &[],
25 entropy_rate_estimate: 2.5,
26 composite: false,
27 is_fast: true,
28};
29
30impl EntropySource for SpeculativeExecutionSource {
31 fn info(&self) -> &SourceInfo {
32 &SPECULATIVE_EXECUTION_INFO
33 }
34
35 fn is_available(&self) -> bool {
36 true
37 }
38
39 fn collect(&self, n_samples: usize) -> Vec<u8> {
40 let num_batches = n_samples + 64;
41 let mut timings = Vec::with_capacity(num_batches);
42
43 let mut lcg_state: u64 = mach_time() ^ 0xDEAD_BEEF_CAFE_BABE;
46
47 for _batch_idx in 0..num_batches {
48 lcg_state = lcg_state
51 .wrapping_mul(6364136223846793005)
52 .wrapping_add(1442695040888963407);
53 let batch_size = 10 + ((lcg_state >> 48) as usize % 31);
54
55 let t0 = mach_time();
56
57 let mut accumulator: u64 = 0;
60 for _ in 0..batch_size {
61 lcg_state = lcg_state
63 .wrapping_mul(6364136223846793005)
64 .wrapping_add(1442695040888963407);
65
66 if lcg_state & 0x8000_0000 != 0 {
68 accumulator = accumulator.wrapping_add(lcg_state);
69 } else {
70 accumulator = accumulator.wrapping_mul(lcg_state | 1);
71 }
72
73 lcg_state = lcg_state
75 .wrapping_mul(6364136223846793005)
76 .wrapping_add(1442695040888963407);
77 if lcg_state & 0x8000_0000 != 0 {
78 accumulator ^= lcg_state.rotate_left(7);
79 } else {
80 accumulator ^= lcg_state.rotate_right(11);
81 }
82
83 lcg_state = lcg_state
85 .wrapping_mul(6364136223846793005)
86 .wrapping_add(1442695040888963407);
87 if lcg_state & 0x1 != 0 {
88 accumulator = accumulator.wrapping_add(lcg_state >> 32);
89 }
90 }
91
92 std::hint::black_box(accumulator);
94
95 let t1 = mach_time();
96 timings.push(t1.wrapping_sub(t0));
97 }
98
99 extract_timing_entropy(&timings, n_samples)
100 }
101}
102
103#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 #[ignore] fn speculative_execution_collects_bytes() {
114 let src = SpeculativeExecutionSource;
115 assert!(src.is_available());
116 let data = src.collect(128);
117 assert!(!data.is_empty());
118 assert!(data.len() <= 128);
119 if data.len() > 1 {
120 let first = data[0];
121 assert!(data.iter().any(|&b| b != first), "all bytes were identical");
122 }
123 }
124
125 #[test]
126 fn source_info_category() {
127 assert_eq!(
128 SpeculativeExecutionSource.info().category,
129 SourceCategory::Microarch
130 );
131 }
132
133 #[test]
134 fn source_info_name() {
135 assert_eq!(SpeculativeExecutionSource.name(), "speculative_execution");
136 }
137}