chacha.rs

13.3 kB · rust · 402 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 a uniform double in the half-open unit interval.69    pub fn unit(&mut self) -> f64 {70        let scale = 1.0 / ((1u64 << 53) as f64);71        scale * ((self.next_u64() >> 11) as f64)72    }7374    /// Returns a fair coin flip.75    pub fn boolean(&mut self) -> bool {76        (self.next_u32() as i32) < 077    }7879    /// Returns an unbiased uniform integer between low and high inclusive.80    pub fn range_i64(&mut self, low: i64, high: i64) -> i64 {81        assert!(low <= high, "range_i64: low > high");82        let range = high.wrapping_sub(low).wrapping_add(1) as u64;83        if range == 0 {84            return self.next_u64() as i64;85        }86        let zone = (range << range.leading_zeros()).wrapping_sub(1);87        loop {88            let (hi, lo) = wide_mul_u64(self.next_u64(), range);89            if lo <= zone {90                return low.wrapping_add(hi as i64);91            }92        }93    }9495    /// Returns an unbiased uniform integer below n.96    pub fn below_u64(&mut self, n: u64) -> u64 {97        assert!(n > 0, "below_u64: empty range");98        let zone = (n << n.leading_zeros()).wrapping_sub(1);99        loop {100            let (hi, lo) = wide_mul_u64(self.next_u64(), n);101            if lo <= zone {102                return hi;103            }104        }105    }106107    /// Shuffles the slice in place by Fisher-Yates.108    pub fn shuffle<T>(&mut self, seq: &mut [T]) {109        for i in (1..seq.len()).rev() {110            let j = self.index_below(i + 1);111            seq.swap(i, j);112        }113    }114115    /// Draws amount distinct indices below length, choosing its strategy by the sizes.116    pub fn sample_indices(&mut self, length: usize, amount: usize) -> Vec<usize> {117        assert!(amount <= length, "sample_indices: amount > length");118        if length > u32::MAX as usize {119            return self.sample_rejection_u64(length as u64, amount as u64);120        }121        let length = length as u32;122        let amount = amount as u32;123        if amount < 163 {124            const C: [[f32; 2]; 2] = [[1.6, 8.0 / 45.0], [10.0, 70.0 / 9.0]];125            let j = if length < 500_000 { 0 } else { 1 };126            let amount_fp = amount as f32;127            let m4 = C[0][j] * amount_fp;128            if amount > 11 && (length as f32) < (C[1][j] + m4) * amount_fp {129                self.sample_inplace(length, amount)130            } else {131                self.sample_floyd(length, amount)132            }133        } else {134            const C: [f32; 2] = [270.0, 330.0 / 9.0];135            let j = if length < 500_000 { 0 } else { 1 };136            if (length as f32) < C[j] * (amount as f32) {137                self.sample_inplace(length, amount)138            } else {139                self.sample_rejection_u32(length, amount)140            }141        }142    }143144    fn index_below(&mut self, ubound: usize) -> usize {145        if ubound <= u32::MAX as usize {146            self.range_u32(0, ubound as u32 - 1) as usize147        } else {148            self.below_u64(ubound as u64) as usize149        }150    }151152    fn range_u32(&mut self, low: u32, high: u32) -> u32 {153        let range = high.wrapping_sub(low).wrapping_add(1);154        if range == 0 {155            return self.next_u32();156        }157        let zone = (range << range.leading_zeros()).wrapping_sub(1);158        loop {159            let (hi, lo) = wide_mul_u32(self.next_u32(), range);160            if lo <= zone {161                return low.wrapping_add(hi);162            }163        }164    }165166    fn sample_floyd(&mut self, length: u32, amount: u32) -> Vec<usize> {167        let floyd_shuffle = amount < 50;168        let mut indices: Vec<u32> = Vec::with_capacity(amount as usize);169        for j in length - amount..length {170            let t = self.range_u32(0, j);171            if floyd_shuffle {172                if let Some(pos) = indices.iter().position(|&x| x == t) {173                    indices.insert(pos, j);174                    continue;175                }176            } else if indices.contains(&t) {177                indices.push(j);178                continue;179            }180            indices.push(t);181        }182        if !floyd_shuffle {183            for i in (1..amount).rev() {184                let j = self.range_u32(0, i);185                indices.swap(i as usize, j as usize);186            }187        }188        indices.into_iter().map(|i| i as usize).collect()189    }190191    fn sample_inplace(&mut self, length: u32, amount: u32) -> Vec<usize> {192        let mut indices: Vec<u32> = (0..length).collect();193        for i in 0..amount {194            let j = self.range_u32(i, length - 1);195            indices.swap(i as usize, j as usize);196        }197        indices.truncate(amount as usize);198        indices.into_iter().map(|i| i as usize).collect()199    }200201    fn sample_rejection_u32(&mut self, length: u32, amount: u32) -> Vec<usize> {202        let ints_to_reject = (u32::MAX - length + 1) % length;203        let zone = u32::MAX - ints_to_reject;204        let mut cache = HashSet::with_capacity(amount as usize);205        let mut indices = Vec::with_capacity(amount as usize);206        for _ in 0..amount {207            let mut pos = self.distr_u32(length, zone);208            while !cache.insert(pos) {209                pos = self.distr_u32(length, zone);210            }211            indices.push(pos as usize);212        }213        indices214    }215216    fn sample_rejection_u64(&mut self, length: u64, amount: u64) -> Vec<usize> {217        let ints_to_reject = (u64::MAX - length + 1) % length;218        let zone = u64::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_u64(length, zone);223            while !cache.insert(pos) {224                pos = self.distr_u64(length, zone);225            }226            indices.push(pos as usize);227        }228        indices229    }230231    fn distr_u32(&mut self, range: u32, zone: u32) -> u32 {232        loop {233            let (hi, lo) = wide_mul_u32(self.next_u32(), range);234            if lo <= zone {235                return hi;236            }237        }238    }239240    fn distr_u64(&mut self, range: u64, zone: u64) -> u64 {241        loop {242            let (hi, lo) = wide_mul_u64(self.next_u64(), range);243            if lo <= zone {244                return hi;245            }246        }247    }248249    fn generate_and_set(&mut self, index: usize) {250        self.generate();251        self.index = index;252    }253254    fn generate(&mut self) {255        for block in 0..4 {256            let counter = self.block_pos.wrapping_add(block as u64);257            let initial = [258                CONSTANTS[0],259                CONSTANTS[1],260                CONSTANTS[2],261                CONSTANTS[3],262                self.key[0],263                self.key[1],264                self.key[2],265                self.key[3],266                self.key[4],267                self.key[5],268                self.key[6],269                self.key[7],270                counter as u32,271                (counter >> 32) as u32,272                0,273                0,274            ];275            let mut state = initial;276            for _ in 0..4 {277                quarter(&mut state, 0, 4, 8, 12);278                quarter(&mut state, 1, 5, 9, 13);279                quarter(&mut state, 2, 6, 10, 14);280                quarter(&mut state, 3, 7, 11, 15);281                quarter(&mut state, 0, 5, 10, 15);282                quarter(&mut state, 1, 6, 11, 12);283                quarter(&mut state, 2, 7, 8, 13);284                quarter(&mut state, 3, 4, 9, 14);285            }286            let out = &mut self.buf[block * BLOCK_WORDS..(block + 1) * BLOCK_WORDS];287            for ((slot, word), start) in out.iter_mut().zip(state).zip(initial) {288                *slot = word.wrapping_add(start);289            }290        }291        self.block_pos = self.block_pos.wrapping_add(4);292    }293}294295fn quarter(s: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {296    s[a] = s[a].wrapping_add(s[b]);297    s[d] = (s[d] ^ s[a]).rotate_left(16);298    s[c] = s[c].wrapping_add(s[d]);299    s[b] = (s[b] ^ s[c]).rotate_left(12);300    s[a] = s[a].wrapping_add(s[b]);301    s[d] = (s[d] ^ s[a]).rotate_left(8);302    s[c] = s[c].wrapping_add(s[d]);303    s[b] = (s[b] ^ s[c]).rotate_left(7);304}305306fn pcg32(state: &mut u64) -> [u8; 4] {307    const MUL: u64 = 6364136223846793005;308    const INC: u64 = 11634580027462260723;309    *state = state.wrapping_mul(MUL).wrapping_add(INC);310    let s = *state;311    let xorshifted = (((s >> 18) ^ s) >> 27) as u32;312    let rot = (s >> 59) as u32;313    xorshifted.rotate_right(rot).to_le_bytes()314}315316fn wide_mul_u64(a: u64, b: u64) -> (u64, u64) {317    let t = u128::from(a) * u128::from(b);318    ((t >> 64) as u64, t as u64)319}320321fn wide_mul_u32(a: u32, b: u32) -> (u32, u32) {322    let t = u64::from(a) * u64::from(b);323    ((t >> 32) as u32, t as u32)324}325326#[cfg(test)]327mod tests {328    use super::*;329330    #[test]331    fn words_match_rand_chacha() {332        let mut c = ChaCha8::from_u64(7);333        let words: Vec<u32> = (0..8).map(|_| c.next_u32()).collect();334        assert_eq!(335            words,336            [337                601310139, 677729076, 781920570, 721508819, 1160025393, 3024842937, 155073251,338                3121330102339            ]340        );341        let doubles: Vec<u64> = (0..4).map(|_| c.next_u64()).collect();342        assert_eq!(343            doubles,344            [345                11091271176959810440,346                6629102542470643238,347                1532177833826564251,348                15666632647712377470349            ]350        );351        let mut z = ChaCha8::from_u64(0);352        let zwords: Vec<u32> = (0..8).map(|_| z.next_u32()).collect();353        assert_eq!(354            zwords,355            [356                2811902828, 3045455719, 3134767159, 2001118559, 2179114726, 3002797362, 2409334908,357                258433188358            ]359        );360    }361362    #[test]363    fn distributions_match_rand() {364        let mut c = ChaCha8::from_u64(7);365        let units: Vec<u64> = (0..4).map(|_| c.unit().to_bits()).collect();366        assert_eq!(367            units,368            [369                4594853223840476064,370                4595220474943196564,371                4604518774960858258,372                4604721123211421639373            ]374        );375        let bools: Vec<bool> = (0..8).map(|_| c.boolean()).collect();376        assert_eq!(bools, [false, true, true, false, false, false, false, true]);377        let ranges: Vec<i64> = (0..8).map(|_| c.range_i64(-1000, 1000)).collect();378        assert_eq!(ranges, [-271, 980, -600, -232, 43, -486, -147, 53]);379        let belows: Vec<u64> = (0..8).map(|_| c.below_u64(97)).collect();380        assert_eq!(belows, [29, 7, 31, 21, 42, 79, 2, 7]);381    }382383    #[test]384    fn sequences_match_rand() {385        let mut s = ChaCha8::from_u64(7);386        let mut perm: Vec<usize> = (0..16).collect();387        s.shuffle(&mut perm);388        assert_eq!(perm, [11, 4, 9, 8, 10, 1, 12, 6, 5, 7, 0, 3, 13, 14, 15, 2]);389        let floyd = ChaCha8::from_u64(7).sample_indices(100, 5);390        assert_eq!(floyd, [13, 15, 16, 26, 70]);391        let inplace = ChaCha8::from_u64(7).sample_indices(100, 20);392        assert_eq!(393            inplace,394            [14, 16, 18, 29, 71, 8, 74, 13, 63, 41, 17, 86, 43, 99, 53, 32, 48, 62, 91, 0]395        );396        let rejection = ChaCha8::from_u64(7).sample_indices(100_000, 163);397        assert_eq!(398            &rejection[..10],399            [14000, 15779, 18205, 16798, 27008, 70427, 3610, 72674, 7116, 60125]400        );401    }402}