Skip to main content

oxicrypto_rand/
lib.rs

1#![forbid(unsafe_code)]
2
3//! Pure Rust CSPRNG for the OxiCrypto stack.
4//!
5//! Provides [`OxiRng`], a ChaCha20-based CSPRNG seeded from the OS
6//! via `getrandom`.
7//!
8//! # Fork Safety
9//!
10//! On Unix platforms, [`OxiRng`] tracks the process PID and automatically
11//! reseeds after a `fork()` to prevent parent/child CSPRNG state sharing.
12//!
13//! # Thread-Local RNG
14//!
15//! Use `with_thread_rng` (available with the `std` feature) for a convenient
16//! per-thread RNG (lazily initialized, no explicit RNG management required).
17//!
18//! # ChaCha Variants
19//!
20//! [`OxiRng8`] and [`OxiRng12`] provide ChaCha8 and ChaCha12-based CSPRNGs
21//! for higher throughput when the full 20-round version is not required.
22//!
23//! # Entropy Health Check
24//!
25//! [`check_entropy`] performs a basic OS-entropy smoke test (two independent
26//! draws must be non-zero and differ). This is not a NIST SP 800-90B test.
27
28mod helpers;
29mod oxirng;
30mod read;
31mod reseeding;
32mod thread_rng;
33
34// ── Public type re-exports ────────────────────────────────────────────────────
35
36pub use oxirng::{OxiRng, OxiRng12, OxiRng8};
37pub use reseeding::ReseedingRng;
38
39// ── Public function re-exports ────────────────────────────────────────────────
40
41pub use helpers::{
42    check_entropy, random_bool, random_bool_with_rng, random_bytes, random_nonce, random_range,
43    random_range_to, random_range_unbiased, random_u128, random_u32, random_u64, reseed, shuffle,
44    weighted_choice, weighted_choice_with_rng,
45};
46
47#[cfg(feature = "std")]
48pub use thread_rng::with_thread_rng;
49
50// ── Deterministic test RNG ───────────────────────────────────────────────────
51
52/// Deterministic RNG for reproducible tests.
53///
54/// Only compiled when `#[cfg(test)]`. Never use in production code.
55#[cfg(test)]
56pub mod test_rng {
57    use rand_chacha::ChaCha20Rng;
58    use rand_core::{SeedableRng, TryCryptoRng, TryRng};
59
60    use oxicrypto_core::{CryptoError, Rng};
61
62    /// A deterministic RNG for test reproducibility.
63    ///
64    /// Wraps [`ChaCha20Rng`] with a fixed seed. Available only in test builds.
65    pub struct TestRng(ChaCha20Rng);
66
67    impl TestRng {
68        /// Create a deterministic RNG from a 32-byte seed.
69        pub fn from_seed(seed: [u8; 32]) -> Self {
70            Self(ChaCha20Rng::from_seed(seed))
71        }
72    }
73
74    impl Rng for TestRng {
75        fn fill(&mut self, dst: &mut [u8]) -> Result<(), CryptoError> {
76            use rand_core::TryRng;
77            self.0.try_fill_bytes(dst).map_err(|_| CryptoError::Rng)
78        }
79    }
80
81    impl TryRng for TestRng {
82        type Error = core::convert::Infallible;
83
84        fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
85            self.0.try_next_u32()
86        }
87
88        fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
89            self.0.try_next_u64()
90        }
91
92        fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
93            self.0.try_fill_bytes(dest)
94        }
95    }
96
97    impl TryCryptoRng for TestRng {}
98}
99
100// ── Unit tests for lib.rs-specific items ─────────────────────────────────────
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use oxicrypto_core::Rng;
106
107    #[test]
108    fn test_rng_deterministic() {
109        use test_rng::TestRng;
110
111        let seed = [42u8; 32];
112        let mut rng1 = TestRng::from_seed(seed);
113        let mut rng2 = TestRng::from_seed(seed);
114
115        let mut out1 = [0u8; 64];
116        let mut out2 = [0u8; 64];
117        rng1.fill(&mut out1).expect("TestRng fill 1 failed");
118        rng2.fill(&mut out2).expect("TestRng fill 2 failed");
119
120        assert_eq!(out1, out2, "Same seed must produce same output");
121    }
122
123    #[test]
124    fn test_rng_different_seeds_differ() {
125        use test_rng::TestRng;
126
127        let mut rng_a = TestRng::from_seed([1u8; 32]);
128        let mut rng_b = TestRng::from_seed([2u8; 32]);
129
130        let mut out_a = [0u8; 64];
131        let mut out_b = [0u8; 64];
132        rng_a.fill(&mut out_a).expect("TestRng fill a failed");
133        rng_b.fill(&mut out_b).expect("TestRng fill b failed");
134
135        assert_ne!(
136            out_a, out_b,
137            "Different seeds must produce different output"
138        );
139    }
140}