Skip to main content

lib_q_random/
specialized.rs

1//! Specialized random number generation implementations for different algorithms
2//!
3//! This module provides algorithm-specific RNG implementations that are optimized
4//! for particular use cases while maintaining the unified libQ random interface.
5
6use core::fmt;
7
8#[cfg_attr(not(feature = "rand"), allow(unused_imports))]
9use rand_core::{
10    Rng,
11    TryCryptoRng,
12    TryRng,
13};
14
15#[cfg(feature = "hash")]
16use crate::Error;
17
18/// Classical `McEliece` compatible RNG
19///
20/// This RNG provides the same interface as the original `AesState` but uses
21/// libQ's standard RNG infrastructure instead of AES-based generation.
22#[derive(Clone, Debug, PartialEq)]
23pub struct ClassicalMcElieceRng {
24    /// Internal state for deterministic mode
25    state: u64,
26    /// Counter for deterministic mode
27    counter: u64,
28    /// Whether this RNG is in deterministic mode
29    deterministic: bool,
30    /// Reseed counter for security
31    reseed_counter: u32,
32}
33
34impl ClassicalMcElieceRng {
35    /// Create a new secure RNG using system entropy
36    ///
37    /// Random output is drawn with `getrandom::fill`. The `classical-mceliece` crate feature
38    /// enables the `getrandom` dependency so non-test builds always have OS entropy available
39    /// (including `wasm_js` on `wasm32-unknown-unknown` when configured in the dependency graph).
40    #[must_use]
41    pub fn new() -> Self {
42        Self {
43            state: 0,
44            counter: 0,
45            deterministic: false,
46            reseed_counter: 0,
47        }
48    }
49
50    /// Create a new deterministic RNG for testing
51    ///
52    /// This creates an RNG that produces deterministic output based on the seed.
53    /// This is useful for testing and reproducible key generation.
54    #[must_use]
55    pub fn new_deterministic(seed: u64) -> Self {
56        Self {
57            state: seed
58                .wrapping_mul(6_364_136_223_846_793_005_u64)
59                .wrapping_add(1_442_695_040_888_963_407_u64),
60            counter: 0,
61            deterministic: true,
62            reseed_counter: 0,
63        }
64    }
65
66    /// Create a new deterministic RNG from byte array
67    ///
68    /// This creates an RNG that produces deterministic output based on the byte array.
69    /// The bytes are hashed to create a 64-bit seed.
70    #[must_use]
71    pub fn new_deterministic_from_bytes(seed_bytes: &[u8]) -> Self {
72        let mut hash = 0u64;
73        for (i, &byte) in seed_bytes.iter().enumerate() {
74            hash = hash.wrapping_add(u64::from(byte) << (i % 8));
75        }
76        Self::new_deterministic(hash)
77    }
78
79    /// Initialize the RNG with entropy (for deterministic mode)
80    ///
81    /// This method is provided for compatibility with the original `AesState` interface.
82    /// In deterministic mode, it updates the internal state.
83    /// In secure mode, it's a no-op as the RNG uses system entropy.
84    pub fn randombytes_init(&mut self, entropy_input: [u8; 48]) {
85        if self.deterministic {
86            // Mix the entropy into the state using a simple hash-like function
87            let mut hash = 0u64;
88            for (i, &byte) in entropy_input.iter().enumerate() {
89                hash = hash.wrapping_add(u64::from(byte) << (i % 8));
90            }
91            self.state = self.state.wrapping_add(hash);
92            self.counter = 0;
93            self.reseed_counter = 1;
94        }
95        // In secure mode, we don't need to initialize with entropy
96        // as the RNG uses system entropy sources
97    }
98
99    /// Generate random bytes using the appropriate method
100    fn generate_bytes(&mut self, dest: &mut [u8]) {
101        if self.deterministic {
102            self.generate_deterministic_bytes(dest);
103        } else {
104            self.generate_secure_bytes(dest);
105        }
106    }
107
108    /// Generate deterministic random bytes for testing
109    ///
110    /// This implementation provides better statistical properties for CB-KEM
111    /// by using a more sophisticated deterministic generation approach.
112    fn generate_deterministic_bytes(&mut self, dest: &mut [u8]) {
113        for chunk in dest.chunks_mut(8) {
114            // Use a more sophisticated deterministic generation
115            // that provides better statistical properties for CB-KEM
116
117            // Multiple LCG iterations for better distribution
118            for _ in 0..3 {
119                self.state = self
120                    .state
121                    .wrapping_mul(6_364_136_223_846_793_005_u64)
122                    .wrapping_add(1_442_695_040_888_963_407_u64);
123            }
124
125            self.counter = self.counter.wrapping_add(1);
126
127            // Enhanced entropy mixing for better statistical properties
128            // This addresses the specific needs of CB-KEM
129            let mut value = self.state ^
130                self.counter ^
131                (u64::from(self.reseed_counter) << 32) ^
132                (u64::from(self.reseed_counter) << 16);
133
134            // Additional mixing for better statistical properties
135            // This helps ensure the RNG output has properties suitable for CB-KEM
136            value = value.wrapping_mul(0x9E37_79B9_7F4A_7C15_u64); // Golden ratio constant
137            value ^= value >> 33;
138            value = value.wrapping_mul(0x9E37_79B9_7F4A_7C15_u64);
139            value ^= value >> 29;
140            value = value.wrapping_mul(0xC4CE_B9FE_1A85_EC53_u64);
141            value ^= value >> 32;
142
143            let bytes = value.to_le_bytes();
144
145            let len = chunk.len().min(8);
146            chunk[..len].copy_from_slice(&bytes[..len]);
147        }
148
149        self.reseed_counter = self.reseed_counter.wrapping_add(1);
150    }
151
152    /// Generate secure random bytes using system entropy
153    ///
154    /// If this crate was built without the `getrandom` feature, this function panics instead of
155    /// emitting predictable bytes. If `getrandom::fill` fails at runtime, this function also
156    /// panics (`TryRng` uses [`core::convert::Infallible`], so errors cannot be propagated).
157    fn generate_secure_bytes(&mut self, dest: &mut [u8]) {
158        #[cfg(feature = "getrandom")]
159        {
160            // Never recurse: `TryRng` uses `Infallible`, so refuse OS RNG failure loudly.
161            assert!(
162                getrandom::fill(dest).is_ok(),
163                "lib_q_random::ClassicalMcElieceRng: getrandom::fill failed; \
164                 refusing non-OS RNG output (enable `custom-entropy` or fix the environment)"
165            );
166        }
167
168        #[cfg(not(feature = "getrandom"))]
169        {
170            let _ = dest;
171            // Supported configurations that expose secure `ClassicalMcElieceRng` enable `getrandom`
172            // (see the `classical-mceliece` feature). If this path is hit, the crate was built
173            // without OS entropy support; do not substitute deterministic or PRNG output.
174            panic!(
175                "lib_q_random: ClassicalMcElieceRng requires the `getrandom` crate feature for secure output; \
176                 enable `getrandom`/`secure`/`classical-mceliece`, or use `new_deterministic` for tests"
177            );
178        }
179
180        self.reseed_counter = self.reseed_counter.wrapping_add(1);
181    }
182}
183
184impl Default for ClassicalMcElieceRng {
185    fn default() -> Self {
186        Self::new()
187    }
188}
189
190impl Eq for ClassicalMcElieceRng {}
191
192impl fmt::Display for ClassicalMcElieceRng {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        writeln!(f, "ClassicalMcElieceRng {{")?;
195        writeln!(f, "  state = {}", self.state)?;
196        writeln!(f, "  counter = {}", self.counter)?;
197        writeln!(f, "  deterministic = {}", self.deterministic)?;
198        writeln!(f, "  reseed_counter = {}", self.reseed_counter)?;
199        writeln!(f, "}}")
200    }
201}
202
203impl TryRng for ClassicalMcElieceRng {
204    type Error = core::convert::Infallible;
205
206    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
207        let mut bytes = [0u8; 4];
208        self.try_fill_bytes(&mut bytes)?;
209        Ok(u32::from_le_bytes(bytes))
210    }
211
212    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
213        let mut bytes = [0u8; 8];
214        self.try_fill_bytes(&mut bytes)?;
215        Ok(u64::from_le_bytes(bytes))
216    }
217
218    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
219        self.generate_bytes(dest);
220        Ok(())
221    }
222}
223
224impl TryCryptoRng for ClassicalMcElieceRng {}
225
226/// HPKE-compatible RNG using RFC 9861 **KT128** (`KangarooTwelve` / `TurboSHAKE128`)
227///
228/// This implementation provides cryptographically secure random number generation
229/// using libQ's **KT128** (`KangarooTwelve`) primitive. K12 is significantly
230/// faster than SHAKE256 while maintaining the same security properties.
231#[cfg(feature = "hash")]
232#[derive(Clone, Debug)]
233pub struct Kt128Rng {
234    expander: crate::kt128_expander::Kt128Expander,
235}
236
237#[cfg(feature = "hash")]
238impl Kt128Rng {
239    /// Create a new secure RNG with system entropy
240    ///
241    /// # Errors
242    ///
243    /// Returns an error if entropy source is unavailable or fails to initialize.
244    pub fn new() -> crate::Result<Self> {
245        #[cfg(feature = "getrandom")]
246        {
247            // Use system entropy to seed the RNG
248            let mut seed = [0u8; 32];
249            getrandom::fill(&mut seed).map_err(|_| Error::EntropySourceUnavailable {
250                source: "system",
251                context: Some("getrandom failed"),
252            })?;
253            Ok(Self::from_seed(&seed))
254        }
255        #[cfg(not(feature = "getrandom"))]
256        {
257            // No insecure fallback - fail fast if getrandom is not available
258            Err(Error::FeatureNotAvailable {
259                feature: "secure entropy",
260                required_features: &["getrandom"],
261            })
262        }
263    }
264
265    /// Create a new secure RNG with explicit seed
266    #[must_use]
267    pub fn from_seed(seed: &[u8]) -> Self {
268        Self {
269            expander: crate::kt128_expander::Kt128Expander::from_seed(
270                crate::kt128_expander::DOMAIN_HPKE_RNG,
271                seed,
272            ),
273        }
274    }
275
276    /// Refill the internal buffer with new random data (advances the KT128 chain).
277    pub fn refill(&mut self) {
278        self.expander.refill();
279    }
280}
281
282#[cfg(feature = "hash")]
283impl TryRng for Kt128Rng {
284    type Error = core::convert::Infallible;
285
286    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
287        let mut bytes = [0u8; 4];
288        self.try_fill_bytes(&mut bytes)?;
289        Ok(u32::from_le_bytes(bytes))
290    }
291
292    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
293        let mut bytes = [0u8; 8];
294        self.try_fill_bytes(&mut bytes)?;
295        Ok(u64::from_le_bytes(bytes))
296    }
297
298    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
299        self.expander.fill_bytes(dest);
300        Ok(())
301    }
302}
303
304#[cfg(feature = "hash")]
305#[cfg(test)]
306mod kt128_rng_tests {
307    use super::*;
308    use crate::kt128_expander::Kt128Expander;
309
310    /// HPKE domain + seed must match the shared expander (regression for refactor).
311    #[test]
312    fn test_kt128_rng_matches_hpke_domain_expander() {
313        let seed = [9u8; 32];
314        let mut rng = Kt128Rng::from_seed(&seed);
315        let mut exp = Kt128Expander::from_seed(crate::kt128_expander::DOMAIN_HPKE_RNG, &seed);
316        let mut a = [0u8; 128];
317        let mut b = [0u8; 128];
318        rng.fill_bytes(&mut a);
319        exp.fill_bytes(&mut b);
320        assert_eq!(a, b);
321    }
322}
323
324#[cfg(feature = "hash")]
325impl TryCryptoRng for Kt128Rng {}
326
327// HPKE-specific trait implementation for compatibility
328// This will be implemented in the HPKE crate itself to avoid circular dependencies
329
330/// FN-DSA compatible RNG with environment-specific implementations
331pub struct FnDsaRng {
332    #[cfg(feature = "rand")]
333    rng: Option<rand::rngs::ThreadRng>,
334}
335
336impl Default for FnDsaRng {
337    fn default() -> Self {
338        Self::new()
339    }
340}
341
342impl FnDsaRng {
343    /// Create a new FN-DSA compatible RNG
344    #[must_use]
345    pub fn new() -> Self {
346        #[cfg(feature = "rand")]
347        {
348            Self {
349                rng: Some(rand::rng()),
350            }
351        }
352        #[cfg(not(feature = "rand"))]
353        {
354            Self {}
355        }
356    }
357}
358
359impl TryRng for FnDsaRng {
360    type Error = core::convert::Infallible;
361
362    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
363        #[cfg(feature = "rand")]
364        {
365            if let Some(ref mut rng) = self.rng {
366                Ok(rng.next_u32())
367            } else {
368                let mut bytes = [0u8; 4];
369                self.try_fill_bytes(&mut bytes)?;
370                Ok(u32::from_le_bytes(bytes))
371            }
372        }
373        #[cfg(not(feature = "rand"))]
374        {
375            #[cfg(feature = "getrandom")]
376            {
377                let mut bytes = [0u8; 4];
378                getrandom::fill(&mut bytes).expect("Failed to get random bytes from getrandom");
379                Ok(u32::from_le_bytes(bytes))
380            }
381            #[cfg(not(feature = "getrandom"))]
382            {
383                panic!(
384                    "FnDsaRng requires either 'rand' or 'getrandom' feature. \
385                       Use deterministic RNG for testing without these features."
386                );
387            }
388        }
389    }
390
391    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
392        #[cfg(all(feature = "getrandom", not(feature = "rand")))]
393        {
394            let mut bytes = [0u8; 8];
395            getrandom::fill(&mut bytes).expect("Failed to get random bytes from getrandom");
396            Ok(u64::from_le_bytes(bytes))
397        }
398
399        #[cfg(not(all(feature = "getrandom", not(feature = "rand"))))]
400        {
401            let upper = u64::from(self.try_next_u32()?);
402            let lower = u64::from(self.try_next_u32()?);
403            Ok((upper << 32) | lower)
404        }
405    }
406
407    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
408        // `rand` disabled + `getrandom` enabled (e.g. WASM with `wasm_js`): fill the whole buffer in
409        // one call. The default path uses `try_next_u32` per 4-byte chunk, which would invoke
410        // `getrandom::fill` once per chunk and amplify syscall / JS bridge overhead.
411        #[cfg(all(feature = "getrandom", not(feature = "rand")))]
412        {
413            getrandom::fill(dest).expect("Failed to get random bytes from getrandom");
414            Ok(())
415        }
416
417        #[cfg(not(all(feature = "getrandom", not(feature = "rand"))))]
418        {
419            for chunk in dest.chunks_mut(4) {
420                let bytes = self.try_next_u32()?.to_le_bytes();
421                let len = chunk.len().min(4);
422                chunk[..len].copy_from_slice(&bytes[..len]);
423            }
424            Ok(())
425        }
426    }
427}
428
429impl TryCryptoRng for FnDsaRng {}
430
431#[cfg(test)]
432mod tests {
433    use rand_core::Rng;
434
435    use super::*;
436
437    #[test]
438    fn test_classical_mceliece_rng_creation() {
439        let rng = ClassicalMcElieceRng::new();
440        assert!(!rng.deterministic);
441        assert_eq!(rng.state, 0);
442        assert_eq!(rng.counter, 0);
443    }
444
445    #[test]
446    fn test_classical_mceliece_deterministic_rng_creation() {
447        let rng = ClassicalMcElieceRng::new_deterministic(12345);
448        assert!(rng.deterministic);
449        // The state is transformed by the LCG, so we check it's not zero
450        assert_ne!(rng.state, 0);
451        assert_eq!(rng.counter, 0);
452    }
453
454    #[test]
455    fn test_classical_mceliece_deterministic_rng_consistency() {
456        let mut rng1 = ClassicalMcElieceRng::new_deterministic(42);
457        let mut rng2 = ClassicalMcElieceRng::new_deterministic(42);
458
459        let mut bytes1 = [0u8; 32];
460        let mut bytes2 = [0u8; 32];
461
462        rng1.fill_bytes(&mut bytes1);
463        rng2.fill_bytes(&mut bytes2);
464
465        assert_eq!(bytes1, bytes2);
466    }
467
468    #[test]
469    fn test_classical_mceliece_rng_interface() {
470        let mut rng = ClassicalMcElieceRng::new_deterministic(100);
471
472        // Test fill_bytes
473        let mut bytes = [0u8; 16];
474        rng.fill_bytes(&mut bytes);
475        assert_ne!(bytes, [0u8; 16]); // Should not be all zeros
476
477        // Test next_u32
478        let val1 = rng.next_u32();
479        let val2 = rng.next_u32();
480        assert_ne!(val1, val2); // Should be different
481
482        // Test next_u64
483        let val3 = rng.next_u64();
484        let val4 = rng.next_u64();
485        assert_ne!(val3, val4); // Should be different
486    }
487
488    #[test]
489    fn test_classical_mceliece_randombytes_init() {
490        let mut rng = ClassicalMcElieceRng::new_deterministic(0);
491        let entropy = [1u8; 48];
492
493        rng.randombytes_init(entropy);
494
495        // State should be updated
496        assert_ne!(rng.state, 0);
497        assert_eq!(rng.reseed_counter, 1);
498    }
499
500    #[test]
501    fn test_fn_dsa_rng_creation() {
502        let _rng = FnDsaRng::new();
503        // Should create without panicking
504        // Test passes if we reach this point
505    }
506
507    #[test]
508    #[cfg(any(feature = "rand", feature = "getrandom"))]
509    fn test_fn_dsa_rng_interface() {
510        let mut rng = FnDsaRng::new();
511
512        // Test fill_bytes
513        let mut bytes = [0u8; 16];
514        rng.fill_bytes(&mut bytes);
515        // Should not panic
516
517        // Test next_u32
518        let val1 = rng.next_u32();
519        let val2 = rng.next_u32();
520        // Should not panic and should be different (very high probability)
521        assert_ne!(val1, val2);
522
523        // Test next_u64
524        let val3 = rng.next_u64();
525        let val4 = rng.next_u64();
526        // Should not panic and should be different (very high probability)
527        assert_ne!(val3, val4);
528    }
529
530    /// Odd-length buffer: `getrandom`-only `try_fill_bytes` must use a single `getrandom::fill`
531    /// (not one `fill` per 4-byte chunk via `try_next_u32`).
532    #[test]
533    #[cfg(all(feature = "getrandom", not(feature = "rand")))]
534    fn test_fn_dsa_rng_fill_bytes_getrandom_only_odd_length() {
535        let mut rng = FnDsaRng::new();
536        let mut buf = [0u8; 1281];
537        rng.fill_bytes(&mut buf);
538    }
539}