Skip to main content

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}