chacha.rs
14.4 kB · rust · 433 lines
1use std::collections::HashSet;23const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];4const BUF_WORDS: usize = 64;5const BLOCK_WORDS: usize = 16;67/// An eight-round ChaCha keystream serving as a seekable source of random words.8#[derive(Clone)]9pub struct ChaCha8 {10 key: [u32; 8],11 block_pos: u64,12 buf: [u32; BUF_WORDS],13 index: usize,14}1516impl ChaCha8 {17 /// Builds the generator from a 32-byte key.18 pub fn from_seed(seed: [u8; 32]) -> ChaCha8 {19 let mut key = [0u32; 8];20 for (word, chunk) in key.iter_mut().zip(seed.chunks_exact(4)) {21 *word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);22 }23 ChaCha8 {24 key,25 block_pos: 0,26 buf: [0; BUF_WORDS],27 index: BUF_WORDS,28 }29 }3031 /// Builds the generator by stretching a word seed into a full key.32 pub fn from_u64(seed: u64) -> ChaCha8 {33 let mut state = seed;34 let mut bytes = [0u8; 32];35 for chunk in bytes.chunks_exact_mut(4) {36 chunk.copy_from_slice(&pcg32(&mut state));37 }38 ChaCha8::from_seed(bytes)39 }4041 /// Returns the next 32-bit word of the keystream.42 pub fn next_u32(&mut self) -> u32 {43 if self.index >= BUF_WORDS {44 self.generate_and_set(0);45 }46 let value = self.buf[self.index];47 self.index += 1;48 value49 }5051 /// Returns the next two keystream words joined into 64 bits, low word first.52 pub fn next_u64(&mut self) -> u64 {53 let index = self.index;54 if index < BUF_WORDS - 1 {55 self.index += 2;56 (u64::from(self.buf[index + 1]) << 32) | u64::from(self.buf[index])57 } else if index >= BUF_WORDS {58 self.generate_and_set(2);59 (u64::from(self.buf[1]) << 32) | u64::from(self.buf[0])60 } else {61 let x = u64::from(self.buf[BUF_WORDS - 1]);62 self.generate_and_set(1);63 let y = u64::from(self.buf[0]);64 (y << 32) | x65 }66 }6768 /// Returns the absolute word position in the keystream.69 pub fn word_pos(&self) -> u128 {70 let buf_start_block = self.block_pos.wrapping_sub(4);71 let blocks_part = (self.index / BLOCK_WORDS) as u64;72 let words_part = (self.index % BLOCK_WORDS) as u64;73 let pos_block = buf_start_block.wrapping_add(blocks_part);74 u128::from(pos_block) * BLOCK_WORDS as u128 + u128::from(words_part)75 }7677 /// Seeks the keystream to an absolute word position.78 pub fn set_word_pos(&mut self, word_offset: u128) {79 self.block_pos = (word_offset / BLOCK_WORDS as u128) as u64;80 self.generate_and_set((word_offset % BLOCK_WORDS as u128) as usize);81 }8283 /// Returns a uniform double in the half-open unit interval.84 pub fn unit(&mut self) -> f64 {85 let scale = 1.0 / ((1u64 << 53) as f64);86 scale * ((self.next_u64() >> 11) as f64)87 }8889 /// Returns a fair coin flip.90 pub fn boolean(&mut self) -> bool {91 (self.next_u32() as i32) < 092 }9394 /// Returns an unbiased uniform integer between low and high inclusive.95 pub fn range_i64(&mut self, low: i64, high: i64) -> i64 {96 assert!(low <= high, "range_i64: low > high");97 let range = high.wrapping_sub(low).wrapping_add(1) as u64;98 if range == 0 {99 return self.next_u64() as i64;100 }101 let zone = (range << range.leading_zeros()).wrapping_sub(1);102 loop {103 let (hi, lo) = wide_mul_u64(self.next_u64(), range);104 if lo <= zone {105 return low.wrapping_add(hi as i64);106 }107 }108 }109110 /// Returns an unbiased uniform integer below n.111 pub fn below_u64(&mut self, n: u64) -> u64 {112 assert!(n > 0, "below_u64: empty range");113 let zone = (n << n.leading_zeros()).wrapping_sub(1);114 loop {115 let (hi, lo) = wide_mul_u64(self.next_u64(), n);116 if lo <= zone {117 return hi;118 }119 }120 }121122 /// Shuffles the slice in place by Fisher-Yates.123 pub fn shuffle<T>(&mut self, seq: &mut [T]) {124 for i in (1..seq.len()).rev() {125 let j = self.index_below(i + 1);126 seq.swap(i, j);127 }128 }129130 /// Draws amount distinct indices below length, choosing its strategy by the sizes.131 pub fn sample_indices(&mut self, length: usize, amount: usize) -> Vec<usize> {132 assert!(amount <= length, "sample_indices: amount > length");133 if length > u32::MAX as usize {134 return self.sample_rejection_u64(length as u64, amount as u64);135 }136 let length = length as u32;137 let amount = amount as u32;138 if amount < 163 {139 const C: [[f32; 2]; 2] = [[1.6, 8.0 / 45.0], [10.0, 70.0 / 9.0]];140 let j = if length < 500_000 { 0 } else { 1 };141 let amount_fp = amount as f32;142 let m4 = C[0][j] * amount_fp;143 if amount > 11 && (length as f32) < (C[1][j] + m4) * amount_fp {144 self.sample_inplace(length, amount)145 } else {146 self.sample_floyd(length, amount)147 }148 } else {149 const C: [f32; 2] = [270.0, 330.0 / 9.0];150 let j = if length < 500_000 { 0 } else { 1 };151 if (length as f32) < C[j] * (amount as f32) {152 self.sample_inplace(length, amount)153 } else {154 self.sample_rejection_u32(length, amount)155 }156 }157 }158159 fn index_below(&mut self, ubound: usize) -> usize {160 if ubound <= u32::MAX as usize {161 self.range_u32(0, ubound as u32 - 1) as usize162 } else {163 self.below_u64(ubound as u64) as usize164 }165 }166167 fn range_u32(&mut self, low: u32, high: u32) -> u32 {168 let range = high.wrapping_sub(low).wrapping_add(1);169 if range == 0 {170 return self.next_u32();171 }172 let zone = (range << range.leading_zeros()).wrapping_sub(1);173 loop {174 let (hi, lo) = wide_mul_u32(self.next_u32(), range);175 if lo <= zone {176 return low.wrapping_add(hi);177 }178 }179 }180181 fn sample_floyd(&mut self, length: u32, amount: u32) -> Vec<usize> {182 let floyd_shuffle = amount < 50;183 let mut indices: Vec<u32> = Vec::with_capacity(amount as usize);184 for j in length - amount..length {185 let t = self.range_u32(0, j);186 if floyd_shuffle {187 if let Some(pos) = indices.iter().position(|&x| x == t) {188 indices.insert(pos, j);189 continue;190 }191 } else if indices.contains(&t) {192 indices.push(j);193 continue;194 }195 indices.push(t);196 }197 if !floyd_shuffle {198 for i in (1..amount).rev() {199 let j = self.range_u32(0, i);200 indices.swap(i as usize, j as usize);201 }202 }203 indices.into_iter().map(|i| i as usize).collect()204 }205206 fn sample_inplace(&mut self, length: u32, amount: u32) -> Vec<usize> {207 let mut indices: Vec<u32> = (0..length).collect();208 for i in 0..amount {209 let j = self.range_u32(i, length - 1);210 indices.swap(i as usize, j as usize);211 }212 indices.truncate(amount as usize);213 indices.into_iter().map(|i| i as usize).collect()214 }215216 fn sample_rejection_u32(&mut self, length: u32, amount: u32) -> Vec<usize> {217 let ints_to_reject = (u32::MAX - length + 1) % length;218 let zone = u32::MAX - ints_to_reject;219 let mut cache = HashSet::with_capacity(amount as usize);220 let mut indices = Vec::with_capacity(amount as usize);221 for _ in 0..amount {222 let mut pos = self.distr_u32(length, zone);223 while !cache.insert(pos) {224 pos = self.distr_u32(length, zone);225 }226 indices.push(pos as usize);227 }228 indices229 }230231 fn sample_rejection_u64(&mut self, length: u64, amount: u64) -> Vec<usize> {232 let ints_to_reject = (u64::MAX - length + 1) % length;233 let zone = u64::MAX - ints_to_reject;234 let mut cache = HashSet::with_capacity(amount as usize);235 let mut indices = Vec::with_capacity(amount as usize);236 for _ in 0..amount {237 let mut pos = self.distr_u64(length, zone);238 while !cache.insert(pos) {239 pos = self.distr_u64(length, zone);240 }241 indices.push(pos as usize);242 }243 indices244 }245246 fn distr_u32(&mut self, range: u32, zone: u32) -> u32 {247 loop {248 let (hi, lo) = wide_mul_u32(self.next_u32(), range);249 if lo <= zone {250 return hi;251 }252 }253 }254255 fn distr_u64(&mut self, range: u64, zone: u64) -> u64 {256 loop {257 let (hi, lo) = wide_mul_u64(self.next_u64(), range);258 if lo <= zone {259 return hi;260 }261 }262 }263264 fn generate_and_set(&mut self, index: usize) {265 self.generate();266 self.index = index;267 }268269 fn generate(&mut self) {270 for block in 0..4 {271 let counter = self.block_pos.wrapping_add(block as u64);272 let initial = [273 CONSTANTS[0],274 CONSTANTS[1],275 CONSTANTS[2],276 CONSTANTS[3],277 self.key[0],278 self.key[1],279 self.key[2],280 self.key[3],281 self.key[4],282 self.key[5],283 self.key[6],284 self.key[7],285 counter as u32,286 (counter >> 32) as u32,287 0,288 0,289 ];290 let mut state = initial;291 for _ in 0..4 {292 quarter(&mut state, 0, 4, 8, 12);293 quarter(&mut state, 1, 5, 9, 13);294 quarter(&mut state, 2, 6, 10, 14);295 quarter(&mut state, 3, 7, 11, 15);296 quarter(&mut state, 0, 5, 10, 15);297 quarter(&mut state, 1, 6, 11, 12);298 quarter(&mut state, 2, 7, 8, 13);299 quarter(&mut state, 3, 4, 9, 14);300 }301 let out = &mut self.buf[block * BLOCK_WORDS..(block + 1) * BLOCK_WORDS];302 for ((slot, word), start) in out.iter_mut().zip(state).zip(initial) {303 *slot = word.wrapping_add(start);304 }305 }306 self.block_pos = self.block_pos.wrapping_add(4);307 }308}309310fn quarter(s: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {311 s[a] = s[a].wrapping_add(s[b]);312 s[d] = (s[d] ^ s[a]).rotate_left(16);313 s[c] = s[c].wrapping_add(s[d]);314 s[b] = (s[b] ^ s[c]).rotate_left(12);315 s[a] = s[a].wrapping_add(s[b]);316 s[d] = (s[d] ^ s[a]).rotate_left(8);317 s[c] = s[c].wrapping_add(s[d]);318 s[b] = (s[b] ^ s[c]).rotate_left(7);319}320321fn pcg32(state: &mut u64) -> [u8; 4] {322 const MUL: u64 = 6364136223846793005;323 const INC: u64 = 11634580027462260723;324 *state = state.wrapping_mul(MUL).wrapping_add(INC);325 let s = *state;326 let xorshifted = (((s >> 18) ^ s) >> 27) as u32;327 let rot = (s >> 59) as u32;328 xorshifted.rotate_right(rot).to_le_bytes()329}330331fn wide_mul_u64(a: u64, b: u64) -> (u64, u64) {332 let t = u128::from(a) * u128::from(b);333 ((t >> 64) as u64, t as u64)334}335336fn wide_mul_u32(a: u32, b: u32) -> (u32, u32) {337 let t = u64::from(a) * u64::from(b);338 ((t >> 32) as u32, t as u32)339}340341#[cfg(test)]342mod tests {343 use super::*;344345 #[test]346 fn words_match_rand_chacha() {347 let mut c = ChaCha8::from_u64(7);348 let words: Vec<u32> = (0..8).map(|_| c.next_u32()).collect();349 assert_eq!(350 words,351 [352 601310139, 677729076, 781920570, 721508819, 1160025393, 3024842937, 155073251,353 3121330102354 ]355 );356 let doubles: Vec<u64> = (0..4).map(|_| c.next_u64()).collect();357 assert_eq!(358 doubles,359 [360 11091271176959810440,361 6629102542470643238,362 1532177833826564251,363 15666632647712377470364 ]365 );366 let mut z = ChaCha8::from_u64(0);367 let zwords: Vec<u32> = (0..8).map(|_| z.next_u32()).collect();368 assert_eq!(369 zwords,370 [371 2811902828, 3045455719, 3134767159, 2001118559, 2179114726, 3002797362, 2409334908,372 258433188373 ]374 );375 }376377 #[test]378 fn distributions_match_rand() {379 let mut c = ChaCha8::from_u64(7);380 let units: Vec<u64> = (0..4).map(|_| c.unit().to_bits()).collect();381 assert_eq!(382 units,383 [384 4594853223840476064,385 4595220474943196564,386 4604518774960858258,387 4604721123211421639388 ]389 );390 let bools: Vec<bool> = (0..8).map(|_| c.boolean()).collect();391 assert_eq!(bools, [false, true, true, false, false, false, false, true]);392 let ranges: Vec<i64> = (0..8).map(|_| c.range_i64(-1000, 1000)).collect();393 assert_eq!(ranges, [-271, 980, -600, -232, 43, -486, -147, 53]);394 let belows: Vec<u64> = (0..8).map(|_| c.below_u64(97)).collect();395 assert_eq!(belows, [29, 7, 31, 21, 42, 79, 2, 7]);396 }397398 #[test]399 fn sequences_match_rand() {400 let mut s = ChaCha8::from_u64(7);401 let mut perm: Vec<usize> = (0..16).collect();402 s.shuffle(&mut perm);403 assert_eq!(perm, [11, 4, 9, 8, 10, 1, 12, 6, 5, 7, 0, 3, 13, 14, 15, 2]);404 let floyd = ChaCha8::from_u64(7).sample_indices(100, 5);405 assert_eq!(floyd, [13, 15, 16, 26, 70]);406 let inplace = ChaCha8::from_u64(7).sample_indices(100, 20);407 assert_eq!(408 inplace,409 [14, 16, 18, 29, 71, 8, 74, 13, 63, 41, 17, 86, 43, 99, 53, 32, 48, 62, 91, 0]410 );411 let rejection = ChaCha8::from_u64(7).sample_indices(100_000, 163);412 assert_eq!(413 &rejection[..10],414 [14000, 15779, 18205, 16798, 27008, 70427, 3610, 72674, 7116, 60125]415 );416 }417418 #[test]419 fn word_pos_seeks_the_stream() {420 let mut c = ChaCha8::from_u64(7);421 assert_eq!(c.word_pos(), 0);422 for _ in 0..70 {423 c.next_u32();424 }425 assert_eq!(c.word_pos(), 70);426 c.next_u64();427 assert_eq!(c.word_pos(), 72);428 c.set_word_pos(5);429 let a = c.next_u64();430 c.set_word_pos(5);431 assert_eq!(c.next_u64(), a);432 }433}