umsh_ulcp_runtime/
reseed.rs1use core::cell::Cell;
16use core::sync::atomic::{AtomicBool, Ordering};
17
18use embassy_sync::blocking_mutex::Mutex;
19use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
20use rand_chacha::ChaCha20Rng;
21use rand_core::{CryptoRng, RngCore, SeedableRng};
22use umsh_crypto::hkdf_sha256;
23use umsh_crypto::software::SoftwareSha256;
24use zeroize::Zeroize;
25
26const INFO_RESEED: &[u8] = b"umsh-reseeding-rng-v1";
27
28pub struct ReseedSlot {
30 pending: AtomicBool,
33 seed: Mutex<CriticalSectionRawMutex, Cell<[u8; 32]>>,
34}
35
36impl ReseedSlot {
37 pub const fn new() -> Self {
38 Self {
39 pending: AtomicBool::new(false),
40 seed: Mutex::new(Cell::new([0; 32])),
41 }
42 }
43
44 pub fn offer(&self, seed: [u8; 32]) {
50 self.seed.lock(|slot| {
51 slot.set(seed);
52 self.pending.store(true, Ordering::Release);
53 });
54 }
55
56 fn take(&self) -> Option<[u8; 32]> {
57 if !self.pending.load(Ordering::Acquire) {
58 return None;
59 }
60 self.seed.lock(|slot| {
61 self.pending
62 .swap(false, Ordering::AcqRel)
63 .then(|| slot.replace([0; 32]))
64 })
65 }
66}
67
68impl Default for ReseedSlot {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74pub struct ReseedingRng {
76 rng: ChaCha20Rng,
77 slot: &'static ReseedSlot,
78}
79
80impl ReseedingRng {
81 pub fn new(seed: [u8; 32], slot: &'static ReseedSlot) -> Self {
82 Self {
83 rng: ChaCha20Rng::from_seed(seed),
84 slot,
85 }
86 }
87
88 fn refresh(&mut self) {
95 let Some(mut fresh) = self.slot.take() else {
96 return;
97 };
98 let mut current = [0u8; 32];
99 self.rng.fill_bytes(&mut current);
100 let mut next = [0u8; 32];
101 hkdf_sha256(&SoftwareSha256, &fresh, ¤t, INFO_RESEED, &mut next);
102 self.rng = ChaCha20Rng::from_seed(next);
103 fresh.zeroize();
104 current.zeroize();
105 next.zeroize();
106 }
107}
108
109impl RngCore for ReseedingRng {
110 fn next_u32(&mut self) -> u32 {
111 self.refresh();
112 self.rng.next_u32()
113 }
114
115 fn next_u64(&mut self) -> u64 {
116 self.refresh();
117 self.rng.next_u64()
118 }
119
120 fn fill_bytes(&mut self, dest: &mut [u8]) {
121 self.refresh();
122 self.rng.fill_bytes(dest);
123 }
124
125 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
126 self.fill_bytes(dest);
127 Ok(())
128 }
129}
130
131impl CryptoRng for ReseedingRng {}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 fn slot() -> &'static ReseedSlot {
139 Box::leak(Box::new(ReseedSlot::new()))
140 }
141
142 fn draw(rng: &mut impl RngCore) -> [u8; 48] {
143 let mut out = [0u8; 48];
144 rng.fill_bytes(&mut out);
145 out
146 }
147
148 #[test]
149 fn an_unreseeded_stream_is_plain_chacha20() {
150 let mut reseeding = ReseedingRng::new([7; 32], slot());
151 let mut plain = ChaCha20Rng::from_seed([7; 32]);
152 assert_eq!(draw(&mut reseeding), draw(&mut plain));
153 assert_eq!(reseeding.next_u32(), plain.next_u32());
154 assert_eq!(reseeding.next_u64(), plain.next_u64());
155 assert_eq!(draw(&mut reseeding), draw(&mut plain));
156 }
157
158 #[test]
159 fn a_reseed_takes_effect_at_the_next_use_and_only_once() {
160 let slot = slot();
161 let mut reseeding = ReseedingRng::new([7; 32], slot);
162 let mut plain = ChaCha20Rng::from_seed([7; 32]);
163 assert_eq!(draw(&mut reseeding), draw(&mut plain));
164
165 slot.offer([9; 32]);
166 let after = draw(&mut reseeding);
167 assert_ne!(after, draw(&mut plain));
168 let mut twin = ReseedingRng::new([7; 32], self::slot());
171 draw(&mut twin);
172 twin.slot.offer([9; 32]);
173 assert_eq!(draw(&mut twin), after);
174 assert_eq!(draw(&mut twin), draw(&mut reseeding));
175 }
176
177 #[test]
178 fn the_new_key_depends_on_the_old_state_and_the_fresh_seed() {
179 let reseeded = |boot: u8, fresh: u8| {
180 let slot = slot();
181 let mut rng = ReseedingRng::new([boot; 32], slot);
182 slot.offer([fresh; 32]);
183 draw(&mut rng)
184 };
185 assert_ne!(reseeded(1, 9), reseeded(2, 9));
188 assert_ne!(reseeded(1, 9), reseeded(1, 8));
191 assert_eq!(reseeded(1, 9), reseeded(1, 9));
192 }
193
194 #[test]
195 fn where_the_generator_has_got_to_matters() {
196 let slot_a = slot();
197 let mut a = ReseedingRng::new([3; 32], slot_a);
198 let slot_b = slot();
199 let mut b = ReseedingRng::new([3; 32], slot_b);
200 draw(&mut b);
201 slot_a.offer([5; 32]);
202 slot_b.offer([5; 32]);
203 assert_ne!(draw(&mut a), draw(&mut b));
204 }
205
206 #[test]
207 fn a_later_offer_replaces_one_not_yet_taken() {
208 let slot_a = slot();
209 let mut a = ReseedingRng::new([4; 32], slot_a);
210 slot_a.offer([1; 32]);
211 slot_a.offer([2; 32]);
212
213 let slot_b = slot();
214 let mut b = ReseedingRng::new([4; 32], slot_b);
215 slot_b.offer([2; 32]);
216 assert_eq!(draw(&mut a), draw(&mut b));
217 }
218}