This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-runtime / src / entropy.rs
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}