1use lib_q_k12::Kt128;
6use lib_q_k12::digest::{
7 ExtendableOutput,
8 Update,
9 XofReader,
10};
11
12pub const DOMAIN_LIBQ_DET_RNG: &[u8] = b"libQ-DET-RNG-v1";
14
15pub const DOMAIN_HPKE_RNG: &[u8] = b"HPKE-RNG";
17
18#[cfg(feature = "deterministic-saturnin")]
20pub const DOMAIN_LIBQ_DET_SATURNIN: &[u8] = b"libQ-DET-SATURNIN-v1";
21
22#[derive(Clone, Debug)]
24pub struct Kt128Expander {
25 domain: &'static [u8],
26 buffer: [u8; 32],
27 position: usize,
28 counter: u64,
29}
30
31impl Kt128Expander {
32 #[must_use]
34 pub fn from_seed_32(domain: &'static [u8], seed: [u8; 32]) -> Self {
35 Self::from_seed(domain, &seed)
36 }
37
38 #[must_use]
40 pub fn from_seed(domain: &'static [u8], seed: &[u8]) -> Self {
41 let mut k12 = Kt128::new(domain);
42 k12.update(seed);
43 let mut reader = k12.finalize_xof();
44 let mut buffer = [0u8; 32];
45 reader.read(&mut buffer);
46 Self {
47 domain,
48 buffer,
49 position: 0,
50 counter: 0,
51 }
52 }
53
54 #[must_use]
56 pub fn from_det_seed_32(seed: [u8; 32]) -> Self {
57 Self::from_seed_32(DOMAIN_LIBQ_DET_RNG, seed)
58 }
59
60 #[must_use]
62 pub fn from_det_u64(seed: u64) -> Self {
63 Self::from_seed_32(DOMAIN_LIBQ_DET_RNG, seed_32_from_u64(seed))
64 }
65
66 pub fn refill(&mut self) {
68 let mut k12 = Kt128::new(self.domain);
69 k12.update(&self.buffer);
70 k12.update(&self.counter.to_le_bytes());
71 let mut reader = k12.finalize_xof();
72 reader.read(&mut self.buffer);
73 self.counter = self.counter.wrapping_add(1);
74 self.position = 0;
75 }
76
77 pub fn fill_bytes(&mut self, dest: &mut [u8]) {
79 let mut remaining = dest.len();
80 let mut offset = 0;
81
82 while remaining > 0 {
83 if self.position >= self.buffer.len() {
84 self.refill();
85 }
86
87 let available = self.buffer.len() - self.position;
88 let to_copy = core::cmp::min(remaining, available);
89
90 dest[offset..offset + to_copy]
91 .copy_from_slice(&self.buffer[self.position..self.position + to_copy]);
92
93 self.position += to_copy;
94 offset += to_copy;
95 remaining -= to_copy;
96 }
97 }
98}
99
100#[must_use]
102pub fn seed_32_from_u64(seed: u64) -> [u8; 32] {
103 let mut out = [0u8; 32];
104 let mut s = seed;
105 for chunk in out.chunks_mut(8) {
106 s = splitmix64_step(s);
107 chunk.copy_from_slice(&s.to_le_bytes());
108 }
109 out
110}
111
112#[inline]
113fn splitmix64_step(mut s: u64) -> u64 {
114 s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
115 s = (s ^ (s >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
116 s = (s ^ (s >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
117 let out = s ^ (s >> 31);
118 if out == 0 { 1 } else { out }
119}
120
121pub const KT128_DET_GOLDEN_ZERO_SEED_64: [u8; 64] = [
123 221, 250, 174, 112, 192, 10, 162, 130, 180, 58, 67, 124, 118, 240, 140, 65, 32, 215, 8, 34,
124 140, 63, 13, 205, 241, 229, 59, 9, 57, 190, 20, 124, 197, 138, 246, 213, 80, 155, 64, 77, 70,
125 54, 191, 17, 7, 229, 73, 226, 157, 172, 235, 183, 104, 145, 73, 150, 229, 58, 50, 22, 40, 119,
126 178, 69,
127];
128
129pub const KT128_DET_GOLDEN_U64_SEED_64: [u8; 64] = [
131 252, 181, 230, 112, 248, 141, 49, 132, 104, 217, 21, 202, 22, 213, 11, 151, 255, 181, 150, 56,
132 230, 170, 210, 70, 45, 58, 246, 36, 221, 142, 143, 69, 198, 102, 112, 157, 221, 138, 218, 8,
133 136, 45, 198, 171, 31, 205, 147, 64, 120, 114, 35, 21, 207, 61, 174, 238, 179, 102, 189, 172,
134 16, 254, 132, 2,
135];
136
137#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_domain_separation_same_seed() {
146 let seed = [7u8; 32];
147 let mut det = Kt128Expander::from_seed_32(DOMAIN_LIBQ_DET_RNG, seed);
148 let mut hpke = Kt128Expander::from_seed_32(DOMAIN_HPKE_RNG, seed);
149 let mut a = [0u8; 32];
150 let mut b = [0u8; 32];
151 det.fill_bytes(&mut a);
152 hpke.fill_bytes(&mut b);
153 assert_ne!(a, b);
154 }
155
156 #[test]
157 fn test_u64_differs_from_raw_32_byte_seed() {
158 let seed_u64 = 0x0123_4567_89AB_CDEF_u64;
159 let mut from_u64 = Kt128Expander::from_det_u64(seed_u64);
160 let mut from_bytes = Kt128Expander::from_det_seed_32(seed_32_from_u64(seed_u64));
161 let mut a = [0u8; 64];
162 let mut b = [0u8; 64];
163 from_u64.fill_bytes(&mut a);
164 from_bytes.fill_bytes(&mut b);
165 assert_eq!(a, b);
166 let mut wrong = Kt128Expander::from_det_seed_32({
167 let mut s = [0u8; 32];
168 s[..8].copy_from_slice(&seed_u64.to_le_bytes());
169 s
170 });
171 let mut c = [0u8; 64];
172 wrong.fill_bytes(&mut c);
173 assert_ne!(a, c);
174 }
175
176 #[test]
177 fn test_deterministic_repeatability() {
178 let seed = [1u8; 32];
179 let mut e1 = Kt128Expander::from_det_seed_32(seed);
180 let mut e2 = Kt128Expander::from_det_seed_32(seed);
181 let mut out1 = [0u8; 128];
182 let mut out2 = [0u8; 128];
183 e1.fill_bytes(&mut out1);
184 e2.fill_bytes(&mut out2);
185 assert_eq!(out1, out2);
186 }
187
188 #[test]
190 fn test_golden_zero_seed_64_bytes() {
191 let mut expander = Kt128Expander::from_det_seed_32([0u8; 32]);
192 let mut out = [0u8; 64];
193 expander.fill_bytes(&mut out);
194 assert_eq!(out, KT128_DET_GOLDEN_ZERO_SEED_64);
195 }
196
197 #[test]
199 fn test_golden_u64_seed_64_bytes() {
200 let mut expander = Kt128Expander::from_det_u64(0x0123_4567_89AB_CDEF);
201 let mut out = [0u8; 64];
202 expander.fill_bytes(&mut out);
203 assert_eq!(out, KT128_DET_GOLDEN_U64_SEED_64);
204 }
205
206 #[test]
208 #[ignore = "manual: cargo test gen_kt128_goldens -- --ignored --nocapture -p lib-q-random"]
209 fn gen_kt128_goldens() {
210 let mut z = Kt128Expander::from_det_seed_32([0u8; 32]);
211 let mut zu = [0u8; 64];
212 z.fill_bytes(&mut zu);
213 let mut u = Kt128Expander::from_det_u64(0x0123_4567_89AB_CDEF);
214 let mut uu = [0u8; 64];
215 u.fill_bytes(&mut uu);
216 println!("zero_seed_64 = {zu:?}");
217 println!("u64_seed_64 = {uu:?}");
218 }
219}