permutation_iterator/lib.rs
1// Copyright 2019, Asim Ihsan
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4// in compliance with the License. You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software distributed under the License
9// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10// or implied. See the License for the specific language governing permissions and limitations under
11// the License.
12
13//! Utilities for iterating over random permutations.
14//!
15//! `permutation-iterator` provides utilities for iterating over random permutations in constant
16//! space.
17//!
18//! # Quick Start
19//!
20//! Please check the GitHub repository's `README.md` and `examples` folder for how to get started
21//! with this library.
22
23use blake2_rfc::blake2b::Blake2b;
24use rand::Rng;
25
26/// Permutor gives you back a permutation iterator that returns a random permutation over
27/// [0, max) (0 inclusive to max exclusive).
28///
29/// # Examples
30///
31/// Permutor can be used to iterate over a random permutation of integers [0..max) (0 inclusive to
32/// max exclusive):
33///
34/// ```
35/// use crate::permutation_iterator::Permutor;
36/// use std::collections::HashSet;
37///
38/// let max: u64 = 10;
39/// let permutor = Permutor::new(max);
40/// for value in permutor {
41/// println!("{}", value);
42/// }
43/// ```
44pub struct Permutor {
45 feistel: FeistelNetwork,
46 max: u64,
47 current: u64,
48 values_returned: u64,
49}
50
51impl Permutor {
52 pub fn new_with_u64_key(max: u64, key: u64) -> Permutor {
53 let key = u64_to_32slice(key);
54 Permutor {
55 feistel: FeistelNetwork::new_with_slice_key(max, key),
56 max,
57 current: 0,
58 values_returned: 0,
59 }
60 }
61
62 pub fn new_with_slice_key(max: u64, key: [u8; 32]) -> Permutor {
63 Permutor {
64 feistel: FeistelNetwork::new_with_slice_key(max, key),
65 max,
66 current: 0,
67 values_returned: 0,
68 }
69 }
70
71 pub fn new(max: u64) -> Permutor {
72 Permutor {
73 feistel: FeistelNetwork::new(max),
74 max,
75 current: 0,
76 values_returned: 0,
77 }
78 }
79}
80
81impl Iterator for Permutor {
82 type Item = u64;
83
84 fn next(&mut self) -> Option<Self::Item> {
85 while self.values_returned < self.max {
86 let next = self.feistel.permute(self.current);
87 self.current += 1;
88 if next >= self.max {
89 continue;
90 }
91 self.values_returned += 1;
92 return Some(next);
93 }
94 None
95 }
96}
97
98/// Iterate over a random permutation of a pair of integer sequences.
99///
100/// # Examples
101///
102/// Suppose you have two lists, first with 3. elements and the second with 7 elements,
103/// and you want to iterate over a random permutation of pairs:
104///
105/// ```
106/// use permutation_iterator::RandomPairPermutor;
107///
108/// let pair_permutor = RandomPairPermutor::new(3, 7);
109/// for (i, j) in pair_permutor {
110/// println!("({}, {})", i, j);
111/// }
112/// ```
113///
114pub struct RandomPairPermutor {
115 permutor: Permutor,
116 max2: u32,
117}
118
119impl RandomPairPermutor {
120 pub fn new(max1: u32, max2: u32) -> RandomPairPermutor {
121 let max: u64 = (max1 as u64) * (max2 as u64);
122 RandomPairPermutor {
123 permutor: Permutor::new(max),
124 max2,
125 }
126 }
127}
128
129impl Iterator for RandomPairPermutor {
130 type Item = (u32, u32);
131
132 fn next(&mut self) -> Option<Self::Item> {
133 match self.permutor.next() {
134 Some(value) => {
135 let first = value as u32 / self.max2;
136 let second = value as u32 % self.max2;
137 Some((first, second))
138 }
139 _ => None,
140 }
141 }
142}
143
144/// Implements a Feistel network, which can take a non-invertible pseudo-random function (PRF)
145/// and turn it into an invertible pseudo-random permutation (PRP).
146///
147/// If you use this struct directly note that its intended purpose is to be a PRP and map from
148/// an n-bit input to an n-bit output, where n is an even positive integer. For example, if
149/// constructed with a `max` of `10`, internally it creates a 4-bit Feistel network, and for all
150/// integers in the 4-bit domain `[0, 16)` (`0` inclusive to `16` exclusive) it will map an input
151/// to one and only one output, and vice-versa (a given output maps to one and only one input).
152/// Even though you specified a max value of `10`, the output range may be larger than expected.
153/// Clients like `RandomPermutor` handle this by excluding output values outside of the desired
154/// range.
155///
156/// This is useful in fields like cryptography, where a block cipher is a PRP.
157///
158/// Another great use of a Feistel network is when you want some input to always map to one and only
159/// one output (and vice versa). For example, given a 32-bit IP address, we could use some secret
160/// key and map each IP address to some other 32-bit IP address. We could log this new 32-bit
161/// IP address and people who do not know what the secret key is would find it difficult
162/// to determine what the input IP address was. This is Format Preserving Encryption (FPE).
163pub struct FeistelNetwork {
164 /// TODO visible just for testing, fix
165 pub half_width: u64,
166
167 /// Mask used to keep within the width for the right.
168 /// TODO visible just for testing, fix
169 pub right_mask: u64,
170
171 /// Mask used to keep within the width for the left.
172 /// TODO visible just for testing, fix
173 pub left_mask: u64,
174
175 /// Private key, some random seed. 256 bits as 32 bytes.
176 key: [u8; 32],
177
178 rounds: u8,
179}
180
181impl FeistelNetwork {
182 /// Create a new FeistelNetwork instance that can give you a random permutation of
183 /// integers.
184 ///
185 /// Note that the value of max is rounded up to the nearest even power of 2. If clients are
186 /// trying to get a permutation of [0, max) they need to iterate over the input range and
187 /// discard values from FeistelNetwork >= max.
188 ///
189 /// The key used for the permutation is made up of securely gathered 32 bytes.
190 pub fn new(max: u64) -> FeistelNetwork {
191 let key = rand::thread_rng().gen::<[u8; 32]>();
192 FeistelNetwork::new_with_slice_key(max, key)
193 }
194
195 /// Create a new FeistelNetwork instance that can give you a random permutation of
196 /// integers.
197 ///
198 /// Note that the value of max is rounded up to the nearest even power of 2. If clients are
199 /// trying to get a permutation of [0, max) they need to iterate over the input range and
200 /// discard values from FeistelNetwork >= max.
201 pub fn new_with_slice_key(max_value: u64, key: [u8; 32]) -> FeistelNetwork {
202 let width = (max_value as f64).log2();
203 let mut width = width.ceil() as u64;
204 if width % 2 != 0 {
205 width += 1;
206 }
207 let half_width = width / 2;
208 let mut right_mask = 0;
209 for i in 0..half_width {
210 right_mask |= 1 << i;
211 }
212 let left_mask = right_mask << half_width;
213 FeistelNetwork {
214 half_width,
215 right_mask,
216 left_mask,
217 key,
218 rounds: 12,
219 }
220 }
221
222 pub fn permute(&self, input: u64) -> u64 {
223 let mut left = (input & self.left_mask) >> self.half_width;
224 let mut right = input & self.right_mask;
225
226 for i in 0..self.rounds as u8 {
227 let new_left = right;
228 let f = self.round_function(right, i, &self.key[..], self.right_mask);
229 right = left ^ f;
230 left = new_left;
231 }
232
233 let result = (left << self.half_width) | right;
234 result & (self.left_mask | self.right_mask)
235 }
236
237 fn round_function(&self, right: u64, round: u8, key: &[u8], mask: u64) -> u64 {
238 let right_bytes = u64_to_8slice(right);
239 let round_bytes = u8_to_1slice(round);
240 let mut context: Blake2b = Blake2b::with_key(8, key);
241 context.update(&right_bytes[..]);
242 context.update(&round_bytes[..]);
243 let hash = context.finalize();
244 let hash_bytes: &[u8] = hash.as_bytes();
245 slice_to_u64(hash_bytes) & mask
246 }
247}
248
249fn slice_to_u64(input: &[u8]) -> u64 {
250 (input[7] as u64)
251 | ((input[6] as u64) << 8)
252 | ((input[5] as u64) << 16)
253 | ((input[4] as u64) << 24)
254 | ((input[3] as u64) << 32)
255 | ((input[2] as u64) << 40)
256 | ((input[1] as u64) << 48)
257 | ((input[0] as u64) << 56)
258}
259
260fn u8_to_1slice(input: u8) -> [u8; 1] {
261 let mut result: [u8; 1] = [0; 1];
262 result[0] = input;
263 result
264}
265
266/// Convert an unsigned 64 bit number so a slice of 8 bytes in big-endian format (most significant
267/// bit first).
268///
269/// # Examples
270///
271/// ```
272/// use crate::permutation_iterator::u64_to_8slice;
273/// let output = u64_to_8slice(42);
274/// assert_eq!(output, [0, 0, 0, 0, 0, 0, 0, 0x2A]);
275/// ```
276pub fn u64_to_8slice(input: u64) -> [u8; 8] {
277 let mut result: [u8; 8] = [0; 8];
278 result[7] = (input & 0xFF) as u8;
279 result[6] = ((input & 0xFF00) >> 8) as u8;
280 result[5] = ((input & 0x00FF_0000) >> 16) as u8;
281 result[4] = ((input & 0xFF00_0000) >> 24) as u8;
282 result[3] = ((input & 0x00FF_0000_0000) >> 32) as u8;
283 result[2] = ((input & 0xFF00_0000_0000) >> 40) as u8;
284 result[1] = ((input & 0x00FF_0000_0000_0000) >> 48) as u8;
285 result[0] = ((input & 0xFF00_0000_0000_0000) >> 56) as u8;
286 result
287}
288
289/// Convert an unsigned 64 bit number so a slice of 32 bytes in big-endian format (most significant
290/// bit first).
291///
292/// # Examples
293///
294/// ```
295/// use crate::permutation_iterator::u64_to_32slice;
296/// let output = u64_to_32slice(42);
297/// assert_eq!(output, [0, 0, 0, 0, 0, 0, 0, 0x2A,
298/// 0, 0, 0, 0, 0, 0, 0, 0,
299/// 0, 0, 0, 0, 0, 0, 0, 0,
300/// 0, 0, 0, 0, 0, 0, 0, 0]);
301/// ```
302pub fn u64_to_32slice(input: u64) -> [u8; 32] {
303 let result8 = u64_to_8slice(input);
304 let mut result: [u8; 32] = [0; 32];
305 result[..8].clone_from_slice(&result8[..8]);
306 result
307}