Skip to main content

rustev_eval/
generator.rs

1//! The bootstrap generator `xoshiro256starstar-splitmix64/1` (spec 029,
2//! 3.4.2 to 3.4.4 and 3.4.6).
3//!
4//! Implemented in-crate on wrapping `u64` arithmetic, so its stream can
5//! never change with a dependency's version. A later change of any step is
6//! a new generator name; this one never changes.
7
8use rustev_contract::canonical::tagged_digest;
9
10/// The generator's name in a repeat configuration.
11pub const GENERATOR: &str = "xoshiro256starstar-splitmix64/1";
12/// The tag of the stream-seed digest (3.4.3).
13pub const STREAM_TAG: &str = "rustev.bootstrap/1";
14
15/// SplitMix64 with state `x`.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct SplitMix64 {
18    pub x: u64,
19}
20
21impl SplitMix64 {
22    pub fn next_u64(&mut self) -> u64 {
23        self.x = self.x.wrapping_add(0x9E37_79B9_7F4A_7C15);
24        let mut z = self.x;
25        z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
26        z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
27        z ^ (z >> 31)
28    }
29}
30
31/// xoshiro256\*\* with state `s[0..4]`.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct Xoshiro256StarStar {
34    pub s: [u64; 4],
35}
36
37impl Xoshiro256StarStar {
38    /// State from four consecutive SplitMix64 outputs.
39    pub fn from_splitmix(seed: u64) -> Self {
40        let mut sm = SplitMix64 { x: seed };
41        let s = [sm.next_u64(), sm.next_u64(), sm.next_u64(), sm.next_u64()];
42        Xoshiro256StarStar { s }
43    }
44
45    pub fn next_u64(&mut self) -> u64 {
46        let s = &mut self.s;
47        let result = s[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9);
48        let t = s[1] << 17;
49        s[2] ^= s[0];
50        s[3] ^= s[1];
51        s[1] ^= s[2];
52        s[0] ^= s[3];
53        s[2] ^= t;
54        s[3] = s[3].rotate_left(45);
55        result
56    }
57
58    /// A uniform index in `[0, n)` by rejection (3.4.4). `n` is at least 1.
59    pub fn index(&mut self, n: u64) -> u64 {
60        assert!(n >= 1, "an index needs a non-empty range");
61        // 2^64 mod n, in u64.
62        let rem = (u64::MAX % n + 1) % n;
63        loop {
64            let u = self.next_u64();
65            if rem != 0 && u >= 0u64.wrapping_sub(rem) {
66                continue;
67            }
68            return u % n;
69        }
70    }
71}
72
73/// The SplitMix64 starting state of the stream for `label` (3.4.3): the
74/// first 8 bytes, big-endian, of the SHA-256 digest in
75/// `tagged_digest("rustev.bootstrap/1", seed || 0x00 || label)`.
76pub fn stream_seed(seed: &str, label: &str) -> u64 {
77    let mut bytes = Vec::with_capacity(seed.len() + 1 + label.len());
78    bytes.extend_from_slice(seed.as_bytes());
79    bytes.push(0);
80    bytes.extend_from_slice(label.as_bytes());
81    let digest = tagged_digest(STREAM_TAG, &bytes);
82    let hex = &digest["sha256:".len().."sha256:".len() + 16];
83    u64::from_str_radix(hex, 16).expect("a tagged digest is lowercase hex")
84}
85
86/// The stream for `label`.
87pub fn stream(seed: &str, label: &str) -> Xoshiro256StarStar {
88    Xoshiro256StarStar::from_splitmix(stream_seed(seed, label))
89}
90
91/// The seed handed to run `r` (3.4.6): the first output of the stream
92/// `run/<r>`, with `r` the run's zero-based position in decimal.
93pub fn run_seed(seed: &str, r: usize) -> u64 {
94    stream(seed, &format!("run/{r}")).next_u64()
95}