1use commonware_macros::stability;
4#[stability(ALPHA)]
5use core::{convert::Infallible, mem::size_of};
6#[stability(BETA)]
7use rand::{CryptoRng, rand_core::UnwrapErr, rngs::SysRng};
8#[stability(ALPHA)]
9use rand::{SeedableRng, TryCryptoRng, TryRng, rngs::StdRng};
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#[stability(ALPHA)]
80#[derive(Debug)]
81pub struct ScriptedRng {
82 samples: std::vec::IntoIter<u64>,
83}
84
85#[stability(ALPHA)]
86impl ScriptedRng {
87 pub fn new(samples: impl IntoIterator<Item = u64>) -> Self {
89 Self {
90 samples: samples.into_iter().collect::<Vec<_>>().into_iter(),
91 }
92 }
93}
94
95#[stability(ALPHA)]
96impl TryRng for ScriptedRng {
97 type Error = Infallible;
98
99 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
100 Ok(self.try_next_u64()? as u32)
101 }
102
103 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
104 Ok(self
105 .samples
106 .next()
107 .expect("scripted RNG consumed more samples than expected"))
108 }
109
110 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
111 rand::rand_core::utils::fill_bytes_via_next_word(dest, || self.try_next_u64())
112 }
113}
114
115#[stability(ALPHA)]
118impl From<ScriptedRng> for Box<dyn CryptoRng + Send + 'static> {
119 fn from(rng: ScriptedRng) -> Self {
120 Box::new(rng)
121 }
122}
123
124#[stability(ALPHA)]
127impl TryCryptoRng for ScriptedRng {}
128
129#[inline]
134#[stability(ALPHA)]
135pub const fn mix64(mut word: u64) -> u64 {
136 word ^= word >> 30;
137 word = word.wrapping_mul(0xbf58_476d_1ce4_e5b9);
138 word ^= word >> 27;
139 word = word.wrapping_mul(0x94d0_49bb_1331_11eb);
140 word ^ (word >> 31)
141}
142
143#[stability(ALPHA)]
145const BLOCK_BYTES: usize = size_of::<u64>();
146
147#[stability(ALPHA)]
199pub struct FuzzRng {
200 bytes: Vec<u8>,
201 ctr: u64,
202 cache: [u8; BLOCK_BYTES],
203 cache_pos: usize,
204}
205
206#[stability(ALPHA)]
212#[derive(Clone, Debug)]
213pub struct Entropy(Vec<u8>);
214
215#[cfg(feature = "arbitrary")]
216#[stability(ALPHA)]
217impl<'a> arbitrary::Arbitrary<'a> for Entropy {
218 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
219 Ok(Self(u.bytes(u.len())?.to_vec()))
220 }
221}
222
223#[stability(ALPHA)]
224impl From<Entropy> for FuzzRng {
225 fn from(entropy: Entropy) -> Self {
226 Self::new(entropy.0)
227 }
228}
229
230#[stability(ALPHA)]
233impl From<Entropy> for Box<dyn CryptoRng + Send + 'static> {
234 fn from(entropy: Entropy) -> Self {
235 Box::new(FuzzRng::from(entropy))
236 }
237}
238
239#[stability(ALPHA)]
242impl From<FuzzRng> for Box<dyn CryptoRng + Send + 'static> {
243 fn from(rng: FuzzRng) -> Self {
244 Box::new(rng)
245 }
246}
247
248#[stability(ALPHA)]
249impl FuzzRng {
250 pub const fn new(bytes: Vec<u8>) -> Self {
252 Self {
253 bytes,
254 ctr: 0,
255 cache: [0u8; BLOCK_BYTES],
256 cache_pos: BLOCK_BYTES,
257 }
258 }
259
260 #[inline]
270 fn next_block_u64(&mut self) -> u64 {
271 let mut bytes = [0u8; BLOCK_BYTES];
274 if !self.bytes.is_empty() {
275 let len = self.bytes.len() as u64;
276 for (i, byte) in bytes.iter_mut().enumerate() {
277 *byte = self.bytes[(self.ctr.wrapping_add(i as u64) % len) as usize];
278 }
279 }
280 let word = u64::from_be_bytes(bytes);
281
282 let ctr = self.ctr;
285 self.ctr = self.ctr.wrapping_add(1);
286 mix64(word ^ ctr ^ crate::GOLDEN_RATIO)
287 }
288
289 fn fill_bytes_stream(&mut self, dest: &mut [u8]) {
290 let mut written = 0;
291 while written < dest.len() {
292 if self.cache_pos == self.cache.len() {
293 self.cache = self.next_block_u64().to_be_bytes();
298 self.cache_pos = 0;
299 }
300
301 let available = self.cache.len() - self.cache_pos;
302 let need = dest.len() - written;
303 let take = available.min(need);
304 dest[written..written + take]
305 .copy_from_slice(&self.cache[self.cache_pos..self.cache_pos + take]);
306 self.cache_pos += take;
307 written += take;
308 }
309 }
310}
311
312#[stability(ALPHA)]
313impl TryRng for FuzzRng {
314 type Error = Infallible;
315
316 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
317 let mut buf = [0u8; 4];
318 self.fill_bytes_stream(&mut buf);
319 Ok(u32::from_be_bytes(buf))
320 }
321
322 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
323 let mut buf = [0u8; BLOCK_BYTES];
324 self.fill_bytes_stream(&mut buf);
325 Ok(u64::from_be_bytes(buf))
326 }
327
328 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
329 self.fill_bytes_stream(dest);
330 Ok(())
331 }
332}
333
334#[stability(ALPHA)]
338impl TryCryptoRng for FuzzRng {}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use rand::Rng;
344
345 #[test]
346 fn test_scripted_rng_output_forms() {
347 let mut rng = ScriptedRng::new([
348 0x0123_4567_89ab_cdef,
349 0xfedc_ba98_7654_3210,
350 0x0807_0605_0403_0201,
351 ]);
352
353 assert_eq!(rng.try_next_u64().unwrap(), 0x0123_4567_89ab_cdef);
354 assert_eq!(rng.try_next_u32().unwrap(), 0x7654_3210);
355
356 let mut bytes = [0; 5];
357 rng.try_fill_bytes(&mut bytes).unwrap();
358 assert_eq!(bytes, [1, 2, 3, 4, 5]);
359 }
360
361 #[test]
362 fn test_empty_bytes_not_constant() {
363 let mut rng = FuzzRng::new(vec![]);
364
365 let values: Vec<_> = (0..BLOCK_BYTES).map(|_| rng.next_u64()).collect();
366 assert!(values.windows(2).any(|w| w[0] != w[1]));
367 }
368
369 #[test]
370 fn test_empty_bytes_deterministic() {
371 let mut rng1 = FuzzRng::new(vec![]);
372 let mut rng2 = FuzzRng::new(vec![]);
373
374 for _ in 0..256 {
375 assert_eq!(rng1.next_u64(), rng2.next_u64());
376 }
377 }
378
379 #[test]
380 fn test_all_zero_bytes_not_constant() {
381 let bytes = vec![0; BLOCK_BYTES];
382 let mut rng = FuzzRng::new(bytes);
383 let values: Vec<_> = (0..BLOCK_BYTES).map(|_| rng.next_u64()).collect();
384 assert!(values.windows(2).any(|w| w[0] != w[1]));
385 }
386
387 #[test]
388 fn test_deterministic_with_same_input() {
389 let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
390
391 let mut rng1 = FuzzRng::new(bytes.clone());
392 let mut rng2 = FuzzRng::new(bytes);
393
394 for _ in 0..1000 {
395 assert_eq!(rng1.next_u64(), rng2.next_u64());
396 }
397 }
398
399 #[test]
400 fn test_short_input_wraparound() {
401 for len in 1..=3 {
402 let bytes = vec![0xAB; len];
403 let mut rng1 = FuzzRng::new(bytes.clone());
404 let mut rng2 = FuzzRng::new(bytes);
405 let out1: Vec<_> = (0..32).map(|_| rng1.next_u64()).collect();
406 let out2: Vec<_> = (0..32).map(|_| rng2.next_u64()).collect();
407 assert_eq!(out1, out2);
408 assert!(out1.windows(2).any(|w| w[0] != w[1]));
409 }
410 }
411
412 #[test]
413 fn test_small_mutation_locality() {
414 let mut base = vec![0u8; 64];
415 for (i, byte) in base.iter_mut().enumerate() {
416 *byte = i as u8;
417 }
418 let mut mutated = base.clone();
419 let mutated_pos = 20usize;
420 mutated[mutated_pos] ^= 0x01;
421
422 let mut rng_a = FuzzRng::new(base);
423 let mut rng_b = FuzzRng::new(mutated);
424
425 let draws = 40usize;
426 let mut diff_indices = Vec::new();
427 for i in 0..draws {
428 if rng_a.next_u64() != rng_b.next_u64() {
429 diff_indices.push(i);
430 }
431 }
432
433 let expected: Vec<usize> = ((mutated_pos - 7)..=mutated_pos).collect();
434 assert_eq!(diff_indices, expected);
435 }
436
437 #[test]
438 fn test_small_mutation_locality_wraparound() {
439 let mut base = vec![0u8; 64];
440 for (i, byte) in base.iter_mut().enumerate() {
441 *byte = i as u8;
442 }
443 let mut mutated = base.clone();
444 let mutated_pos = 2usize;
445 mutated[mutated_pos] ^= 0x01;
446
447 let mut rng_a = FuzzRng::new(base);
448 let mut rng_b = FuzzRng::new(mutated);
449
450 let draws = 64usize;
451 let mut diff_indices = Vec::new();
452 for i in 0..draws {
453 if rng_a.next_u64() != rng_b.next_u64() {
454 diff_indices.push(i);
455 }
456 }
457
458 assert_eq!(diff_indices, vec![0, 1, 2, 59, 60, 61, 62, 63]);
459 }
460
461 #[test]
462 fn test_fill_bytes_shape_stability() {
463 let bytes: Vec<u8> = (0..32u8).collect();
464
465 let mut from_u64_rng = FuzzRng::new(bytes.clone());
466 let mut from_u64 = Vec::with_capacity(128);
467 for _ in 0..16 {
468 from_u64.extend_from_slice(&from_u64_rng.next_u64().to_be_bytes());
469 }
470
471 let mut from_fill_rng = FuzzRng::new(bytes);
472 let mut from_fill = vec![0u8; from_u64.len()];
473 let chunk_sizes = [3usize, 1, 7, 2, 11, 5, 13, 17];
474 let mut offset = 0;
475 let mut idx = 0;
476 while offset < from_fill.len() {
477 let chunk = chunk_sizes[idx % chunk_sizes.len()].min(from_fill.len() - offset);
478 from_fill_rng.fill_bytes(&mut from_fill[offset..offset + chunk]);
479 offset += chunk;
480 idx += 1;
481 }
482 assert_eq!(from_u64, from_fill);
483 }
484
485 #[test]
486 fn test_next_u32_consistency_with_fill_bytes() {
487 let bytes: Vec<u8> = (0..16u8).collect();
488
489 let mut from_u32_rng = FuzzRng::new(bytes.clone());
490 let mut from_u32 = Vec::with_capacity(64);
491 for _ in 0..16 {
492 from_u32.extend_from_slice(&from_u32_rng.next_u32().to_be_bytes());
493 }
494
495 let mut from_fill_rng = FuzzRng::new(bytes);
496 let mut from_fill = vec![0u8; from_u32.len()];
497 from_fill_rng.fill_bytes(&mut from_fill);
498 assert_eq!(from_u32, from_fill);
499 }
500
501 #[test]
502 fn test_try_fill_bytes_consistency_with_fill_bytes() {
503 let bytes: Vec<u8> = (0..16u8).collect();
504
505 let mut fill_rng = FuzzRng::new(bytes.clone());
506 let mut try_fill_rng = FuzzRng::new(bytes);
507
508 let mut fill_out = vec![0u8; 257];
509 fill_rng.fill_bytes(&mut fill_out);
510
511 let mut try_out = vec![0u8; 257];
512 try_fill_rng
513 .try_fill_bytes(&mut try_out)
514 .expect("try_fill_bytes should never fail");
515
516 assert_eq!(fill_out, try_out);
517 }
518
519 #[test]
520 fn test_next_u64_includes_counter_in_mix_input() {
521 let bytes = vec![0xAA; BLOCK_BYTES];
524 let mut rng = FuzzRng::new(bytes.clone());
525
526 let mut source = [0u8; BLOCK_BYTES];
527 source.copy_from_slice(&bytes[..BLOCK_BYTES]);
528 let word = u64::from_be_bytes(source);
529
530 let mix = |mut x: u64| {
531 x ^= x >> 30;
532 x = x.wrapping_mul(0xbf58476d1ce4e5b9);
533 x ^= x >> 27;
534 x = x.wrapping_mul(0x94d049bb133111eb);
535 x ^= x >> 31;
536 x
537 };
538
539 #[allow(clippy::identity_op)]
540 let expected0 = mix(word ^ 0 ^ crate::GOLDEN_RATIO);
541 let expected1 = mix(word ^ 1 ^ crate::GOLDEN_RATIO);
542
543 assert_eq!(rng.next_u64(), expected0);
544 assert_eq!(rng.next_u64(), expected1);
545 }
546
547 #[cfg(feature = "arbitrary")]
548 mod conformance {
549 use super::*;
550 use commonware_conformance::Conformance;
551 use rand::RngExt as _;
552
553 struct FuzzRngConformance;
559
560 impl Conformance for FuzzRngConformance {
561 async fn commit(seed: u64) -> Vec<u8> {
562 let mut seed_rng = TestRng::new(seed);
563 let len = seed_rng.random_range(1..=64);
564 let mut input = vec![0u8; len];
565 seed_rng.fill_bytes(&mut input);
566
567 let mut rng = FuzzRng::new(input);
568 const CONFORMANCE_BLOCKS: usize = 32;
569
570 let mut output = Vec::with_capacity(CONFORMANCE_BLOCKS * BLOCK_BYTES);
572 for _ in 0..CONFORMANCE_BLOCKS {
573 output.extend_from_slice(&rng.next_u64().to_be_bytes());
574 }
575 output
576 }
577 }
578
579 commonware_conformance::conformance_tests! {
580 FuzzRngConformance => 1024,
581 }
582 }
583}