use rand_xoshiro::{rand_core::RngCore, Xoshiro256PlusPlus};
use xxhash_rust::xxh3::xxh3_128;
use crate::prng::make_prng;
pub struct SubRandomnessProvider {
rng: Xoshiro256PlusPlus,
}
impl SubRandomnessProvider {
pub fn provide(&mut self) -> [u8; 32] {
let mut out = [0u8; 32];
self.rng.fill_bytes(&mut out);
out
}
}
impl Iterator for SubRandomnessProvider {
type Item = [u8; 32];
fn next(&mut self) -> Option<Self::Item> {
Some(self.provide())
}
}
pub fn sub_randomness_with_key(
mut randomness: [u8; 32],
key: impl AsRef<[u8]>,
) -> Box<SubRandomnessProvider> {
let hashed_key = xxh3_128(key.as_ref()).to_be_bytes();
for (pos, byte) in hashed_key.iter().enumerate() {
randomness[pos] ^= byte;
}
let rng = make_prng(randomness);
Box::new(SubRandomnessProvider { rng })
}
pub fn sub_randomness(randomness: [u8; 32]) -> Box<SubRandomnessProvider> {
sub_randomness_with_key(randomness, b"_^default^_")
}
#[cfg(test)]
mod tests {
use crate::{coinflip, pick, RANDOMNESS1};
use super::*;
#[test]
fn sub_randomness_with_key_works() {
let mut provider1 = sub_randomness_with_key([0xA6; 32], "A");
let mut provider2 = sub_randomness_with_key([0xA6; 32], "A");
assert_eq!(provider1.provide(), provider2.provide());
assert_eq!(provider1.provide(), provider2.provide());
assert_eq!(provider1.provide(), provider2.provide());
let mut provider1 = sub_randomness_with_key([0xA6; 32], "/my_namespace/ab");
let mut provider2 = sub_randomness_with_key([0xA6; 32], "/my_namespace/cd");
assert_ne!(provider1.provide(), provider2.provide());
assert_ne!(provider1.provide(), provider2.provide());
assert_ne!(provider1.provide(), provider2.provide());
}
#[test]
fn sub_randomness_works() {
let randomness: [u8; 32] = [0x77; 32];
let mut provider = sub_randomness(randomness);
let v1 = provider.provide();
let v2 = provider.provide();
let v3 = provider.provide();
let v4 = provider.provide();
println!("v1 = {v1:?}");
println!("v2 = {v2:?}");
println!("v3 = {v3:?}");
println!("v4 = {v4:?}");
let mut provider1 = sub_randomness([0xA6; 32]);
let mut provider2 = sub_randomness([0xA6; 32]);
assert_eq!(provider1.provide(), provider2.provide());
assert_eq!(provider1.provide(), provider2.provide());
assert_eq!(provider1.provide(), provider2.provide());
let mut provider1 = sub_randomness([0xA6; 32]);
let mut provider2 = sub_randomness([0xCF; 32]);
assert_ne!(provider1.provide(), provider2.provide());
assert_ne!(provider1.provide(), provider2.provide());
assert_ne!(provider1.provide(), provider2.provide());
let mut provider1 = sub_randomness([0xA6; 32]);
let mut provider2 = sub_randomness_with_key([0xA6; 32], "_^default^_");
assert_eq!(provider1.provide(), provider2.provide());
assert_eq!(provider1.provide(), provider2.provide());
assert_eq!(provider1.provide(), provider2.provide());
}
#[test]
fn sub_randomness_implements_iterator() {
let randomness: [u8; 32] = [0x77; 32];
let mut provider = sub_randomness(randomness);
let v1 = provider.next().unwrap();
let v2 = provider.next().unwrap();
let v3 = provider.next().unwrap();
let v4 = provider.next().unwrap();
println!("v1 = {v1:?}");
println!("v2 = {v2:?}");
println!("v3 = {v3:?}");
println!("v4 = {v4:?}");
}
#[test]
fn coinflip_distribution_is_uniform() {
use crate::sub_randomness::sub_randomness;
use std::collections::HashMap;
const TEST_SAMPLE_SIZE: usize = 100_000;
const ACCURACY: f32 = 0.01;
let mut result = vec![];
let mut provider = sub_randomness(RANDOMNESS1);
for _ in 0..TEST_SAMPLE_SIZE {
let flip_is_heads = coinflip(provider.next().unwrap()).is_heads();
println!("{}", flip_is_heads);
result.push(flip_is_heads);
}
let mut histogram = HashMap::new();
for element in result {
let count = histogram.entry(element).or_insert(0);
*count += 1;
}
let estimated_count_for_uniform_distribution = (TEST_SAMPLE_SIZE / 2) as f32;
let estimation_min: i32 =
(estimated_count_for_uniform_distribution * (1_f32 - ACCURACY)) as i32;
let estimation_max: i32 =
(estimated_count_for_uniform_distribution * (1_f32 + ACCURACY)) as i32;
println!(
"estimation {}, max: {}, min: {}",
estimated_count_for_uniform_distribution, estimation_max, estimation_min
);
for (bin, count) in histogram {
println!("{}: {}", bin, count);
assert!(count >= estimation_min && count <= estimation_max);
}
}
#[test]
fn pick_distribution_is_uniform() {
use crate::sub_randomness::sub_randomness;
use std::collections::HashMap;
const TEST_SAMPLE_SIZE: usize = 300_000;
const N_PICKED_ELEMENTS: usize = 3;
const ACCURACY: f32 = 0.01;
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
let mut result = vec![vec![]];
let mut provider = sub_randomness(RANDOMNESS1);
for _ in 0..TEST_SAMPLE_SIZE - 1 {
let pick_result = pick(provider.next().unwrap(), N_PICKED_ELEMENTS, data.clone());
result.push(pick_result);
}
let mut histogram = HashMap::new();
for row in result {
for element in row {
let count = histogram.entry(element).or_insert(0);
*count += 1;
}
}
let estimated_count_for_uniform_distribution =
(TEST_SAMPLE_SIZE * N_PICKED_ELEMENTS / data.len()) as f32;
let estimation_min: i32 =
(estimated_count_for_uniform_distribution * (1_f32 - ACCURACY)) as i32;
let estimation_max: i32 =
(estimated_count_for_uniform_distribution * (1_f32 + ACCURACY)) as i32;
println!(
"estimation {}, max: {}, min: {}",
estimated_count_for_uniform_distribution, estimation_max, estimation_min
);
for (bin, count) in histogram {
println!("{}: {}", bin, count);
assert!(count >= estimation_min && count <= estimation_max);
}
}
}