strat9_kernel/entropy.rs
1//! Kernel entropy pool : a cryptographically sound random bytes from interrupt noise.
2//!
3//! Collects entropy from:
4//! - keyboard IRQ timing + scancode data
5//! - timer tick jitter (low bits of TSC)
6//! - storage (AHCI) IRQ timing
7//! - RDRAND when available (as seed material)
8//!
9//! The pool uses a tweaked Threefish-like mixing over 128 bytes (4×4 u64s)
10//! for efficient diffusion. Output is extracted by hashing the pool state
11//! through a **compression round** : no built-in hash dependency needed.
12
13use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
14
15// === Pool constants ===================================
16/// Number of 64-bit words in the entropy pool (16 = 128 bytes).
17const POOL_WORDS: usize = 16;
18/// Entropy threshold in bytes before we consider the pool "initialised".
19const ENTROPY_HIGH_WATER: u32 = 64;
20
21// === Global pool state =================================
22/// The entropy pool : a 128‑byte array of 16 u64s.
23static POOL: [AtomicU64; POOL_WORDS] = [const { AtomicU64::new(0) }; POOL_WORDS];
24/// Estimated entropy count (in bytes). Saturates at POOL_WORDS * 8.
25static ENTROPY_CTR: AtomicU32 = AtomicU32::new(0);
26/// Index where the next sample will be mixed in.
27static POOL_IDX: AtomicU32 = AtomicU32::new(0);
28
29// === Initial seed from RDRAND ================================================
30/// One-time boot‑time seed of the pool from RDRAND (if available).
31pub fn seed_from_rdrand() {
32 // RDRAND can hang on some QEMU configurations (especially AMD CPU models).
33 // Fall back to TSC as a weak initial seed (better than all-zero pool).
34 let seed = crate::arch::rdtsc().wrapping_mul(6364136223846793005);
35 for w in &POOL {
36 let old = w.load(Ordering::Relaxed);
37 w.store(old ^ seed, Ordering::Relaxed);
38 }
39 ENTROPY_CTR.store(32, Ordering::Relaxed);
40}
41
42// === public API ================================================
43
44/// Check whether the entropy pool has accumulated enough entropy
45/// (at least `ENTROPY_HIGH_WATER` bytes). Used by GRND_NONBLOCK.
46pub fn is_ready() -> bool {
47 ENTROPY_CTR.load(Ordering::Relaxed) >= ENTROPY_HIGH_WATER
48}
49
50/// Feed a 64‑bit sample into the entropy pool (called from interrupt handlers).
51///
52/// `tag` should be a small unique discriminator for the source
53/// (e.g. 1 = keyboard, 2 = timer, 3 = storage).
54#[inline]
55pub fn add_entropy(tag: u8, sample: u64) {
56 let idx = POOL_IDX.fetch_add(1, Ordering::Relaxed) as usize % POOL_WORDS;
57 let tag64 = (tag as u64) << 56;
58
59 // Mix: fold in the sample with a non‑linear twist.
60 // The tagged value is permuted through a small S‑box (multiplicative inverse)
61 // to destroy algebraic structure, then XORed into the pool word.
62 let mixed = twist(tag64 | (sample & 0x00FF_FFFF_FFFF_FFFF));
63 let old = POOL[idx].load(Ordering::Relaxed);
64 POOL[idx].store(old.wrapping_add(mixed), Ordering::Relaxed);
65
66 // Gentle avalanche: stir two more pool slots to avoid "stuck" zero
67 // if add_entropy is never called for some indices.
68 let idx2 = (idx + 3) % POOL_WORDS;
69 let old2 = POOL[idx2].load(Ordering::Relaxed);
70 POOL[idx2].store(old2 ^ mixed.rotate_right(17), Ordering::Relaxed);
71
72 // Bump entropy estimate (saturing at the pool capacity).
73 let old_e = ENTROPY_CTR.load(Ordering::Relaxed);
74 if old_e < (POOL_WORDS * 8) as u32 {
75 ENTROPY_CTR.fetch_add(2, Ordering::Relaxed); // 2 bytes per sample
76 }
77}
78
79/// Fill a byte buffer with random bytes from the entropy pool.
80///
81/// If the pool has not accumulated `ENTROPY_HIGH_WATER` bytes of entropy yet,
82/// this function spins briefly, waiting for interrupt noise. It will not
83/// block indefinitely: after ~1000 spin iterations it falls through and
84/// delivers whatever randomness is available.
85pub fn fill_random(buf: &mut [u8]) {
86 crate::e9_println!("FR start");
87 // Wait until we have enough entropy (very short on any live system).
88 let mut spins = 0u32;
89 crate::e9_println!("FR loop start");
90 while ENTROPY_CTR.load(Ordering::Relaxed) < ENTROPY_HIGH_WATER {
91 core::hint::spin_loop();
92 spins += 1;
93 if spins > 1024 {
94 break; // don't hang if entropy source is absent
95 }
96 }
97 crate::e9_println!("FR loop done");
98
99 let mut offset = 0usize;
100 crate::e9_println!("FR extract start");
101 while offset < buf.len() {
102 crate::e9_println!("FR loop");
103 let block = extract_block();
104 crate::e9_println!("FR got block");
105 let n = (buf.len() - offset).min(8);
106 buf[offset..offset + n].copy_from_slice(&block[..n]);
107 offset += n;
108 }
109}
110
111// === Internal helpers ===============================================
112
113/// Non‑linear twist: multiplicative inverse in GF(2⁶⁴) masked by a prime,
114/// then XORed with a rotation of itself.
115#[inline(always)]
116fn twist(x: u64) -> u64 {
117 // "Multiply‑then‑rotate" – cheap, non‑linear, 1‑to‑1.
118 let a = x.wrapping_mul(0x6A09E667F3BCC909);
119 a ^ a.rotate_right(37)
120}
121
122/// Extract 8 bytes from the pool by folding all words together.
123///
124/// This is **not** a cryptographic hash : it's a feed‑forward compression
125/// that provides avalanche. If the pool has been seeded from RDRAND and
126/// stirred by interrupt noise, the output is cryptographically acceptable.
127#[inline(always)]
128fn extract_block() -> [u8; 8] {
129 crate::e9_println!("EB entry");
130 let mut h: u64 = 0x9E3779B97F4A7C15; // nothing‑up‑my‑sleeve constant
131 crate::e9_println!("EB loop");
132 for (i, w) in POOL.iter().enumerate() {
133 crate::e9_println!("EB i");
134 let v = w.load(Ordering::Relaxed);
135 crate::e9_println!("EB v");
136 h = h.wrapping_add(v).wrapping_mul(0xBF58476D1CE4E5B9);
137 crate::e9_println!("EB add");
138 h ^= h.rotate_right((i as u32) % 63 + 1);
139 crate::e9_println!("EB rot");
140 }
141
142 crate::e9_println!("EB post-loop");
143 // One final avalanche
144 h ^= h >> 33;
145 crate::e9_println!("EB av1");
146 h = h.wrapping_mul(0xFF51AFD7ED558CCD);
147 crate::e9_println!("EB av2");
148 h ^= h >> 33;
149 crate::e9_println!("EB av3");
150 h = h.wrapping_mul(0xC4CEB9FE1A85EC53);
151 crate::e9_println!("EB av4");
152 h ^= h >> 33;
153 crate::e9_println!("EB av5");
154
155 crate::e9_println!("EB return");
156 h.to_le_bytes()
157}
158
159/// BUG TODO : RDRAND helper (used during boot seeding).
160// hang during boot on some QEMU configurations if RDRAND is unavailable
161fn rdrand64() -> Option<u64> {
162 #[cfg(target_arch = "x86_64")]
163 {
164 let mut val: u64 = 0;
165 let mut ok: u8;
166 for _ in 0..10 {
167 unsafe {
168 core::arch::asm!(
169 "rdrand {0}",
170 "setc {1}",
171 out(reg) val,
172 out(reg_byte) ok,
173 options(nostack, preserves_flags),
174 );
175 }
176 if ok != 0 {
177 return Some(val);
178 }
179 }
180 }
181 None
182}