Skip to main content

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}