ya_rand/
secure.rs

1use crate::rng::{Generator, SecureGenerator};
2use chachacha::{BUF_LEN_U64, ChaCha8Djb, SEED_LEN_U8};
3use core::fmt;
4use core::mem::MaybeUninit;
5
6/// A cryptographically secure random number generator.
7///
8/// The current implementation uses ChaCha with 8 rounds and a 64-bit counter.
9/// This allows for 1 ZiB (2<sup>70</sup> bytes) of output before repeating.
10/// That's over 147 **quintillion** calls to [`SecureRng::u64`].
11pub struct SecureRng {
12    buf: [u64; BUF_LEN_U64],
13    index: usize,
14    internal: ChaCha8Djb,
15}
16
17impl fmt::Debug for SecureRng {
18    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19        f.write_str("all `SecureRng` fields are private")
20    }
21}
22
23impl SecureGenerator for SecureRng {
24    #[inline]
25    fn fill_bytes(&mut self, dst: &mut [u8]) {
26        // The `chachacha` crate provides a thoroughly tested and
27        // extremely fast fill implementation.
28        self.internal.fill(dst);
29    }
30}
31
32impl Generator for SecureRng {
33    #[inline]
34    fn try_new() -> Result<Self, getrandom::Error> {
35        // We want to randomize **all** bits of the matrix, even the counter.
36        // They're all going to be overwritten so no need to initialize them.
37        #[allow(clippy::uninit_assumed_init, invalid_value)]
38        let mut state = unsafe { MaybeUninit::<[u8; SEED_LEN_U8]>::uninit().assume_init() };
39        getrandom::fill(&mut state)?;
40        let mut internal = ChaCha8Djb::from(state);
41        let buf = internal.get_block_u64();
42        let index = 0;
43        Ok(Self {
44            buf,
45            index,
46            internal,
47        })
48    }
49
50    #[cfg_attr(feature = "inline", inline)]
51    fn u64(&mut self) -> u64 {
52        // TODO: Use the `unlikely` hint when it comes to stable.
53        if self.index >= self.buf.len() {
54            self.index = 0;
55            self.internal.fill_block_u64(&mut self.buf);
56        }
57        // SAFETY: We've just guaranteed that `self.index` will be
58        // in bounds in the above if-statement.
59        let ret = unsafe { *self.buf.get_unchecked(self.index) };
60        self.index += 1;
61        ret
62    }
63}