poulpy_hal/source.rs
1use rand_chacha::{ChaCha8Rng, rand_core::SeedableRng};
2use rand_core::{Infallible, Rng, TryRng};
3
4/// 2^53, the number of distinct values representable in the 53-bit significand
5/// of an IEEE-754 `f64`. Used as the denominator when converting a random
6/// `u64` to a uniformly distributed floating-point value in `[0, 1)`.
7const MAXF64: f64 = 9007199254740992.0;
8
9/// Deterministic pseudorandom number generator based on ChaCha8.
10///
11/// Wraps [`ChaCha8Rng`] to provide reproducible random sampling for
12/// lattice-based cryptographic operations. Given the same 32-byte seed,
13/// the output sequence is identical across platforms.
14///
15/// **Not suitable for cryptographic key generation.** This type is intended
16/// for deterministic test vectors, noise sampling, and reproducible
17/// benchmarks, not for generating secrets.
18pub struct Source {
19 source: ChaCha8Rng,
20}
21
22impl Source {
23 /// Creates a new `Source` from a 32-byte seed.
24 ///
25 /// The same seed always produces the same pseudorandom sequence.
26 pub fn new(seed: [u8; 32]) -> Source {
27 Source {
28 source: ChaCha8Rng::from_seed(seed),
29 }
30 }
31
32 /// Derives an independent child `Source` for sub-stream splitting.
33 ///
34 /// Draws a fresh 32-byte seed from `self` and returns both the seed and
35 /// a new `Source` seeded with it. The parent and child streams are
36 /// statistically independent.
37 pub fn branch(&mut self) -> ([u8; 32], Self) {
38 let seed: [u8; 32] = self.new_seed();
39 (seed, Source::new(seed))
40 }
41
42 /// Draws 32 random bytes suitable for use as a derived seed.
43 pub fn new_seed(&mut self) -> [u8; 32] {
44 let mut seed: [u8; 32] = [0u8; 32];
45 self.fill_bytes(&mut seed);
46 seed
47 }
48
49 /// Returns a uniformly distributed `u64` in `[0, max)` using rejection
50 /// sampling with bitmask `mask`.
51 ///
52 /// `mask` should be `max.next_power_of_two() - 1` (or wider). Each
53 /// iteration draws one `u64` and masks it; values `>= max` are rejected.
54 /// Expected iterations: at most 2 when `mask` is tight.
55 #[inline(always)]
56 pub fn next_u64n(&mut self, max: u64, mask: u64) -> u64 {
57 let mut x: u64 = self.next_u64() & mask;
58 while x >= max {
59 x = self.next_u64() & mask;
60 }
61 x
62 }
63
64 /// Returns a uniformly distributed f64 in [min, max).
65 ///
66 /// # Panics
67 /// Panics if `min > max`.
68 #[inline(always)]
69 pub fn next_f64(&mut self, min: f64, max: f64) -> f64 {
70 debug_assert!(min <= max, "next_f64: min ({min}) > max ({max})");
71 min + ((self.next_u64() << 11 >> 11) as f64) / MAXF64 * (max - min)
72 }
73
74 /// Returns a uniformly distributed `i32` (bit-reinterpretation of a random `u32`).
75 #[inline(always)]
76 pub fn next_i32(&mut self) -> i32 {
77 self.next_u32() as i32
78 }
79
80 /// Returns a uniformly distributed `i64` (bit-reinterpretation of a random `u64`).
81 #[inline(always)]
82 pub fn next_i64(&mut self) -> i64 {
83 self.next_u64() as i64
84 }
85
86 /// Returns a uniformly distributed `i128` (bit-reinterpretation of a random `u128`).
87 #[inline(always)]
88 pub fn next_i128(&mut self) -> i128 {
89 self.next_u128() as i128
90 }
91
92 /// Returns a uniformly distributed `u128` by concatenating two `u64` draws.
93 #[inline(always)]
94 pub fn next_u128(&mut self) -> u128 {
95 (self.next_u64() as u128) << 64 | (self.next_u64() as u128)
96 }
97}
98
99/// Implements [`TryRng`] by delegating to the inner [`ChaCha8Rng`].
100/// The blanket `impl<R: TryRng<Error = Infallible>> Rng for R` in `rand_core`
101/// then provides [`Rng`] automatically.
102impl TryRng for Source {
103 type Error = Infallible;
104
105 #[inline(always)]
106 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
107 self.source.try_next_u32()
108 }
109
110 #[inline(always)]
111 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
112 self.source.try_next_u64()
113 }
114
115 #[inline(always)]
116 fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Self::Error> {
117 self.source.try_fill_bytes(bytes)
118 }
119}