perfect_rand/
lib.rs

1//! A port of the Blackrock cipher used in [Masscan](https://github.com/robertdavidgraham/masscan) to Rust.
2//!
3//! Its original purpose is efficiently randomizing the order of port scans
4//! without having to put every possible target in memory and shuffling.
5//!
6//! [Original code](https://github.com/robertdavidgraham/masscan/blob/master/src/crypto-blackrock2.c).
7//!
8//! The DES S-boxes have been replaced with the SipHash round function.
9//!
10//! # Example
11//!
12//! ```
13//! //! Print 10 random IPv4 addresses.
14//!
15//! # use std::net::Ipv4Addr;
16//! # use perfect_rand::PerfectRng;
17//!
18//! let randomizer = PerfectRng::from_range(2u64.pow(32));
19//! for i in 0..10 {
20//!     let randomized_ip = Ipv4Addr::from(randomizer.shuffle(i) as u32);
21//!     println!("{randomized_ip:?}");
22//! }
23//! ```
24
25#[derive(Default, Debug)]
26pub struct PerfectRng {
27    range: u64,
28    seed: u64,
29    rounds: usize,
30    a_bits: u32,
31    a_mask: u64,
32    b_mask: u64,
33}
34
35#[derive(Default, Debug)]
36pub struct PerfectRng32 {
37    range: u32,
38    seed: u32,
39    rounds: usize,
40    a_bits: u32,
41    a_mask: u32,
42    b_mask: u32,
43}
44
45fn count_bits(num: u64) -> u32 {
46    let mut bits = 0;
47    while (num >> bits) != 0 {
48        bits += 1;
49    }
50    bits
51}
52
53#[inline]
54fn sipround(mut v0: u64, mut v1: u64, mut v2: u64, mut v3: u64) -> (u64, u64, u64, u64) {
55    v0 = v0.wrapping_add(v1);
56    v2 = v2.wrapping_add(v3);
57    v1 = v1.rotate_left(13) ^ v0;
58    v3 = v3.rotate_left(16) ^ v2;
59    v0 = v0.rotate_left(32);
60
61    v2 = v2.wrapping_add(v1);
62    v0 = v0.wrapping_add(v3);
63    v1 = v1.rotate_left(17) ^ v2;
64    v3 = v3.rotate_left(21) ^ v0;
65    v2 = v2.rotate_left(32);
66
67    (v0, v1, v2, v3)
68}
69
70#[inline]
71fn sipround32(mut v0: u32, mut v1: u32, mut v2: u32, mut v3: u32) -> (u32, u32, u32, u32) {
72    v0 = v0.wrapping_add(v1);
73    v2 = v2.wrapping_add(v3);
74    v1 = v1.rotate_left(5) ^ v0;
75    v3 = v3.rotate_left(8) ^ v2;
76    v0 = v0.rotate_left(16);
77
78    v2 = v2.wrapping_add(v1);
79    v0 = v0.wrapping_add(v3);
80    v1 = v1.rotate_left(13) ^ v2;
81    v3 = v3.rotate_left(17) ^ v0;
82    v2 = v2.rotate_left(16);
83
84    (v0, v1, v2, v3)
85}
86
87impl PerfectRng {
88    /// Create a new perfect cipher with a specific range, seed, and rounds.
89    /// Use [`PerfectRng::from_range`] to use the default seed and rounds.
90    ///
91    /// - `range`: The number of possible values. In other words, the highest
92    ///   value you will try to shuffle. For example, this would be 2**32-1 for
93    ///   an IPv4 address.
94    /// - `seed`: The seed used for randomization.
95    /// - `rounds`: The amount of times the randomization is done, to make it
96    ///   more random. Recommended value is either 3 or 4, depending on your
97    ///   performance/quality needs.
98    ///
99    /// ```
100    /// # use perfect_rand::PerfectRng;
101    /// let perfect_rng = PerfectRng::new(10, rand::random(), 4);
102    /// ```
103    #[must_use]
104    #[inline]
105    pub fn new(range: u64, seed: u64, rounds: usize) -> Self {
106        assert_ne!(range, 0);
107
108        let bits = count_bits(range - 1);
109        let b = bits / 2;
110        // if an odd number of bits, a gets the leftover bit
111        let a = bits - b;
112
113        PerfectRng {
114            range,
115            seed,
116            rounds,
117            a_bits: a,
118            a_mask: (1 << a) - 1,
119            b_mask: (1 << b) - 1,
120        }
121    }
122
123    /// Create a new `PerfectRng` with a random seed and default rounds.
124    ///
125    /// ```
126    /// # use perfect_rand::PerfectRng;
127    /// let perfect_rng = PerfectRng::from_range(2u64.pow(32));
128    /// ```
129    #[must_use]
130    pub fn from_range(range: u64) -> Self {
131        Self::new(range, rand::random(), 4)
132    }
133
134    #[inline]
135    fn round(&self, j: usize, right: u64) -> u64 {
136        let v0 = self.seed;
137        let v1 = j as u64;
138        let v2 = right;
139        // all zeroes will lead to an all-zero output,
140        // this adds some randomness for that case.
141        let v3: u64 = 0xf3016d19bc9ad940;
142
143        let (v0, v1, v2, v3) = sipround(v0, v1, v2, v3);
144        let (v0, v1, v2, v3) = sipround(v0, v1, v2, v3);
145        let (v0, v1, v2, v3) = sipround(v0, v1, v2, v3);
146        let (v0, _, _, _) = sipround(v0, v1, v2, v3);
147
148        v0
149    }
150
151    #[inline]
152    fn encrypt(&self, m: u64) -> u64 {
153        let mut left = m & self.a_mask;
154        let mut right = m >> self.a_bits;
155
156        let mut j = 1;
157        while j <= self.rounds {
158            if j % 2 != 0 {
159                let tmp = (left + self.round(j, right)) & self.a_mask;
160                left = right;
161                right = tmp;
162                j += 1;
163            } else {
164                let tmp = (left + self.round(j, right)) & self.b_mask;
165                left = right;
166                right = tmp;
167                j += 1;
168            }
169        }
170
171        if self.rounds % 2 != 0 {
172            (left << self.a_bits) + right
173        } else {
174            (right << self.a_bits) + left
175        }
176    }
177
178    /// Randomize your input.
179    ///
180    /// ```
181    /// # use perfect_rand::PerfectRng;
182    ///
183    /// let randomizer = PerfectRng::from_range(100);
184    /// for i in 0..100 {
185    ///     let shuffled_i = randomizer.shuffle(i);
186    ///     assert!(shuffled_i <= 100);
187    /// }
188    /// ```
189    #[must_use]
190    #[inline]
191    pub fn shuffle(&self, m: u64) -> u64 {
192        assert!(m < self.range);
193
194        let mut c = self.encrypt(m);
195        while c >= self.range {
196            c = self.encrypt(c);
197        }
198        c
199    }
200}
201
202impl PerfectRng32 {
203    /// Create a new perfect cipher with a specific range, seed, and rounds.
204    /// Use [`PerfectRng::from_range`] to use the default seed and rounds.
205    ///
206    /// - `range`: The number of possible values. In other words, the highest
207    ///   value you will try to shuffle. For example, this would be 2**32-1 for
208    ///   an IPv4 address.
209    /// - `seed`: The seed used for randomization.
210    /// - `rounds`: The amount of times the randomization is done, to make it
211    ///   more random. Recommended value is either 3 or 4, depending on your
212    ///   performance/quality needs.
213    ///
214    /// ```
215    /// # use perfect_rand::PerfectRng;
216    /// let perfect_rng = PerfectRng::new(10, rand::random(), 4);
217    /// ```
218    #[must_use]
219    #[inline]
220    pub fn new(range: u32, seed: u32, rounds: usize) -> Self {
221        assert_ne!(range, 0);
222
223        let bits = count_bits((range - 1) as u64);
224        let b = bits / 2;
225        // if an odd number of bits, a gets the leftover bit
226        let a = bits - b;
227
228        PerfectRng32 {
229            range,
230            seed,
231            rounds,
232            a_bits: a,
233            a_mask: (1 << a) - 1,
234            b_mask: (1 << b) - 1,
235        }
236    }
237
238    /// Create a new `PerfectRng` with a random seed and default rounds.
239    ///
240    /// ```
241    /// # use perfect_rand::PerfectRng;
242    /// let perfect_rng = PerfectRng::from_range(2u64.pow(32));
243    /// ```
244    #[must_use]
245    pub fn from_range(range: u32) -> Self {
246        Self::new(range, rand::random(), 4)
247    }
248
249    #[inline]
250    fn round(&self, j: usize, right: u32) -> u32 {
251        let v0 = self.seed;
252        let v1 = j as u32;
253        let v2 = right;
254        // all zeroes will lead to an all-zero output,
255        // this adds some randomness for that case.
256        let v3: u32 = 0xbc9ad940;
257
258        let (v0, v1, v2, v3) = sipround32(v0, v1, v2, v3);
259        let (v0, v1, v2, v3) = sipround32(v0, v1, v2, v3);
260        let (v0, v1, v2, v3) = sipround32(v0, v1, v2, v3);
261        let (v0, _, _, _) = sipround32(v0, v1, v2, v3);
262
263        v0
264    }
265
266    #[inline]
267    fn encrypt(&self, m: u32) -> u32 {
268        let mut left = m & self.a_mask;
269        let mut right = m >> self.a_bits;
270
271        let mut j = 1;
272        while j <= self.rounds {
273            if j % 2 != 0 {
274                let tmp = (left + self.round(j, right)) & self.a_mask;
275                left = right;
276                right = tmp;
277                j += 1;
278            } else {
279                let tmp = (left + self.round(j, right)) & self.b_mask;
280                left = right;
281                right = tmp;
282                j += 1;
283            }
284        }
285
286        if self.rounds % 2 != 0 {
287            (left << self.a_bits) + right
288        } else {
289            (right << self.a_bits) + left
290        }
291    }
292
293    /// Randomize your input.
294    ///
295    /// ```
296    /// # use perfect_rand::PerfectRng;
297    ///
298    /// let randomizer = PerfectRng::from_range(100);
299    /// for i in 0..100 {
300    ///     let shuffled_i = randomizer.shuffle(i);
301    ///     assert!(shuffled_i <= 100);
302    /// }
303    /// ```
304    #[must_use]
305    #[inline]
306    pub fn shuffle(&self, m: u32) -> u32 {
307        assert!(m < self.range);
308
309        let mut c = self.encrypt(m);
310        while c >= self.range {
311            c = self.encrypt(c);
312        }
313        c
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use ntest::timeout;
320
321    use super::PerfectRng;
322
323    fn verify(range: u64, seed: u64, rounds: usize) {
324        let randomizer = PerfectRng::new(range, seed, rounds);
325        println!("randomizer: {randomizer:?}");
326
327        // make sure every number gets added exactly once
328        let mut list = vec![0; range as usize];
329        for i in 0..range {
330            let x = randomizer.shuffle(i) as usize;
331            list[x] += 1;
332        }
333
334        for (i, number) in list.into_iter().enumerate() {
335            assert_eq!(number, 1, "Index: {i}, range: {range:?}");
336        }
337    }
338
339    #[test]
340    #[timeout(1250)]
341    fn verify_ranges() {
342        let mut range = 3015 * 3;
343
344        for i in 0..5 {
345            range += 11 + i;
346            range *= 1 + i;
347
348            verify(range, 0, 6);
349        }
350
351        verify(10, 0, 4);
352        verify(100, 0, 4);
353    }
354
355    #[test]
356    #[timeout(100)]
357    fn dont_get_stuck() {
358        for range in [10, 100] {
359            for seed in 0..100 {
360                let randomizer = PerfectRng::new(range, seed, 4);
361
362                for i in 0..range {
363                    let _ = randomizer.shuffle(i);
364                }
365            }
366        }
367    }
368
369    #[test]
370    fn sufficiently_random() {
371        let randomizer = PerfectRng::new(65536, 0, 4);
372        let mut inc = 0_u32;
373        let mut dec = 0_u32;
374        let mut prev = 0;
375
376        for i in 0..65535 {
377            let shuffled_i = randomizer.shuffle(i);
378            if shuffled_i > prev {
379                inc += 1;
380            } else {
381                dec += 1;
382            }
383            prev = shuffled_i;
384        }
385
386        // 512 = 0.003% chance of failing on a completely random hash
387        assert!(
388            inc.abs_diff(dec) < 512,
389            "insufficiently random (inc: {inc}, dec: {dec})"
390        );
391    }
392}