use cryptography::Sha256;
use super::{OsRng, Rng};
const BLOCK: usize = 32;
#[derive(Clone, Debug)]
pub struct Squidward {
state: [u8; BLOCK],
offset: usize,
}
impl Squidward {
#[must_use]
pub fn from_seed(seed: &[u8]) -> Self {
Self {
state: sha256(seed),
offset: 0,
}
}
#[must_use]
pub fn from_os_rng() -> Self {
let mut os = OsRng::new();
let mut seed = [0u8; BLOCK];
for chunk in seed.chunks_exact_mut(4) {
chunk.copy_from_slice(&os.next_u32().to_le_bytes());
}
Self::from_seed(&seed)
}
#[must_use]
pub fn with_test_seed() -> Self {
let seed: [u8; BLOCK] = core::array::from_fn(|i| i as u8);
Self::from_seed(&seed)
}
fn refill(&mut self) {
self.state = sha256(&self.state);
self.offset = 0;
}
#[inline]
fn take_bytes<const N: usize>(&mut self) -> [u8; N] {
const { assert!(N <= BLOCK, "chunk larger than Squidward state") }
if self.offset + N > BLOCK {
self.refill();
}
let out = self.state[self.offset..self.offset + N].try_into().unwrap();
self.offset += N;
out
}
}
impl Default for Squidward {
fn default() -> Self {
Self::from_os_rng()
}
}
impl Rng for Squidward {
fn next_u32(&mut self) -> u32 {
u32::from_le_bytes(self.take_bytes::<4>())
}
fn next_u64(&mut self) -> u64 {
u64::from_le_bytes(self.take_bytes::<8>())
}
}
#[inline]
fn sha256(data: &[u8]) -> [u8; BLOCK] {
Sha256::digest(data)
}
#[cfg(test)]
mod tests {
use super::*;
use cryptography::Sha256;
#[test]
fn identical_seed_replays_identically() {
let seed = b"Squidward likes deterministic tests";
let mut a = Squidward::from_seed(seed);
let mut b = Squidward::from_seed(seed);
for _ in 0..32 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn pool_is_sha256_chain() {
let seed = b"entropy::Squidward";
let x0 = sha256(seed);
let mut rng = Squidward::from_seed(seed);
for chunk in x0.chunks_exact(8) {
assert_eq!(
rng.next_u64(),
u64::from_le_bytes(chunk.try_into().unwrap())
);
}
}
#[test]
fn refill_hashes_previous_state() {
let seed = b"hash the state again";
let x0 = sha256(seed);
let x1 = sha256(&x0);
let mut rng = Squidward::from_seed(seed);
for _ in 0..4 {
let _ = rng.next_u64();
}
assert_eq!(
rng.next_u64(),
u64::from_le_bytes(x1[0..8].try_into().unwrap())
);
}
#[test]
fn u32_and_u64_share_byte_stream() {
let mut a = Squidward::from_seed(b"stream");
let mut b = Squidward::from_seed(b"stream");
for _ in 0..64 {
let lo = a.next_u32() as u64;
let hi = a.next_u32() as u64;
assert_eq!(hi << 32 | lo, b.next_u64());
}
}
#[test]
fn sha256_matches_known_vector() {
let sw = Sha256::digest(b"");
let expected: [u8; 32] = [
0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
0x78, 0x52, 0xb8, 0x55,
];
assert_eq!(sw, expected);
}
}