use blake2::digest::{consts::U32, Digest};
type Blake2b256 = blake2::Blake2b<U32>;
const PRNG_DOMAIN: &[u8] = b"MFENX_PRNG";
const CHALLENGE_DOMAIN: &[u8] = b"MFENX_CHALLENGE";
#[derive(Debug, Clone)]
pub struct SimplePrng {
seed: [u8; 32],
counter: u64,
buffer: [u8; 32],
offset: usize,
}
impl SimplePrng {
pub fn new(seed: u64) -> Self {
let mut hasher = Blake2b256::new();
hasher.update(PRNG_DOMAIN);
hasher.update(seed.to_be_bytes());
let mut base = [0u8; 32];
base.copy_from_slice(&hasher.finalize());
Self::from_seed_bytes(base)
}
pub fn from_seed_bytes(seed: [u8; 32]) -> Self {
Self {
seed,
counter: 0,
buffer: [0u8; 32],
offset: 32,
}
}
fn refill(&mut self) {
let mut hasher = Blake2b256::new();
hasher.update(PRNG_DOMAIN);
hasher.update(self.seed);
hasher.update(self.counter.to_be_bytes());
self.buffer.copy_from_slice(&hasher.finalize());
self.counter = self.counter.wrapping_add(1);
self.offset = 0;
}
pub fn next_u64(&mut self) -> u64 {
if self.offset >= self.buffer.len() {
self.refill();
}
let mut chunk = [0u8; 8];
chunk.copy_from_slice(&self.buffer[self.offset..self.offset + 8]);
self.offset += 8;
u64::from_be_bytes(chunk)
}
pub fn gen_mod(&mut self, modulus: u64) -> u64 {
assert!(modulus != 0, "modulus must be non-zero");
self.next_u64() % modulus
}
}
pub fn derive_many_mod_p(p: u64, domain_tag: &[u8], transcript: &[u64], count: usize) -> Vec<u64> {
assert!(p != 0, "modulus must be non-zero");
let mut seed_hasher = Blake2b256::new();
seed_hasher.update(CHALLENGE_DOMAIN);
seed_hasher.update((domain_tag.len() as u64).to_be_bytes());
seed_hasher.update(domain_tag);
seed_hasher.update((transcript.len() as u64).to_be_bytes());
for &word in transcript {
seed_hasher.update(word.to_be_bytes());
}
let mut seed_bytes = [0u8; 32];
seed_bytes.copy_from_slice(&seed_hasher.finalize());
let mut prng = SimplePrng::from_seed_bytes(seed_bytes);
let mut out = Vec::with_capacity(count);
for _ in 0..count {
out.push(prng.gen_mod(p));
}
out
}