1use rand_chacha::{
13 ChaCha20Rng,
14 rand_core::{RngCore, 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 random_hex(size: usize) -> Result<String, RngError> {
80 let bytes = random_bytes(size)?;
81 Ok(hex::encode(bytes))
82}
83
84pub fn next_u8() -> Result<u8, RngError> {
86 Ok((next_u16()? & 0xFF) as u8)
87}
88
89#[allow(clippy::cast_possible_truncation)]
91pub fn next_u16() -> Result<u16, RngError> {
92 with_rng(|rand| rand.next_u32() as u16)
93}
94
95pub fn next_u32() -> Result<u32, RngError> {
97 with_rng(RngCore::next_u32)
98}
99
100pub fn next_u64() -> Result<u64, RngError> {
102 with_rng(RngCore::next_u64)
103}
104
105pub fn next_u128() -> Result<u128, RngError> {
107 with_rng(|rand| {
108 let hi = u128::from(rand.next_u64());
109 let lo = u128::from(rand.next_u64());
110 (hi << 64) | lo
111 })
112}
113
114#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_unique_u64s() {
124 use std::collections::HashSet;
125
126 seed_from([7; 32]);
127
128 let mut set = HashSet::new();
129 while set.len() < 1000 {
130 let random_value = next_u64().expect("seeded RNG");
131 assert!(set.insert(random_value), "value already in set");
132 }
133 }
134
135 #[test]
136 fn test_rng_reseeding() {
137 seed_from([1; 32]);
138 let first = next_u64().expect("seeded RNG");
139 seed_from([2; 32]);
140 let second = next_u64().expect("seeded RNG");
141
142 assert_ne!(
143 first, second,
144 "RNGs with different seeds unexpectedly produced the same value"
145 );
146 }
147
148 #[test]
149 fn test_determinism_with_fixed_seed() {
150 let seed = [42u8; 32];
151 seed_from(seed);
152
153 let values: Vec<u64> = (0..100).map(|_| next_u64().expect("seeded RNG")).collect();
154
155 seed_from(seed);
156 for value in values {
157 assert_eq!(next_u64().expect("seeded RNG"), value);
158 }
159 }
160
161 #[test]
162 fn test_missing_seed_errors() {
163 RNG.with_borrow_mut(|rng| {
164 *rng = None;
165 });
166
167 assert!(matches!(random_bytes(8), Err(RngError::NotInitialized)));
168 }
169
170 #[test]
171 fn test_random_hex_length() {
172 seed_from([9; 32]);
173
174 let value = random_hex(6).expect("seeded RNG");
175 assert_eq!(value.len(), 12);
176 }
177
178 #[test]
181 fn test_bit_entropy() {
182 seed_from([3; 32]);
183
184 let mut bits = 0u64;
185 for _ in 0..100 {
186 bits |= next_u64().expect("seeded RNG");
187 }
188
189 let bit_count = bits.count_ones();
190 assert!(
191 bit_count > 8,
192 "Low entropy: only {bit_count} bits set in 100 samples",
193 );
194 }
195}