use rustev_contract::canonical::tagged_digest;
pub const GENERATOR: &str = "xoshiro256starstar-splitmix64/1";
pub const STREAM_TAG: &str = "rustev.bootstrap/1";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SplitMix64 {
pub x: u64,
}
impl SplitMix64 {
pub fn next_u64(&mut self) -> u64 {
self.x = self.x.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.x;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Xoshiro256StarStar {
pub s: [u64; 4],
}
impl Xoshiro256StarStar {
pub fn from_splitmix(seed: u64) -> Self {
let mut sm = SplitMix64 { x: seed };
let s = [sm.next_u64(), sm.next_u64(), sm.next_u64(), sm.next_u64()];
Xoshiro256StarStar { s }
}
pub fn next_u64(&mut self) -> u64 {
let s = &mut self.s;
let result = s[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9);
let t = s[1] << 17;
s[2] ^= s[0];
s[3] ^= s[1];
s[1] ^= s[2];
s[0] ^= s[3];
s[2] ^= t;
s[3] = s[3].rotate_left(45);
result
}
pub fn index(&mut self, n: u64) -> u64 {
assert!(n >= 1, "an index needs a non-empty range");
let rem = (u64::MAX % n + 1) % n;
loop {
let u = self.next_u64();
if rem != 0 && u >= 0u64.wrapping_sub(rem) {
continue;
}
return u % n;
}
}
}
pub fn stream_seed(seed: &str, label: &str) -> u64 {
let mut bytes = Vec::with_capacity(seed.len() + 1 + label.len());
bytes.extend_from_slice(seed.as_bytes());
bytes.push(0);
bytes.extend_from_slice(label.as_bytes());
let digest = tagged_digest(STREAM_TAG, &bytes);
let hex = &digest["sha256:".len().."sha256:".len() + 16];
u64::from_str_radix(hex, 16).expect("a tagged digest is lowercase hex")
}
pub fn stream(seed: &str, label: &str) -> Xoshiro256StarStar {
Xoshiro256StarStar::from_splitmix(stream_seed(seed, label))
}
pub fn run_seed(seed: &str, r: usize) -> u64 {
stream(seed, &format!("run/{r}")).next_u64()
}