entropy/seed.rs
1//! Deterministic seeding helpers shared across all auxiliary probe binaries.
2//!
3//! [`splitmix64`] is the Vigna 64-bit finalizer used here only to expand a
4//! single-word seed into wider key material before constructing the RNG under
5//! test. It is **not** itself the generator being evaluated.
6//!
7//! [`seed_material`] converts a `u64` seed into an arbitrary-width byte array
8//! using [`splitmix64`]. The XOR with `0xa076_1d64_78bd_642f` (the first
9//! Weyl-sequence prime from wyhash, Wang Yi, 2019) ensures that seed = 0 does
10//! not produce the all-zeros splitmix64 state.
11//!
12//! Seed derivation is part of experimental reproducibility: having one
13//! definition, one comment, and one set of tests reduces the risk that a
14//! future bug fix lands in some probe binaries but not others.
15//!
16//! ## Cipher test keys
17//!
18//! [`K16`], [`K32`], [`IV8`], and [`IV16`] are the fixed keys and IVs used to
19//! construct cipher-based RNGs in the test harness. They are defined here
20//! once and imported wherever needed.
21//!
22//! **These constants are NOT suitable for any production use.** Sequential
23//! byte strings are present in every published test-vector corpus and would
24//! immediately compromise any real cryptographic deployment.
25
26/// One step of the Vigna splitmix64 mixer.
27///
28/// Advances `state` by one splitmix64 step and returns the mixed output word.
29pub fn splitmix64(state: &mut u64) -> u64 {
30 *state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
31 let mut z = *state;
32 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
33 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
34 z ^ (z >> 31)
35}
36
37/// Derive `N` bytes of seed material from a single 64-bit seed.
38///
39/// Expands `seed` via repeated [`splitmix64`] calls, writing 8 bytes per
40/// iteration until `N` bytes are filled (big-endian word order). The XOR
41/// with `0xa076_1d64_78bd_642f` (wyhash wyp0 prime) before the first call
42/// ensures that seed = 0 yields a non-trivial initial state.
43pub fn seed_material<const N: usize>(seed: u64) -> [u8; N] {
44 let mut state = seed ^ 0xa076_1d64_78bd_642f;
45 let mut out = [0u8; N];
46 let mut pos = 0usize;
47 while pos < N {
48 let word = splitmix64(&mut state).to_be_bytes();
49 let take = (N - pos).min(8);
50 out[pos..pos + take].copy_from_slice(&word[..take]);
51 pos += take;
52 }
53 out
54}
55
56/// Build an `N`-byte array whose elements are `0, 1, 2, …, N-1 (mod 256)`.
57///
58/// Used to construct fixed test keys for cipher-based RNGs so that the key
59/// bytes appear only once in the codebase rather than being repeated at each
60/// call site as an inline hex literal.
61///
62/// # Warning — NOT FOR PRODUCTION USE
63///
64/// Sequential byte strings `[0x00, 0x01, …, N-1]` are present in every
65/// published test-vector corpus. Any cipher initialised with these values in
66/// a real deployment would be immediately broken. Use a cryptographically
67/// secure random source for all production key material.
68pub const fn sequential_bytes<const N: usize>() -> [u8; N] {
69 let mut out = [0u8; N];
70 let mut i = 0;
71 while i < N {
72 out[i] = i as u8;
73 i += 1;
74 }
75 out
76}
77
78// ── Cipher test keys ─────────────────────────────────────────────────────────
79//
80// Defined here once; imported by main.rs, pilot_rng.rs, and any future binary
81// that instantiates cipher-based RNGs. See the module-level warning above.
82
83/// 128-bit test key: `[0x00, 0x01, …, 0x0f]`. NOT FOR PRODUCTION USE.
84pub const K16: [u8; 16] = sequential_bytes();
85/// 256-bit test key: `[0x00, 0x01, …, 0x1f]`. NOT FOR PRODUCTION USE.
86pub const K32: [u8; 32] = sequential_bytes();
87/// 64-bit test IV: `[0x00, 0x01, …, 0x07]`. NOT FOR PRODUCTION USE.
88pub const IV8: [u8; 8] = sequential_bytes();
89/// 128-bit test IV: `[0x00, 0x01, …, 0x0f]`. NOT FOR PRODUCTION USE.
90pub const IV16: [u8; 16] = sequential_bytes();
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn splitmix64_nonzero_seed_zero() {
98 // seed = 0 must not produce all-zeros output.
99 let out: [u8; 8] = seed_material(0);
100 assert!(out.iter().any(|&b| b != 0));
101 }
102
103 #[test]
104 fn splitmix64_deterministic() {
105 let mut s1 = 42u64;
106 let mut s2 = 42u64;
107 assert_eq!(splitmix64(&mut s1), splitmix64(&mut s2));
108 }
109
110 #[test]
111 fn seed_material_nonzero_for_seed_zero() {
112 let out: [u8; 32] = seed_material(0);
113 assert!(out.iter().any(|&b| b != 0));
114 }
115
116 #[test]
117 fn seed_material_deterministic() {
118 let a: [u8; 16] = seed_material(99);
119 let b: [u8; 16] = seed_material(99);
120 assert_eq!(a, b);
121 }
122
123 #[test]
124 fn sequential_bytes_correct() {
125 let b: [u8; 4] = sequential_bytes();
126 assert_eq!(b, [0x00, 0x01, 0x02, 0x03]);
127 let b16: [u8; 16] = sequential_bytes();
128 assert_eq!(b16[0], 0x00);
129 assert_eq!(b16[15], 0x0f);
130 // Verify wrapping at 256.
131 let b257: [u8; 257] = sequential_bytes();
132 assert_eq!(b257[255], 0xff);
133 assert_eq!(b257[256], 0x00);
134 }
135}