Skip to main content

commonware_utils/
rng.rs

1//! Utilities for random number generation.
2
3use rand::{rngs::StdRng, CryptoRng, RngCore, SeedableRng};
4
5/// Returns a seeded RNG for deterministic testing.
6///
7/// Uses seed 0 by default to ensure reproducible test results.
8pub fn test_rng() -> StdRng {
9    StdRng::seed_from_u64(0)
10}
11
12/// Returns a seeded RNG with a custom seed for deterministic testing.
13///
14/// Use this when you need multiple independent RNG streams in the same test,
15/// or when a helper function needs its own RNG that won't collide with the caller's.
16pub fn test_rng_seeded(seed: u64) -> StdRng {
17    StdRng::seed_from_u64(seed)
18}
19
20/// FNV-1a hash for deterministic seed generation.
21///
22/// Uses FNV-1a instead of `DefaultHasher` because `DefaultHasher` is not
23/// guaranteed to be stable across Rust versions.
24fn fnv1a_hash(bytes: &[u8]) -> u64 {
25    const FNV_OFFSET: u64 = 0xcbf29ce484222325;
26    const FNV_PRIME: u64 = 0x100000001b3;
27
28    let mut hash = FNV_OFFSET;
29    for &byte in bytes {
30        hash ^= byte as u64;
31        hash = hash.wrapping_mul(FNV_PRIME);
32    }
33    hash
34}
35
36/// An RNG that reads from a byte buffer, falling back to a seeded RNG when exhausted.
37///
38/// This is useful for fuzzing where you want the fuzzer to control randomness
39/// through byte mutations. The raw bytes are consumed sequentially, and when
40/// exhausted, a fallback RNG (seeded from a hash of the buffer) provides additional
41/// randomness deterministically.
42pub struct BytesRng {
43    bytes: Vec<u8>,
44    offset: usize,
45    fallback: StdRng,
46}
47
48impl BytesRng {
49    /// Creates a new `BytesRng` from a byte buffer.
50    ///
51    /// All bytes are consumed sequentially as output. When exhausted, a fallback
52    /// RNG (seeded from a hash of the entire buffer) provides additional randomness.
53    pub fn new(bytes: Vec<u8>) -> Self {
54        let fallback = StdRng::seed_from_u64(fnv1a_hash(&bytes));
55        Self {
56            bytes,
57            offset: 0,
58            fallback,
59        }
60    }
61
62    /// Returns the number of raw bytes remaining before fallback.
63    pub const fn remaining(&self) -> usize {
64        self.bytes.len().saturating_sub(self.offset)
65    }
66
67    /// Returns the total number of bytes consumed from the raw buffer.
68    pub fn consumed(&self) -> usize {
69        self.offset.min(self.bytes.len())
70    }
71}
72
73impl RngCore for BytesRng {
74    fn next_u32(&mut self) -> u32 {
75        let mut buf = [0u8; 4];
76        self.fill_bytes(&mut buf);
77        u32::from_be_bytes(buf)
78    }
79
80    fn next_u64(&mut self) -> u64 {
81        let mut buf = [0u8; 8];
82        self.fill_bytes(&mut buf);
83        u64::from_be_bytes(buf)
84    }
85
86    fn fill_bytes(&mut self, dest: &mut [u8]) {
87        let from_buffer = dest.len().min(self.bytes.len().saturating_sub(self.offset));
88        dest[..from_buffer].copy_from_slice(&self.bytes[self.offset..self.offset + from_buffer]);
89        self.offset += from_buffer;
90        if from_buffer < dest.len() {
91            self.fallback.fill_bytes(&mut dest[from_buffer..]);
92        }
93    }
94
95    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
96        self.fill_bytes(dest);
97        Ok(())
98    }
99}
100
101impl CryptoRng for BytesRng {}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_empty_bytes() {
109        let mut rng = BytesRng::new(vec![]);
110        assert_eq!(rng.remaining(), 0);
111        assert_eq!(rng.consumed(), 0);
112
113        // Should use fallback immediately
114        let v1 = rng.next_u64();
115        let v2 = rng.next_u64();
116        assert_ne!(v1, v2); // Fallback should produce different values
117    }
118
119    #[test]
120    fn test_consumes_bytes_in_order() {
121        let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8];
122        let mut rng = BytesRng::new(bytes);
123
124        assert_eq!(rng.remaining(), 8);
125        assert_eq!(rng.consumed(), 0);
126
127        let mut buf = [0u8; 4];
128        rng.fill_bytes(&mut buf);
129        assert_eq!(buf, [1, 2, 3, 4]);
130        assert_eq!(rng.remaining(), 4);
131        assert_eq!(rng.consumed(), 4);
132
133        rng.fill_bytes(&mut buf);
134        assert_eq!(buf, [5, 6, 7, 8]);
135        assert_eq!(rng.remaining(), 0);
136        assert_eq!(rng.consumed(), 8);
137    }
138
139    #[test]
140    fn test_fallback_after_exhaustion() {
141        let bytes = vec![1, 2, 3, 4];
142        let mut rng = BytesRng::new(bytes.clone());
143
144        // Consume all bytes
145        let mut buf = [0u8; 4];
146        rng.fill_bytes(&mut buf);
147        assert_eq!(buf, [1, 2, 3, 4]);
148
149        // Now should use fallback
150        rng.fill_bytes(&mut buf);
151        let first_fallback = buf;
152
153        // Create another RNG with same bytes - fallback should be deterministic
154        let mut rng2 = BytesRng::new(bytes);
155        let mut buf2 = [0u8; 4];
156        rng2.fill_bytes(&mut buf2); // Skip raw bytes
157        rng2.fill_bytes(&mut buf2); // Get fallback
158
159        assert_eq!(first_fallback, buf2);
160    }
161
162    #[test]
163    fn test_fallback_seed_from_hash() {
164        // Different buffers should have different fallbacks
165        let bytes1 = vec![1, 2, 3, 4];
166        let bytes2 = vec![1, 2, 3, 5];
167
168        let mut rng1 = BytesRng::new(bytes1);
169        let mut rng2 = BytesRng::new(bytes2);
170
171        // Exhaust both
172        let mut buf = [0u8; 4];
173        rng1.fill_bytes(&mut buf);
174        rng2.fill_bytes(&mut buf);
175
176        // Fallback values should differ (different input hashes)
177        assert_ne!(rng1.next_u64(), rng2.next_u64());
178    }
179
180    #[test]
181    fn test_short_buffer_fallback_seed() {
182        // Buffer shorter than 8 bytes
183        let bytes = vec![1, 2, 3];
184        let mut rng = BytesRng::new(bytes);
185
186        // Exhaust buffer
187        let mut buf = [0u8; 3];
188        rng.fill_bytes(&mut buf);
189        assert_eq!(buf, [1, 2, 3]);
190
191        // Should still work with fallback
192        let v = rng.next_u64();
193        assert_ne!(v, 0); // Just verify it produces something
194    }
195
196    #[test]
197    fn test_deterministic_with_same_input() {
198        let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
199
200        let mut rng1 = BytesRng::new(bytes.clone());
201        let mut rng2 = BytesRng::new(bytes);
202
203        // Both should produce identical sequences
204        for _ in 0..100 {
205            assert_eq!(rng1.next_u64(), rng2.next_u64());
206        }
207    }
208
209    #[test]
210    fn test_next_u32() {
211        let bytes = vec![0x01, 0x02, 0x03, 0x04];
212        let mut rng = BytesRng::new(bytes);
213
214        let v = rng.next_u32();
215        assert_eq!(v, u32::from_be_bytes([0x01, 0x02, 0x03, 0x04]));
216    }
217
218    #[test]
219    fn test_next_u64() {
220        let bytes = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
221        let mut rng = BytesRng::new(bytes);
222
223        let v = rng.next_u64();
224        assert_eq!(
225            v,
226            u64::from_be_bytes([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
227        );
228    }
229
230    mod conformance {
231        use super::*;
232        use commonware_conformance::Conformance;
233
234        /// Conformance wrapper for BytesRng that tests output stability.
235        ///
236        /// This ensures that the FNV-1a hash and fallback RNG behavior
237        /// remain stable across versions.
238        struct BytesRngConformance;
239
240        impl Conformance for BytesRngConformance {
241            async fn commit(seed: u64) -> Vec<u8> {
242                let mut rng = BytesRng::new(seed.to_be_bytes().to_vec());
243
244                // Generate enough output to exercise both raw bytes and fallback
245                let mut output = Vec::with_capacity(64);
246                for _ in 0..8 {
247                    output.extend_from_slice(&rng.next_u64().to_be_bytes());
248                }
249                output
250            }
251        }
252
253        commonware_conformance::conformance_tests! {
254            BytesRngConformance => 1024,
255        }
256    }
257}