1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use crate::error::OptimusError;
pub const MAX_INT: u64 = i32::MAX as u64;
///Optimus is used to encode and decode integers using Knuth's Hashing Algorithm.
#[derive(Debug, Clone, Copy)]
pub struct Optimus {
prime: u64,
mod_inverse: u64,
random: u64,
}
impl Optimus {
/// Returns an Optimus struct that can be used to encode and decode integers.
/// A common use case is for obfuscating internal ids of database primary keys.
/// It is imperative that you keep a record of prime, modInverse and random so that
/// you can decode an encoded integer correctly. random must be an integer less than `MAX_INT`.
///
/// # Errors
///
/// Will return `OptimusError` if the argument `prime` is not prime
///
/// CAUTION: DO NOT DIVULGE prime, modInverse and random!
pub fn new(prime: u64, mod_inverse: u64, random: u64) -> Result<Self, OptimusError> {
if !primal_check::miller_rabin(prime) {
return Err(OptimusError::NotPrime);
}
Ok(Self {
prime,
mod_inverse,
random,
})
}
///Returns an Optimus struct that can be used to encode and decode integers.
///random must be an integer less than `MAX_INT`.
///It automatically calculates prime's mod inverse and then calls new.
/// # Errors
///
/// Will return `OptimusError` if the argument `prime` is not prime
/// or if a Mod Inverse cannot be found
///
pub fn new_calculated(prime: u64, random: u64) -> Result<Self, OptimusError> {
Self::new(prime, Self::calc_mod_inverse(prime as i64)?, random)
}
///returns the modular inverse of a given prime number.
///The modular inverse is defined such that
///(`PRIME` * `MODULAR_INVERSE`) & (`MAX_INT`) = 1.
///
///See: <http://en.wikipedia.org/wiki/Modular_multiplicative_inverse>
///
///NOTE: prime is assumed to be a valid prime. If prime is outside the bounds of
///an i64, then the function panics as it can not calculate the mod inverse.
/// # Errors
/// Will return `OptimusError` if the argument `prime` is not prime
/// or if a mod inverse cannot be found
///
pub fn calc_mod_inverse(prime: i64) -> Result<u64, OptimusError> {
const MAX: i64 = (MAX_INT + 1) as i64;
if !primal_check::miller_rabin(prime as u64) {
return Err(OptimusError::NotPrime);
}
Ok(modinverse::modinverse(prime, MAX).ok_or(OptimusError::NoModInverse)? as u64)
}
///Encodes n using Knuth's hashing algorithm.
pub fn encode(&self, n: u64) -> u64 {
((n * self.prime) & MAX_INT) ^ self.random
}
///Decodes n back to the original. It will only decode correctly if the Optimus struct
///is consistent with what was used to encode n.
pub fn decode(&self, n: u64) -> u64 {
((n ^ self.random) * self.mod_inverse) & MAX_INT
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::Rng;
#[test]
fn test_calc_mod_inverse() {
let prime = 309779747;
let expected_mod_inverse = 49560203;
let calculated = Optimus::calc_mod_inverse(prime).unwrap();
assert_eq!(
calculated, expected_mod_inverse,
"mod inverse incorrect. Expected={}, Actual={}",
expected_mod_inverse, calculated
);
}
/// Tests if the encoding process correctly decodes the id back to the original
#[test]
fn test_encode() {
let mut rng = rand::thread_rng();
let os = [
Optimus::new(309779747, 49560203, 57733611).unwrap(),
Optimus::new(684934207, 1505143743, 846034763).unwrap(),
Optimus::new(743534599, 1356791223, 1336232185).unwrap(),
Optimus::new(54661037, 1342843941, 576322863).unwrap(),
Optimus::new(198194831, 229517423, 459462336).unwrap(),
Optimus::new_calculated(198194831, 459462336).unwrap(),
];
println!("{:?}", os[1]);
for o in os {
let c = 10;
let h = 100; // How many random numbers to select in between 0-c and (MAX_INT-c) - MAX-INT
let mut vars = vec![];
for t in 0..c {
vars.push(t);
}
for _ in 0..h {
let upper = MAX_INT - 2 * c;
let rand = rng.gen_range(0..upper);
vars.push(rand + c);
}
for t in (MAX_INT - c..MAX_INT).rev() {
vars.push(t);
}
for value in vars {
let orig = value;
let hashed = o.encode(value);
let unhashed = o.decode(hashed);
println!("%{orig}: %{hashed} -> %{unhashed}");
assert_eq!(
orig, unhashed,
"%{}: %{} -> %{} - FAILED",
orig, hashed, unhashed
)
}
}
}
}