1use super::chacha::chacha20_block;
9use std::cell::RefCell;
10
11const RESEED_BYTES: u64 = 1 << 20;
12
13struct Drbg {
14 key: [u8; 32],
15 counter: u64,
16 generated: u64,
17}
18
19fn os_seed(out: &mut [u8; 32]) {
20 assert!(simoom_sys::arch::os_random(out), "simoom-tls: the operating system random source failed");
21}
22
23impl Drbg {
24 fn seeded() -> Drbg {
25 let mut key = [0u8; 32];
26 os_seed(&mut key);
27 Drbg { key, counter: 0, generated: 0 }
28 }
29
30 fn block(&mut self) -> [u8; 64] {
31 let mut nonce = [0u8; 12];
32 nonce[4..].copy_from_slice(&self.counter.to_le_bytes());
33 self.counter = self.counter.wrapping_add(1);
34 chacha20_block(&self.key, 0, &nonce)
35 }
36
37 fn fill(&mut self, out: &mut [u8]) {
38 if self.generated > RESEED_BYTES {
39 let mut fresh = [0u8; 32];
40 os_seed(&mut fresh);
41 for (k, f) in self.key.iter_mut().zip(fresh) {
42 *k ^= f;
43 }
44 self.generated = 0;
45 }
46 for chunk in out.chunks_mut(64) {
47 let b = self.block();
48 chunk.copy_from_slice(&b[..chunk.len()]);
49 }
50 let b = self.block();
51 self.key.copy_from_slice(&b[..32]);
52 self.generated += out.len() as u64 + 64;
53 }
54}
55
56thread_local! {
57 static RNG: RefCell<Option<Drbg>> = const { RefCell::new(None) };
58}
59
60pub fn fill(out: &mut [u8]) {
62 RNG.with(|r| r.borrow_mut().get_or_insert_with(Drbg::seeded).fill(out));
63}
64
65pub fn array<const N: usize>() -> [u8; N] {
67 let mut a = [0u8; N];
68 fill(&mut a);
69 a
70}
71
72pub fn bytes(n: usize) -> Vec<u8> {
74 let mut v = vec![0u8; n];
75 fill(&mut v);
76 v
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn outputs_differ_and_are_balanced() {
85 assert_ne!(array::<32>(), array::<32>());
86 let big = bytes(1 << 16);
87 let ones: u32 = big.iter().map(|x| x.count_ones()).sum();
88 let ratio = ones as f64 / (big.len() * 8) as f64;
89 assert!((ratio - 0.5).abs() < 0.01, "bit ratio {ratio}");
90 }
91
92 #[test]
93 fn deterministic_given_key_and_erases_it() {
94 let mut d1 = Drbg { key: [7; 32], counter: 0, generated: 0 };
95 let mut d2 = Drbg { key: [7; 32], counter: 0, generated: 0 };
96 let (mut a, mut b) = ([0u8; 100], [0u8; 100]);
97 d1.fill(&mut a);
98 d2.fill(&mut b);
99 assert_eq!(a, b);
100 assert_ne!(d1.key, [7; 32]);
101 d1.fill(&mut a);
102 assert_ne!(a, b);
103 }
104}