This repository has no description
3.9 kB
146 lines
1use std::sync::atomic::{AtomicU64, Ordering};
2
3// Golden gamma gem alert
4const GOLDEN_GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
5
6fn splitmix64(z: u64) -> u64 {
7 let z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
8 let z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
9 z ^ (z >> 31)
10}
11
12pub trait Entropy: Send + Sync + 'static {
13 fn next_u64(&self) -> u64;
14 fn fill(&self, buffer: &mut [u8]);
15 fn derive(&self, label: u64) -> Box<dyn Entropy>;
16}
17
18pub struct OsEntropy;
19
20impl Entropy for OsEntropy {
21 fn next_u64(&self) -> u64 {
22 let mut bytes = [0u8; 8];
23 getrandom::fill(&mut bytes).expect("OS entropy unavailable");
24 u64::from_le_bytes(bytes)
25 }
26
27 fn fill(&self, buffer: &mut [u8]) {
28 getrandom::fill(buffer).expect("OS entropy unavailable");
29 }
30
31 fn derive(&self, _label: u64) -> Box<dyn Entropy> {
32 Box::new(OsEntropy)
33 }
34}
35
36pub struct SeededEntropy {
37 seed: u64,
38 state: AtomicU64,
39}
40
41impl SeededEntropy {
42 pub fn new(seed: u64) -> Self {
43 Self {
44 seed,
45 state: AtomicU64::new(seed),
46 }
47 }
48
49 pub fn derive(&self, label: u64) -> SeededEntropy {
50 SeededEntropy::new(splitmix64(self.seed ^ splitmix64(label)))
51 }
52}
53
54impl Entropy for SeededEntropy {
55 fn next_u64(&self) -> u64 {
56 let z = self
57 .state
58 .fetch_add(GOLDEN_GAMMA, Ordering::SeqCst)
59 .wrapping_add(GOLDEN_GAMMA);
60 splitmix64(z)
61 }
62
63 fn fill(&self, buffer: &mut [u8]) {
64 buffer.chunks_mut(8).for_each(|chunk| {
65 let value = self.next_u64().to_le_bytes();
66 chunk.copy_from_slice(&value[..chunk.len()]);
67 });
68 }
69
70 fn derive(&self, label: u64) -> Box<dyn Entropy> {
71 Box::new(SeededEntropy::derive(self, label))
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn stream(entropy: &SeededEntropy, count: usize) -> Vec<u64> {
80 std::iter::repeat_with(|| entropy.next_u64())
81 .take(count)
82 .collect()
83 }
84
85 #[test]
86 fn seeded_entropy_is_deterministic() {
87 let one = SeededEntropy::new(42);
88 let two = SeededEntropy::new(42);
89 assert_eq!(stream(&one, 256), stream(&two, 256));
90 }
91
92 #[test]
93 fn distinct_seeds_diverge() {
94 assert_ne!(
95 stream(&SeededEntropy::new(1), 64),
96 stream(&SeededEntropy::new(2), 64)
97 );
98 }
99
100 #[test]
101 fn derive_is_independent_of_parent_draw_timing() {
102 let early = SeededEntropy::new(42);
103 let child_before = early.derive(7);
104
105 let late = SeededEntropy::new(42);
106 let _ = stream(&late, 100);
107 let child_after = late.derive(7);
108
109 assert_eq!(stream(&child_before, 128), stream(&child_after, 128));
110 }
111
112 #[test]
113 fn derived_streams_differ_by_label() {
114 let parent = SeededEntropy::new(42);
115 assert_ne!(stream(&parent.derive(1), 64), stream(&parent.derive(2), 64));
116 }
117
118 fn first_fill(entropy: &dyn Entropy, label: u64) -> [u8; 16] {
119 let mut buffer = [0u8; 16];
120 entropy.derive(label).fill(&mut buffer);
121 buffer
122 }
123
124 #[test]
125 fn trait_object_derive_is_independent_of_draw_order() {
126 let parent: &dyn Entropy = &SeededEntropy::new(77);
127 let in_order = [first_fill(parent, 10), first_fill(parent, 20)];
128
129 let parent: &dyn Entropy = &SeededEntropy::new(77);
130 let reversed = [first_fill(parent, 20), first_fill(parent, 10)];
131
132 assert_eq!(in_order[0], reversed[1]);
133 assert_eq!(in_order[1], reversed[0]);
134 assert_ne!(in_order[0], in_order[1]);
135 }
136
137 #[test]
138 fn seeded_fill_matches_stream() {
139 let stream = SeededEntropy::new(7);
140 let expected = stream.next_u64().to_le_bytes();
141 let bytes = SeededEntropy::new(7);
142 let mut buffer = [0u8; 8];
143 bytes.fill(&mut buffer);
144 assert_eq!(buffer, expected);
145 }
146}