1use rand_chacha::{
13 ChaCha20Rng,
14 rand_core::{Rng, SeedableRng},
15};
16use std::cell::RefCell;
17use thiserror::Error as ThisError;
18
19thread_local! {
20 static RNG: RefCell<Option<ChaCha20Rng>> = const { RefCell::new(None) };
21}
22
23#[derive(Debug, ThisError)]
33pub enum RngError {
34 #[error("Randomness is not initialized. Please try again later")]
35 NotInitialized,
36}
37
38pub fn seed_from(seed: [u8; 32]) {
44 RNG.with_borrow_mut(|rng| {
45 *rng = Some(ChaCha20Rng::from_seed(seed));
46 });
47}
48
49#[must_use]
51pub fn is_seeded() -> bool {
52 RNG.with_borrow(Option::is_some)
53}
54
55fn with_rng<T>(f: impl FnOnce(&mut ChaCha20Rng) -> T) -> Result<T, RngError> {
56 RNG.with_borrow_mut(|rng| match rng.as_mut() {
57 Some(rand) => Ok(f(rand)),
58 None => Err(RngError::NotInitialized),
59 })
60}
61
62pub fn fill_bytes(dest: &mut [u8]) -> Result<(), RngError> {
68 with_rng(|rand| rand.fill_bytes(dest))
69}
70
71pub fn random_bytes(size: usize) -> Result<Vec<u8>, RngError> {
73 let mut buf = vec![0u8; size];
74 fill_bytes(&mut buf)?;
75 Ok(buf)
76}
77
78pub fn next_u8() -> Result<u8, RngError> {
80 Ok((next_u16()? & 0xFF) as u8)
81}
82
83#[expect(clippy::cast_possible_truncation)]
85pub fn next_u16() -> Result<u16, RngError> {
86 with_rng(|rand| rand.next_u32() as u16)
87}
88
89pub fn next_u32() -> Result<u32, RngError> {
91 with_rng(Rng::next_u32)
92}
93
94pub fn next_u64() -> Result<u64, RngError> {
96 with_rng(Rng::next_u64)
97}
98
99pub fn next_u128() -> Result<u128, RngError> {
101 with_rng(|rand| {
102 let hi = u128::from(rand.next_u64());
103 let lo = u128::from(rand.next_u64());
104 (hi << 64) | lo
105 })
106}
107
108#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_unique_u64s() {
118 use std::collections::HashSet;
119
120 seed_from([7; 32]);
121
122 let mut set = HashSet::new();
123 while set.len() < 1000 {
124 let random_value = next_u64().expect("seeded RNG");
125 assert!(set.insert(random_value), "value already in set");
126 }
127 }
128
129 #[test]
130 fn test_rng_reseeding() {
131 seed_from([1; 32]);
132 let first = next_u64().expect("seeded RNG");
133 seed_from([2; 32]);
134 let second = next_u64().expect("seeded RNG");
135
136 assert_ne!(
137 first, second,
138 "RNGs with different seeds unexpectedly produced the same value"
139 );
140 }
141
142 #[test]
143 fn test_determinism_with_fixed_seed() {
144 let seed = [42u8; 32];
145 seed_from(seed);
146
147 let values: Vec<u64> = (0..100).map(|_| next_u64().expect("seeded RNG")).collect();
148
149 seed_from(seed);
150 for value in values {
151 assert_eq!(next_u64().expect("seeded RNG"), value);
152 }
153 }
154
155 #[test]
156 fn test_missing_seed_errors() {
157 RNG.with_borrow_mut(|rng| {
158 *rng = None;
159 });
160
161 assert!(matches!(random_bytes(8), Err(RngError::NotInitialized)));
162 }
163
164 #[test]
167 fn test_bit_entropy() {
168 seed_from([3; 32]);
169
170 let mut bits = 0u64;
171 for _ in 0..100 {
172 bits |= next_u64().expect("seeded RNG");
173 }
174
175 let bit_count = bits.count_ones();
176 assert!(
177 bit_count > 8,
178 "Low entropy: only {bit_count} bits set in 100 samples",
179 );
180 }
181}