Now let's take a silly one
1use std::sync::atomic::{AtomicU64, Ordering};
2
3const GOLDEN_GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
4
5fn splitmix64(z: u64) -> u64 {
6 let z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
7 let z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
8 z ^ (z >> 31)
9}
10
11pub trait Entropy: Send + Sync + 'static {
12 fn next_u64(&self) -> u64;
13 fn fill(&self, buffer: &mut [u8]);
14 fn derive(&self, label: u64) -> Box<dyn Entropy>;
15}
16
17pub struct OsEntropy;
18
19impl Entropy for OsEntropy {
20 fn next_u64(&self) -> u64 {
21 let mut bytes = [0u8; 8];
22 getrandom::fill(&mut bytes).expect("OS entropy unavailable");
23 u64::from_le_bytes(bytes)
24 }
25
26 fn fill(&self, buffer: &mut [u8]) {
27 getrandom::fill(buffer).expect("OS entropy unavailable");
28 }
29
30 fn derive(&self, _label: u64) -> Box<dyn Entropy> {
31 Box::new(OsEntropy)
32 }
33}
34
35/// A reproducible splitmix64 stream for tests and deterministic simulation.
36///
37/// Draws from one instance are reproducible only when taken sequentially: a
38/// single instance shared across concurrent tasks interleaves its atomic
39/// counter nondeterministically. To fan entropy out to concurrent consumers,
40/// give each its own substream with [`SeededEntropy::derive`], which is a pure
41/// function of the parent seed and a label and so is independent of draw
42/// timing. That keeps the whole fan-out reproducible regardless of scheduling.
43pub struct SeededEntropy {
44 seed: u64,
45 state: AtomicU64,
46}
47
48impl SeededEntropy {
49 pub fn new(seed: u64) -> Self {
50 Self {
51 seed,
52 state: AtomicU64::new(seed),
53 }
54 }
55
56 pub fn derive(&self, label: u64) -> SeededEntropy {
57 SeededEntropy::new(splitmix64(self.seed ^ splitmix64(label)))
58 }
59}
60
61impl Entropy for SeededEntropy {
62 fn next_u64(&self) -> u64 {
63 let z = self
64 .state
65 .fetch_add(GOLDEN_GAMMA, Ordering::SeqCst)
66 .wrapping_add(GOLDEN_GAMMA);
67 splitmix64(z)
68 }
69
70 fn fill(&self, buffer: &mut [u8]) {
71 buffer.chunks_mut(8).for_each(|chunk| {
72 let value = self.next_u64().to_le_bytes();
73 chunk.copy_from_slice(&value[..chunk.len()]);
74 });
75 }
76
77 fn derive(&self, label: u64) -> Box<dyn Entropy> {
78 Box::new(SeededEntropy::derive(self, label))
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 fn stream(entropy: &SeededEntropy, count: usize) -> Vec<u64> {
87 std::iter::repeat_with(|| entropy.next_u64())
88 .take(count)
89 .collect()
90 }
91
92 #[test]
93 fn seeded_entropy_is_deterministic() {
94 let one = SeededEntropy::new(42);
95 let two = SeededEntropy::new(42);
96 assert_eq!(stream(&one, 256), stream(&two, 256));
97 }
98
99 #[test]
100 fn distinct_seeds_diverge() {
101 assert_ne!(
102 stream(&SeededEntropy::new(1), 64),
103 stream(&SeededEntropy::new(2), 64)
104 );
105 }
106
107 #[test]
108 fn derive_is_independent_of_parent_draw_timing() {
109 let early = SeededEntropy::new(42);
110 let child_before = early.derive(7);
111
112 let late = SeededEntropy::new(42);
113 let _ = stream(&late, 100);
114 let child_after = late.derive(7);
115
116 assert_eq!(stream(&child_before, 128), stream(&child_after, 128));
117 }
118
119 #[test]
120 fn derived_streams_differ_by_label() {
121 let parent = SeededEntropy::new(42);
122 assert_ne!(stream(&parent.derive(1), 64), stream(&parent.derive(2), 64));
123 }
124
125 fn first_fill(entropy: &dyn Entropy, label: u64) -> [u8; 16] {
126 let mut buffer = [0u8; 16];
127 entropy.derive(label).fill(&mut buffer);
128 buffer
129 }
130
131 #[test]
132 fn trait_object_derive_is_independent_of_draw_order() {
133 let parent: &dyn Entropy = &SeededEntropy::new(77);
134 let in_order = [first_fill(parent, 10), first_fill(parent, 20)];
135
136 let parent: &dyn Entropy = &SeededEntropy::new(77);
137 let reversed = [first_fill(parent, 20), first_fill(parent, 10)];
138
139 assert_eq!(in_order[0], reversed[1]);
140 assert_eq!(in_order[1], reversed[0]);
141 assert_ne!(in_order[0], in_order[1]);
142 }
143
144 #[test]
145 fn seeded_fill_matches_stream() {
146 let stream = SeededEntropy::new(7);
147 let expected = stream.next_u64().to_le_bytes();
148 let bytes = SeededEntropy::new(7);
149 let mut buffer = [0u8; 8];
150 bytes.fill(&mut buffer);
151 assert_eq!(buffer, expected);
152 }
153
154 #[test]
155 fn os_entropy_fills() {
156 let mut buffer = [0u8; 16];
157 OsEntropy.fill(&mut buffer);
158 assert!(buffer.iter().any(|byte| *byte != 0));
159 }
160}