use core::sync::atomic::{AtomicUsize, Ordering};
pub struct Random {
state: [u32; 16],
out: [u32; 16],
used: usize,
}
#[inline]
fn qround(x: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
x[a] = x[a].wrapping_add(x[b]);
x[d] = (x[d] ^ x[a]).rotate_left(16);
x[c] = x[c].wrapping_add(x[d]);
x[b] = (x[b] ^ x[c]).rotate_left(12);
x[a] = x[a].wrapping_add(x[b]);
x[d] = (x[d] ^ x[a]).rotate_left(8);
x[c] = x[c].wrapping_add(x[d]);
x[b] = (x[b] ^ x[c]).rotate_left(7);
}
impl Random {
pub const fn new() -> Random {
Random {
state: [0; 16],
out: [0; 16],
used: 16,
}
}
pub fn seed_from(&mut self, key: [u32; 8], stream: u64) {
self.state[0] = 0x6170_7865;
self.state[1] = 0x3320_646e;
self.state[2] = 0x7962_2d32;
self.state[3] = 0x6b20_6574;
self.state[4..12].copy_from_slice(&key);
self.state[12] = 0; self.state[13] = 0;
self.state[14] = stream as u32;
self.state[15] = (stream >> 32) as u32;
self.used = 16;
}
pub fn reseed(&mut self) {
let mut key = [0u32; 8];
if !os_entropy(&mut key) {
const GOLDEN: usize = 0x9E37_79B9_usize;
static COUNTER: AtomicUsize = AtomicUsize::new(GOLDEN);
let stack = core::ptr::from_ref(&key) as usize as u64;
let mut acc = crate::prim::clock_now()
^ stack.rotate_left(17)
^ (crate::prim::thread_id() as u64).rotate_left(33)
^ COUNTER.fetch_add(GOLDEN, Ordering::Relaxed) as u64;
for k in key.iter_mut() {
acc = acc.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = acc;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
*k = ((z ^ (z >> 31)) & 0xFFFF_FFFF) as u32;
}
}
let stream = crate::prim::thread_id() as u64;
self.seed_from(key, stream);
}
fn refill(&mut self) {
let mut x = self.state;
for _ in 0..4 {
qround(&mut x, 0, 4, 8, 12);
qround(&mut x, 1, 5, 9, 13);
qround(&mut x, 2, 6, 10, 14);
qround(&mut x, 3, 7, 11, 15);
qround(&mut x, 0, 5, 10, 15);
qround(&mut x, 1, 6, 11, 12);
qround(&mut x, 2, 7, 8, 13);
qround(&mut x, 3, 4, 9, 14);
}
for (o, (xi, si)) in self.out.iter_mut().zip(x.iter().zip(self.state.iter())) {
*o = xi.wrapping_add(*si);
}
let (lo, carry) = self.state[12].overflowing_add(1);
self.state[12] = lo;
if carry {
self.state[13] = self.state[13].wrapping_add(1);
}
self.used = 0;
}
#[inline]
pub fn next_u32(&mut self) -> u32 {
if self.used >= 16 {
self.refill();
}
let v = self.out[self.used];
self.used += 1;
v
}
#[inline]
pub fn next_usize(&mut self) -> usize {
let lo = self.next_u32() as usize;
#[cfg(target_pointer_width = "64")]
{
let hi = self.next_u32() as usize;
(hi << 32) | lo
}
#[cfg(not(target_pointer_width = "64"))]
{
lo
}
}
#[inline]
pub fn below(&mut self, n: usize) -> usize {
if n <= 1 {
return 0;
}
((self.next_usize() as u128 * n as u128) >> usize::BITS) as usize
}
}
impl Default for Random {
fn default() -> Self {
Self::new()
}
}
#[cfg(all(windows, not(miri)))]
fn os_entropy(key: &mut [u32; 8]) -> bool {
use windows_sys::Win32::Security::Cryptography::{
BCRYPT_USE_SYSTEM_PREFERRED_RNG, BCryptGenRandom,
};
let status = unsafe {
BCryptGenRandom(
core::ptr::null_mut(),
key.as_mut_ptr().cast::<u8>(),
(key.len() * 4) as u32,
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
)
};
status == 0
}
#[cfg(all(unix, feature = "std", not(miri)))]
fn os_entropy(key: &mut [u32; 8]) -> bool {
use std::io::Read;
let Ok(mut f) = std::fs::File::open("/dev/urandom") else {
return false;
};
let buf =
unsafe { core::slice::from_raw_parts_mut(key.as_mut_ptr().cast::<u8>(), key.len() * 4) };
f.read_exact(buf).is_ok()
}
#[cfg(miri)]
fn os_entropy(_key: &mut [u32; 8]) -> bool {
false }
#[cfg(all(
not(miri),
not(windows),
not(target_arch = "wasm32"),
// Bare metal, AND unix-without-`std`: both reach here because neither can
// open `/dev/urandom`.
not(all(unix, feature = "std"))
))]
fn os_entropy(_key: &mut [u32; 8]) -> bool {
false
}
#[cfg(all(target_arch = "wasm32", not(miri)))]
fn os_entropy(_key: &mut [u32; 8]) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stream_is_deterministic_and_varied() {
let mut a = Random::new();
let mut b = Random::new();
a.seed_from([1, 2, 3, 4, 5, 6, 7, 8], 42);
b.seed_from([1, 2, 3, 4, 5, 6, 7, 8], 42);
let xs: Vec<u32> = (0..64).map(|_| a.next_u32()).collect();
let ys: Vec<u32> = (0..64).map(|_| b.next_u32()).collect();
assert_eq!(xs, ys, "same seed must give the same stream");
assert!(xs.windows(2).any(|w| w[0] != w[1]), "stream is constant");
let mut c = Random::new();
c.seed_from([1, 2, 3, 4, 5, 6, 7, 8], 43);
let zs: Vec<u32> = (0..64).map(|_| c.next_u32()).collect();
assert_ne!(xs, zs);
}
#[test]
fn reseed_produces_distinct_streams() {
let mut a = Random::new();
let mut b = Random::new();
a.reseed();
b.reseed();
let xs: Vec<u32> = (0..8).map(|_| a.next_u32()).collect();
let ys: Vec<u32> = (0..8).map(|_| b.next_u32()).collect();
assert_ne!(xs, ys, "two reseeds collided");
}
#[test]
fn quarter_round_matches_rfc8439() {
let mut x = [0u32; 16];
x[0] = 0x1111_1111;
x[1] = 0x0102_0304;
x[2] = 0x9b8d_6f43;
x[3] = 0x0123_4567;
qround(&mut x, 0, 1, 2, 3);
assert_eq!(x[0], 0xea2a_92f4, "quarter round: a");
assert_eq!(x[1], 0xcb1c_f8ce, "quarter round: b");
assert_eq!(x[2], 0x4581_472e, "quarter round: c");
assert_eq!(x[3], 0x5881_c4bb, "quarter round: d");
}
#[test]
fn state_layout_matches_rfc8439() {
let mut r = Random::new();
let key = [
0x0302_0100,
0x0706_0504,
0x0b0a_0908,
0x0f0e_0d0c,
0x1312_1110,
0x1716_1514,
0x1b1a_1918,
0x1f1e_1d1c,
];
r.seed_from(key, 0xdead_beef_cafe_f00d);
assert_eq!(
[r.state[0], r.state[1], r.state[2], r.state[3]],
[0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574],
"the four ChaCha constants are wrong"
);
assert_eq!(&r.state[4..12], &key, "key is not in words 4..12");
assert_eq!(
[r.state[12], r.state[13]],
[0, 0],
"counter must start at 0"
);
assert_eq!(r.state[14], 0xcafe_f00d, "stream low word");
assert_eq!(r.state[15], 0xdead_beef, "stream high word");
}
#[test]
fn block_counter_advances_and_carries() {
let mut r = Random::new();
r.seed_from([0; 8], 0);
for _ in 0..16 {
r.next_u32(); }
assert_eq!(
[r.state[12], r.state[13]],
[1, 0],
"counter did not advance"
);
r.state[12] = u32::MAX;
r.used = 16;
r.next_u32();
assert_eq!(
[r.state[12], r.state[13]],
[0, 1],
"64-bit counter carry is broken: the keystream would repeat"
);
}
#[test]
fn chacha8_block_is_frozen() {
let mut r = Random::new();
r.seed_from([0; 8], 0);
let block: Vec<u32> = (0..16).map(|_| r.next_u32()).collect();
let mut s = [0u32; 16];
s[0] = 0x6170_7865;
s[1] = 0x3320_646e;
s[2] = 0x7962_2d32;
s[3] = 0x6b20_6574;
let orig = s;
let mut x = s;
for _ in 0..4 {
for &(a, b, c, d) in &[
(0usize, 4usize, 8usize, 12usize),
(1, 5, 9, 13),
(2, 6, 10, 14),
(3, 7, 11, 15),
(0, 5, 10, 15),
(1, 6, 11, 12),
(2, 7, 8, 13),
(3, 4, 9, 14),
] {
x[a] = x[a].wrapping_add(x[b]);
x[d] = (x[d] ^ x[a]).rotate_left(16);
x[c] = x[c].wrapping_add(x[d]);
x[b] = (x[b] ^ x[c]).rotate_left(12);
x[a] = x[a].wrapping_add(x[b]);
x[d] = (x[d] ^ x[a]).rotate_left(8);
x[c] = x[c].wrapping_add(x[d]);
x[b] = (x[b] ^ x[c]).rotate_left(7);
}
}
let expect: Vec<u32> = x
.iter()
.zip(orig.iter())
.map(|(a, b)| a.wrapping_add(*b))
.collect();
assert_eq!(
block, expect,
"ChaCha8 block function diverged from the RFC construction"
);
s = orig; let _ = s;
}
#[test]
fn keystream_is_not_degenerate() {
let mut r = Random::new();
r.seed_from([0xdead_beef; 8], 7);
let n: u32 = 4096;
let words: Vec<u32> = (0..n).map(|_| r.next_u32()).collect();
let ones: u32 = words.iter().map(|w| w.count_ones()).sum();
let frac = f64::from(ones) / f64::from(n * 32);
assert!(
(0.45..0.55).contains(&frac),
"keystream bit balance {frac} is not plausibly random"
);
let mut sorted = words.clone();
sorted.sort_unstable();
sorted.dedup();
assert!(
sorted.len() >= words.len() - 1,
"keystream repeated words: period far too short"
);
}
}