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")]
232pub struct Kt128Rng {
233    /// Internal buffer for K12 output
234    pub buffer: [u8; 32], // K12 output size
235    /// Current position in the buffer
236    pub position: usize,
237    counter: u64,
238}
239
240#[cfg(feature = "hash")]
241impl Kt128Rng {
242    /// Create a new secure RNG with system entropy
243    ///
244    /// # Errors
245    ///
246    /// Returns an error if entropy source is unavailable or fails to initialize.
247    pub fn new() -> crate::Result<Self> {
248        #[cfg(feature = "getrandom")]
249        {
250            // Use system entropy to seed the RNG
251            let mut seed = [0u8; 32];
252            getrandom::fill(&mut seed).map_err(|_| Error::EntropySourceUnavailable {
253                source: "system",
254                context: Some("getrandom failed"),
255            })?;
256            Ok(Self::from_seed(&seed))
257        }
258        #[cfg(not(feature = "getrandom"))]
259        {
260            // No insecure fallback - fail fast if getrandom is not available
261            Err(Error::FeatureNotAvailable {
262                feature: "secure entropy",
263                required_features: &["getrandom"],
264            })
265        }
266    }
267
268    /// Create a new secure RNG with explicit seed
269    #[must_use]
270    pub fn from_seed(seed: &[u8]) -> Self {
271        use lib_q_hash::digest::{
272            ExtendableOutput,
273            Update,
274            XofReader,
275        };
276
277        let mut k12 = lib_q_hash::Kt128::new(b"HPKE-RNG");
278        k12.update(seed);
279        let mut reader = k12.finalize_xof();
280
281        // Fill initial buffer
282        let mut buffer = [0u8; 32];
283        reader.read(&mut buffer);
284
285        Self {
286            buffer,
287            position: 0,
288            counter: 0,
289        }
290    }
291
292    /// Refill the internal buffer with new random data
293    pub fn refill(&mut self) {
294        use lib_q_hash::digest::{
295            ExtendableOutput,
296            Update,
297            XofReader,
298        };
299
300        // Use current buffer + counter as seed for next generation
301        let mut k12 = lib_q_hash::Kt128::new(b"HPKE-RNG");
302        k12.update(&self.buffer);
303        k12.update(&self.counter.to_le_bytes());
304        let mut reader = k12.finalize_xof();
305        reader.read(&mut self.buffer);
306        self.counter = self.counter.wrapping_add(1);
307        self.position = 0;
308    }
309}
310
311#[cfg(feature = "hash")]
312impl TryRng for Kt128Rng {
313    type Error = core::convert::Infallible;
314
315    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
316        let mut bytes = [0u8; 4];
317        self.try_fill_bytes(&mut bytes)?;
318        Ok(u32::from_le_bytes(bytes))
319    }
320
321    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
322        let mut bytes = [0u8; 8];
323        self.try_fill_bytes(&mut bytes)?;
324        Ok(u64::from_le_bytes(bytes))
325    }
326
327    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
328        let mut remaining = dest.len();
329        let mut offset = 0;
330
331        while remaining > 0 {
332            if self.position >= self.buffer.len() {
333                self.refill();
334            }
335
336            let available = self.buffer.len() - self.position;
337            let to_copy = core::cmp::min(remaining, available);
338
339            dest[offset..offset + to_copy]
340                .copy_from_slice(&self.buffer[self.position..self.position + to_copy]);
341
342            self.position += to_copy;
343            offset += to_copy;
344            remaining -= to_copy;
345        }
346        Ok(())
347    }
348}
349
350#[cfg(feature = "hash")]
351impl TryCryptoRng for Kt128Rng {}
352
353// HPKE-specific trait implementation for compatibility
354// This will be implemented in the HPKE crate itself to avoid circular dependencies
355
356/// FN-DSA compatible RNG with environment-specific implementations
357pub struct FnDsaRng {
358    #[cfg(feature = "rand")]
359    rng: Option<rand::rngs::ThreadRng>,
360}
361
362impl Default for FnDsaRng {
363    fn default() -> Self {
364        Self::new()
365    }
366}
367
368impl FnDsaRng {
369    /// Create a new FN-DSA compatible RNG
370    #[must_use]
371    pub fn new() -> Self {
372        #[cfg(feature = "rand")]
373        {
374            Self {
375                rng: Some(rand::rng()),
376            }
377        }
378        #[cfg(not(feature = "rand"))]
379        {
380            Self {}
381        }
382    }
383}
384
385impl TryRng for FnDsaRng {
386    type Error = core::convert::Infallible;
387
388    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
389        #[cfg(feature = "rand")]
390        {
391            if let Some(ref mut rng) = self.rng {
392                Ok(rng.next_u32())
393            } else {
394                let mut bytes = [0u8; 4];
395                self.try_fill_bytes(&mut bytes)?;
396                Ok(u32::from_le_bytes(bytes))
397            }
398        }
399        #[cfg(not(feature = "rand"))]
400        {
401            #[cfg(feature = "getrandom")]
402            {
403                let mut bytes = [0u8; 4];
404                getrandom::fill(&mut bytes).expect("Failed to get random bytes from getrandom");
405                Ok(u32::from_le_bytes(bytes))
406            }
407            #[cfg(not(feature = "getrandom"))]
408            {
409                panic!(
410                    "FnDsaRng requires either 'rand' or 'getrandom' feature. \
411                       Use deterministic RNG for testing without these features."
412                );
413            }
414        }
415    }
416
417    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
418        #[cfg(all(feature = "getrandom", not(feature = "rand")))]
419        {
420            let mut bytes = [0u8; 8];
421            getrandom::fill(&mut bytes).expect("Failed to get random bytes from getrandom");
422            Ok(u64::from_le_bytes(bytes))
423        }
424
425        #[cfg(not(all(feature = "getrandom", not(feature = "rand"))))]
426        {
427            let upper = u64::from(self.try_next_u32()?);
428            let lower = u64::from(self.try_next_u32()?);
429            Ok((upper << 32) | lower)
430        }
431    }
432
433    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
434        // `rand` disabled + `getrandom` enabled (e.g. WASM with `wasm_js`): fill the whole buffer in
435        // one call. The default path uses `try_next_u32` per 4-byte chunk, which would invoke
436        // `getrandom::fill` once per chunk and amplify syscall / JS bridge overhead.
437        #[cfg(all(feature = "getrandom", not(feature = "rand")))]
438        {
439            getrandom::fill(dest).expect("Failed to get random bytes from getrandom");
440            Ok(())
441        }
442
443        #[cfg(not(all(feature = "getrandom", not(feature = "rand"))))]
444        {
445            for chunk in dest.chunks_mut(4) {
446                let bytes = self.try_next_u32()?.to_le_bytes();
447                let len = chunk.len().min(4);
448                chunk[..len].copy_from_slice(&bytes[..len]);
449            }
450            Ok(())
451        }
452    }
453}
454
455impl TryCryptoRng for FnDsaRng {}
456
457#[cfg(test)]
458mod tests {
459    use rand_core::Rng;
460
461    use super::*;
462
463    #[test]
464    fn test_classical_mceliece_rng_creation() {
465        let rng = ClassicalMcElieceRng::new();
466        assert!(!rng.deterministic);
467        assert_eq!(rng.state, 0);
468        assert_eq!(rng.counter, 0);
469    }
470
471    #[test]
472    fn test_classical_mceliece_deterministic_rng_creation() {
473        let rng = ClassicalMcElieceRng::new_deterministic(12345);
474        assert!(rng.deterministic);
475        // The state is transformed by the LCG, so we check it's not zero
476        assert_ne!(rng.state, 0);
477        assert_eq!(rng.counter, 0);
478    }
479
480    #[test]
481    fn test_classical_mceliece_deterministic_rng_consistency() {
482        let mut rng1 = ClassicalMcElieceRng::new_deterministic(42);
483        let mut rng2 = ClassicalMcElieceRng::new_deterministic(42);
484
485        let mut bytes1 = [0u8; 32];
486        let mut bytes2 = [0u8; 32];
487
488        rng1.fill_bytes(&mut bytes1);
489        rng2.fill_bytes(&mut bytes2);
490
491        assert_eq!(bytes1, bytes2);
492    }
493
494    #[test]
495    fn test_classical_mceliece_rng_interface() {
496        let mut rng = ClassicalMcElieceRng::new_deterministic(100);
497
498        // Test fill_bytes
499        let mut bytes = [0u8; 16];
500        rng.fill_bytes(&mut bytes);
501        assert_ne!(bytes, [0u8; 16]); // Should not be all zeros
502
503        // Test next_u32
504        let val1 = rng.next_u32();
505        let val2 = rng.next_u32();
506        assert_ne!(val1, val2); // Should be different
507
508        // Test next_u64
509        let val3 = rng.next_u64();
510        let val4 = rng.next_u64();
511        assert_ne!(val3, val4); // Should be different
512    }
513
514    #[test]
515    fn test_classical_mceliece_randombytes_init() {
516        let mut rng = ClassicalMcElieceRng::new_deterministic(0);
517        let entropy = [1u8; 48];
518
519        rng.randombytes_init(entropy);
520
521        // State should be updated
522        assert_ne!(rng.state, 0);
523        assert_eq!(rng.reseed_counter, 1);
524    }
525
526    #[test]
527    fn test_fn_dsa_rng_creation() {
528        let _rng = FnDsaRng::new();
529        // Should create without panicking
530        // Test passes if we reach this point
531    }
532
533    #[test]
534    #[cfg(any(feature = "rand", feature = "getrandom"))]
535    fn test_fn_dsa_rng_interface() {
536        let mut rng = FnDsaRng::new();
537
538        // Test fill_bytes
539        let mut bytes = [0u8; 16];
540        rng.fill_bytes(&mut bytes);
541        // Should not panic
542
543        // Test next_u32
544        let val1 = rng.next_u32();
545        let val2 = rng.next_u32();
546        // Should not panic and should be different (very high probability)
547        assert_ne!(val1, val2);
548
549        // Test next_u64
550        let val3 = rng.next_u64();
551        let val4 = rng.next_u64();
552        // Should not panic and should be different (very high probability)
553        assert_ne!(val3, val4);
554    }
555
556    /// Odd-length buffer: `getrandom`-only `try_fill_bytes` must use a single `getrandom::fill`
557    /// (not one `fill` per 4-byte chunk via `try_next_u32`).
558    #[test]
559    #[cfg(all(feature = "getrandom", not(feature = "rand")))]
560    fn test_fn_dsa_rng_fill_bytes_getrandom_only_odd_length() {
561        let mut rng = FnDsaRng::new();
562        let mut buf = [0u8; 1281];
563        rng.fill_bytes(&mut buf);
564    }
565}