1use commonware_macros::stability;
4#[stability(ALPHA)]
5use core::{convert::Infallible, mem::size_of};
6#[stability(BETA)]
7use rand::{rand_core::UnwrapErr, rngs::SysRng, CryptoRng};
8#[stability(ALPHA)]
9use rand::{rngs::StdRng, SeedableRng, TryCryptoRng, TryRng};
10
11#[stability(BETA)]
20pub fn sys_rng() -> impl CryptoRng {
21 UnwrapErr(SysRng)
22}
23
24#[stability(ALPHA)]
31#[derive(Debug)]
32pub struct TestRng(StdRng);
33
34#[stability(ALPHA)]
35impl TryRng for TestRng {
36 type Error = Infallible;
37
38 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
39 self.0.try_next_u32()
40 }
41
42 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
43 self.0.try_next_u64()
44 }
45
46 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
47 self.0.try_fill_bytes(dest)
48 }
49}
50
51#[stability(ALPHA)]
52impl TryCryptoRng for TestRng {}
53
54#[stability(ALPHA)]
55impl TestRng {
56 pub fn new(seed: u64) -> Self {
61 Self(StdRng::seed_from_u64(seed))
62 }
63}
64
65#[stability(ALPHA)]
69pub fn test_rng() -> TestRng {
70 TestRng::new(0)
71}
72
73#[inline]
78#[stability(ALPHA)]
79pub const fn mix64(mut word: u64) -> u64 {
80 word ^= word >> 30;
81 word = word.wrapping_mul(0xbf58_476d_1ce4_e5b9);
82 word ^= word >> 27;
83 word = word.wrapping_mul(0x94d0_49bb_1331_11eb);
84 word ^ (word >> 31)
85}
86
87#[stability(ALPHA)]
89const BLOCK_BYTES: usize = size_of::<u64>();
90
91#[stability(ALPHA)]
143pub struct FuzzRng {
144 bytes: Vec<u8>,
145 ctr: u64,
146 cache: [u8; BLOCK_BYTES],
147 cache_pos: usize,
148}
149
150#[stability(ALPHA)]
151impl FuzzRng {
152 pub const fn new(bytes: Vec<u8>) -> Self {
154 Self {
155 bytes,
156 ctr: 0,
157 cache: [0u8; BLOCK_BYTES],
158 cache_pos: BLOCK_BYTES,
159 }
160 }
161
162 #[inline]
172 fn next_block_u64(&mut self) -> u64 {
173 let mut bytes = [0u8; BLOCK_BYTES];
176 if !self.bytes.is_empty() {
177 let len = self.bytes.len() as u64;
178 for (i, byte) in bytes.iter_mut().enumerate() {
179 *byte = self.bytes[(self.ctr.wrapping_add(i as u64) % len) as usize];
180 }
181 }
182 let word = u64::from_be_bytes(bytes);
183
184 let ctr = self.ctr;
187 self.ctr = self.ctr.wrapping_add(1);
188 mix64(word ^ ctr ^ crate::GOLDEN_RATIO)
189 }
190
191 fn fill_bytes_stream(&mut self, dest: &mut [u8]) {
192 let mut written = 0;
193 while written < dest.len() {
194 if self.cache_pos == self.cache.len() {
195 self.cache = self.next_block_u64().to_be_bytes();
200 self.cache_pos = 0;
201 }
202
203 let available = self.cache.len() - self.cache_pos;
204 let need = dest.len() - written;
205 let take = available.min(need);
206 dest[written..written + take]
207 .copy_from_slice(&self.cache[self.cache_pos..self.cache_pos + take]);
208 self.cache_pos += take;
209 written += take;
210 }
211 }
212}
213
214#[stability(ALPHA)]
215impl TryRng for FuzzRng {
216 type Error = Infallible;
217
218 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
219 let mut buf = [0u8; 4];
220 self.fill_bytes_stream(&mut buf);
221 Ok(u32::from_be_bytes(buf))
222 }
223
224 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
225 let mut buf = [0u8; BLOCK_BYTES];
226 self.fill_bytes_stream(&mut buf);
227 Ok(u64::from_be_bytes(buf))
228 }
229
230 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
231 self.fill_bytes_stream(dest);
232 Ok(())
233 }
234}
235
236#[stability(ALPHA)]
240impl TryCryptoRng for FuzzRng {}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use rand::Rng;
246
247 #[test]
248 fn test_empty_bytes_not_constant() {
249 let mut rng = FuzzRng::new(vec![]);
250
251 let values: Vec<_> = (0..BLOCK_BYTES).map(|_| rng.next_u64()).collect();
252 assert!(values.windows(2).any(|w| w[0] != w[1]));
253 }
254
255 #[test]
256 fn test_empty_bytes_deterministic() {
257 let mut rng1 = FuzzRng::new(vec![]);
258 let mut rng2 = FuzzRng::new(vec![]);
259
260 for _ in 0..256 {
261 assert_eq!(rng1.next_u64(), rng2.next_u64());
262 }
263 }
264
265 #[test]
266 fn test_all_zero_bytes_not_constant() {
267 let bytes = vec![0; BLOCK_BYTES];
268 let mut rng = FuzzRng::new(bytes);
269 let values: Vec<_> = (0..BLOCK_BYTES).map(|_| rng.next_u64()).collect();
270 assert!(values.windows(2).any(|w| w[0] != w[1]));
271 }
272
273 #[test]
274 fn test_deterministic_with_same_input() {
275 let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
276
277 let mut rng1 = FuzzRng::new(bytes.clone());
278 let mut rng2 = FuzzRng::new(bytes);
279
280 for _ in 0..1000 {
281 assert_eq!(rng1.next_u64(), rng2.next_u64());
282 }
283 }
284
285 #[test]
286 fn test_short_input_wraparound() {
287 for len in 1..=3 {
288 let bytes = vec![0xAB; len];
289 let mut rng1 = FuzzRng::new(bytes.clone());
290 let mut rng2 = FuzzRng::new(bytes);
291 let out1: Vec<_> = (0..32).map(|_| rng1.next_u64()).collect();
292 let out2: Vec<_> = (0..32).map(|_| rng2.next_u64()).collect();
293 assert_eq!(out1, out2);
294 assert!(out1.windows(2).any(|w| w[0] != w[1]));
295 }
296 }
297
298 #[test]
299 fn test_small_mutation_locality() {
300 let mut base = vec![0u8; 64];
301 for (i, byte) in base.iter_mut().enumerate() {
302 *byte = i as u8;
303 }
304 let mut mutated = base.clone();
305 let mutated_pos = 20usize;
306 mutated[mutated_pos] ^= 0x01;
307
308 let mut rng_a = FuzzRng::new(base);
309 let mut rng_b = FuzzRng::new(mutated);
310
311 let draws = 40usize;
312 let mut diff_indices = Vec::new();
313 for i in 0..draws {
314 if rng_a.next_u64() != rng_b.next_u64() {
315 diff_indices.push(i);
316 }
317 }
318
319 let expected: Vec<usize> = ((mutated_pos - 7)..=mutated_pos).collect();
320 assert_eq!(diff_indices, expected);
321 }
322
323 #[test]
324 fn test_small_mutation_locality_wraparound() {
325 let mut base = vec![0u8; 64];
326 for (i, byte) in base.iter_mut().enumerate() {
327 *byte = i as u8;
328 }
329 let mut mutated = base.clone();
330 let mutated_pos = 2usize;
331 mutated[mutated_pos] ^= 0x01;
332
333 let mut rng_a = FuzzRng::new(base);
334 let mut rng_b = FuzzRng::new(mutated);
335
336 let draws = 64usize;
337 let mut diff_indices = Vec::new();
338 for i in 0..draws {
339 if rng_a.next_u64() != rng_b.next_u64() {
340 diff_indices.push(i);
341 }
342 }
343
344 assert_eq!(diff_indices, vec![0, 1, 2, 59, 60, 61, 62, 63]);
345 }
346
347 #[test]
348 fn test_fill_bytes_shape_stability() {
349 let bytes: Vec<u8> = (0..32u8).collect();
350
351 let mut from_u64_rng = FuzzRng::new(bytes.clone());
352 let mut from_u64 = Vec::with_capacity(128);
353 for _ in 0..16 {
354 from_u64.extend_from_slice(&from_u64_rng.next_u64().to_be_bytes());
355 }
356
357 let mut from_fill_rng = FuzzRng::new(bytes);
358 let mut from_fill = vec![0u8; from_u64.len()];
359 let chunk_sizes = [3usize, 1, 7, 2, 11, 5, 13, 17];
360 let mut offset = 0;
361 let mut idx = 0;
362 while offset < from_fill.len() {
363 let chunk = chunk_sizes[idx % chunk_sizes.len()].min(from_fill.len() - offset);
364 from_fill_rng.fill_bytes(&mut from_fill[offset..offset + chunk]);
365 offset += chunk;
366 idx += 1;
367 }
368 assert_eq!(from_u64, from_fill);
369 }
370
371 #[test]
372 fn test_next_u32_consistency_with_fill_bytes() {
373 let bytes: Vec<u8> = (0..16u8).collect();
374
375 let mut from_u32_rng = FuzzRng::new(bytes.clone());
376 let mut from_u32 = Vec::with_capacity(64);
377 for _ in 0..16 {
378 from_u32.extend_from_slice(&from_u32_rng.next_u32().to_be_bytes());
379 }
380
381 let mut from_fill_rng = FuzzRng::new(bytes);
382 let mut from_fill = vec![0u8; from_u32.len()];
383 from_fill_rng.fill_bytes(&mut from_fill);
384 assert_eq!(from_u32, from_fill);
385 }
386
387 #[test]
388 fn test_try_fill_bytes_consistency_with_fill_bytes() {
389 let bytes: Vec<u8> = (0..16u8).collect();
390
391 let mut fill_rng = FuzzRng::new(bytes.clone());
392 let mut try_fill_rng = FuzzRng::new(bytes);
393
394 let mut fill_out = vec![0u8; 257];
395 fill_rng.fill_bytes(&mut fill_out);
396
397 let mut try_out = vec![0u8; 257];
398 try_fill_rng
399 .try_fill_bytes(&mut try_out)
400 .expect("try_fill_bytes should never fail");
401
402 assert_eq!(fill_out, try_out);
403 }
404
405 #[test]
406 fn test_next_u64_includes_counter_in_mix_input() {
407 let bytes = vec![0xAA; BLOCK_BYTES];
410 let mut rng = FuzzRng::new(bytes.clone());
411
412 let mut source = [0u8; BLOCK_BYTES];
413 source.copy_from_slice(&bytes[..BLOCK_BYTES]);
414 let word = u64::from_be_bytes(source);
415
416 let mix = |mut x: u64| {
417 x ^= x >> 30;
418 x = x.wrapping_mul(0xbf58476d1ce4e5b9);
419 x ^= x >> 27;
420 x = x.wrapping_mul(0x94d049bb133111eb);
421 x ^= x >> 31;
422 x
423 };
424
425 #[allow(clippy::identity_op)]
426 let expected0 = mix(word ^ 0 ^ crate::GOLDEN_RATIO);
427 let expected1 = mix(word ^ 1 ^ crate::GOLDEN_RATIO);
428
429 assert_eq!(rng.next_u64(), expected0);
430 assert_eq!(rng.next_u64(), expected1);
431 }
432
433 #[cfg(feature = "arbitrary")]
434 mod conformance {
435 use super::*;
436 use commonware_conformance::Conformance;
437 use rand::RngExt as _;
438
439 struct FuzzRngConformance;
445
446 impl Conformance for FuzzRngConformance {
447 async fn commit(seed: u64) -> Vec<u8> {
448 let mut seed_rng = TestRng::new(seed);
449 let len = seed_rng.random_range(1..=64);
450 let mut input = vec![0u8; len];
451 seed_rng.fill_bytes(&mut input);
452
453 let mut rng = FuzzRng::new(input);
454 const CONFORMANCE_BLOCKS: usize = 32;
455
456 let mut output = Vec::with_capacity(CONFORMANCE_BLOCKS * BLOCK_BYTES);
458 for _ in 0..CONFORMANCE_BLOCKS {
459 output.extend_from_slice(&rng.next_u64().to_be_bytes());
460 }
461 output
462 }
463 }
464
465 commonware_conformance::conformance_tests! {
466 FuzzRngConformance => 1024,
467 }
468 }
469}