1use rand_chacha::{
11 ChaCha20Rng,
12 rand_core::{RngCore, SeedableRng},
13};
14use std::cell::RefCell;
15
16thread_local! {
17 static RNG: RefCell<Option<ChaCha20Rng>> = const { RefCell::new(None) };
18}
19
20#[derive(Debug)]
30pub enum RngError {
31 RngNotInitialized(String),
32}
33
34impl RngError {
35 fn not_initialized() -> Self {
36 Self::RngNotInitialized("Randomness is not initialized. Please try again later".to_string())
37 }
38}
39
40pub fn seed_from(seed: [u8; 32]) {
46 RNG.with_borrow_mut(|rng| {
47 *rng = Some(ChaCha20Rng::from_seed(seed));
48 });
49}
50
51#[must_use]
53pub fn is_seeded() -> bool {
54 RNG.with_borrow(Option::is_some)
55}
56
57fn with_rng<T>(f: impl FnOnce(&mut ChaCha20Rng) -> T) -> Result<T, RngError> {
58 RNG.with_borrow_mut(|rng| match rng.as_mut() {
59 Some(rand) => Ok(f(rand)),
60 None => Err(RngError::not_initialized()),
61 })
62}
63
64pub fn fill_bytes(dest: &mut [u8]) -> Result<(), RngError> {
70 with_rng(|rand| rand.fill_bytes(dest))
71}
72
73pub fn random_bytes(size: usize) -> Result<Vec<u8>, RngError> {
75 let mut buf = vec![0u8; size];
76 fill_bytes(&mut buf)?;
77 Ok(buf)
78}
79
80pub fn random_hex(size: usize) -> Result<String, RngError> {
82 let bytes = random_bytes(size)?;
83 Ok(hex::encode(bytes))
84}
85
86pub fn next_u8() -> Result<u8, RngError> {
88 Ok((next_u16()? & 0xFF) as u8)
89}
90
91#[allow(clippy::cast_possible_truncation)]
93pub fn next_u16() -> Result<u16, RngError> {
94 with_rng(|rand| rand.next_u32() as u16)
95}
96
97pub fn next_u32() -> Result<u32, RngError> {
99 with_rng(RngCore::next_u32)
100}
101
102pub fn next_u64() -> Result<u64, RngError> {
104 with_rng(RngCore::next_u64)
105}
106
107pub fn next_u128() -> Result<u128, RngError> {
109 with_rng(|rand| {
110 let hi = u128::from(rand.next_u64());
111 let lo = u128::from(rand.next_u64());
112 (hi << 64) | lo
113 })
114}
115
116#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_unique_u64s() {
126 use std::collections::HashSet;
127
128 seed_from([7; 32]);
129
130 let mut set = HashSet::new();
131 while set.len() < 1000 {
132 let random_value = next_u64().expect("seeded RNG");
133 assert!(set.insert(random_value), "value already in set");
134 }
135 }
136
137 #[test]
138 fn test_rng_reseeding() {
139 seed_from([1; 32]);
140 let first = next_u64().expect("seeded RNG");
141 seed_from([2; 32]);
142 let second = next_u64().expect("seeded RNG");
143
144 assert_ne!(
145 first, second,
146 "RNGs with different seeds unexpectedly produced the same value"
147 );
148 }
149
150 #[test]
151 fn test_determinism_with_fixed_seed() {
152 let seed = [42u8; 32];
153 seed_from(seed);
154
155 let values: Vec<u64> = (0..100).map(|_| next_u64().expect("seeded RNG")).collect();
156
157 seed_from(seed);
158 for value in values {
159 assert_eq!(next_u64().expect("seeded RNG"), value);
160 }
161 }
162
163 #[test]
164 fn test_missing_seed_errors() {
165 RNG.with_borrow_mut(|rng| {
166 *rng = None;
167 });
168
169 assert!(matches!(
170 random_bytes(8),
171 Err(RngError::RngNotInitialized(_))
172 ));
173 }
174
175 #[test]
176 fn test_random_hex_length() {
177 seed_from([9; 32]);
178
179 let value = random_hex(6).expect("seeded RNG");
180 assert_eq!(value.len(), 12);
181 }
182
183 #[test]
186 fn test_bit_entropy() {
187 seed_from([3; 32]);
188
189 let mut bits = 0u64;
190 for _ in 0..100 {
191 bits |= next_u64().expect("seeded RNG");
192 }
193
194 let bit_count = bits.count_ones();
195 assert!(
196 bit_count > 8,
197 "Low entropy: only {bit_count} bits set in 100 samples",
198 );
199 }
200}