use blake3::Hasher;
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
pub type Key = [u8; 32];
#[derive(Copy, Clone, Debug)]
pub struct Seed {
pub master: u64,
}
impl Seed {
pub fn new(master: u64) -> Self {
Self { master }
}
fn derive(&self, domain: &[u8], indices: &[u64]) -> Key {
let mut h = Hasher::new();
h.update(b"shuflr-v1\0");
h.update(&self.master.to_le_bytes());
h.update(b"\0");
h.update(domain);
for idx in indices {
h.update(b"\0");
h.update(&idx.to_le_bytes());
}
*h.finalize().as_bytes()
}
pub fn epoch(&self, epoch: u64) -> Key {
self.derive(b"epoch", &[epoch])
}
pub fn perm(&self, epoch: u64) -> Key {
self.derive(b"perm", &[epoch])
}
pub fn frame(&self, epoch: u64, frame_id: u64) -> Key {
self.derive(b"frame", &[epoch, frame_id])
}
pub fn rng_from(key: Key) -> ChaCha20Rng {
ChaCha20Rng::from_seed(key)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn different_epochs_produce_different_keys() {
let s = Seed::new(42);
assert_ne!(s.epoch(0), s.epoch(1));
assert_ne!(s.perm(0), s.perm(1));
assert_ne!(s.frame(0, 0), s.frame(0, 1));
assert_ne!(s.frame(0, 0), s.frame(1, 0));
}
#[test]
fn determinism() {
let s1 = Seed::new(7);
let s2 = Seed::new(7);
assert_eq!(s1.epoch(3), s2.epoch(3));
assert_eq!(s1.frame(5, 17), s2.frame(5, 17));
}
#[test]
fn different_masters_produce_different_keys() {
let a = Seed::new(1);
let b = Seed::new(2);
assert_ne!(a.epoch(0), b.epoch(0));
assert_ne!(a.frame(3, 5), b.frame(3, 5));
}
}