Skip to main content

fair/
rng.rs

1use hmac::{Hmac, Mac};
2use sha2::Sha256;
3
4use hmac::crypto_mac::generic_array::typenum::*;
5use hmac::crypto_mac::generic_array::GenericArray;
6
7use std::marker::PhantomData;
8
9// Create alias for HMAC-SHA256
10type HmacSha256 = Hmac<Sha256>;
11
12pub struct ProvablyFairConfig {
13    client_seed: String,
14    server_seed: String,
15    nonce: u64,
16}
17
18impl ProvablyFairConfig {
19    pub fn new(client_seed: &str, server_seed: &str, nonce: u64) -> ProvablyFairConfig {
20        ProvablyFairConfig {
21            client_seed: client_seed.to_string(),
22            server_seed: server_seed.to_string(),
23            nonce,
24        }
25    }
26}
27
28pub struct ProvablyFairRNG<T> {
29    config: ProvablyFairConfig,
30    current_round: u64,
31    current_round_cursor: usize,
32    current_round_mac: Option<GenericArray<u8, U32>>,
33    rng_type: PhantomData<T>,
34}
35
36///
37/// ## Examples
38///
39/// For byte mode:
40///
41/// ```
42/// use fair::ProvablyFairRNG;
43/// let client_seed = "some client seed";
44/// let server_seed = "some server seed";
45/// let nonce = 1;
46/// let mut rng: ProvablyFairRNG<u8> = ProvablyFairRNG::new(client_seed, server_seed, nonce);
47/// // this is an inifinite iterator, it never returns None
48/// println!("{}", rng.next().unwrap())
49/// ```
50///
51/// For float mode:
52///
53/// ```
54/// use fair::ProvablyFairRNG;
55/// let client_seed = "some client seed";
56/// let server_seed = "some server seed";
57/// let nonce = 1;
58/// let mut rng: ProvablyFairRNG<f64> = ProvablyFairRNG::new(client_seed, server_seed, nonce);
59/// // this is an inifinite iterator, it never returns None
60/// println!("{}", rng.next().unwrap())
61/// ```
62///
63///
64impl<T> ProvablyFairRNG<T> {
65    pub fn from_config(config: ProvablyFairConfig) -> ProvablyFairRNG<T> {
66        ProvablyFairRNG {
67            config,
68            // TODO: group this under iter field?
69            current_round: 0,
70            current_round_cursor: 0,
71            current_round_mac: None,
72            rng_type: PhantomData,
73        }
74    }
75
76    pub fn new(client_seed: &str, server_seed: &str, nonce: u64) -> ProvablyFairRNG<T> {
77        let config = ProvablyFairConfig {
78            client_seed: client_seed.to_string(),
79            server_seed: server_seed.to_string(),
80            nonce,
81        };
82        Self::from_config(config)
83    }
84
85    fn update_current_round_buffer(&mut self) {
86        // Create HMAC-SHA256 instance which implements `Mac` trait
87        let key = self.config.server_seed.as_bytes();
88        let input = format!(
89            "{}:{}:{}",
90            self.config.client_seed, self.config.nonce, self.current_round
91        );
92
93        let mut mac =
94            HmacSha256::new_varkey(key).expect("HMAC can take key of any size, never errors here");
95        mac.input(input.as_bytes());
96        let result = mac.result();
97        self.current_round_mac = Some(result.code());
98    }
99
100    fn next_byte(&mut self) -> u8 {
101        // 32 = number of bytes in self.current_round_buffer (aka size of hmac signature)
102        // TODO: use sizeof pragma?
103        let mac = match &self.current_round_mac {
104            None => {
105                self.update_current_round_buffer();
106                return self.next_byte();
107            }
108            Some(v) => v,
109        };
110
111        let buf = mac;
112        let result = buf[self.current_round_cursor];
113        if self.current_round_cursor == 31 {
114            self.current_round_cursor = 0;
115            self.current_round += 1;
116            self.current_round_mac = None;
117        } else {
118            self.current_round_cursor += 1;
119        }
120        return result;
121    }
122
123    fn next_float(&mut self) -> f64 {
124        let bytes_per_float = 4;
125        let bytes = &mut [0; 4];
126        for i in 0..bytes_per_float {
127            let byte = self.next_byte();
128            bytes[i] = byte;
129        }
130        let result = bytes_to_float(bytes);
131        return result;
132    }
133}
134
135impl std::iter::Iterator for ProvablyFairRNG<f64> {
136    type Item = f64;
137    fn next(&mut self) -> Option<f64> {
138        Some(self.next_float())
139    }
140}
141
142impl std::iter::Iterator for ProvablyFairRNG<u8> {
143    type Item = u8;
144    fn next(&mut self) -> Option<u8> {
145        Some(self.next_byte())
146    }
147}
148
149// technique for converting groups of bytes into a float
150fn bytes_to_float(bytes: &[u8]) -> f64 {
151    let (float, _) = bytes.iter().fold((0., 0.), |(result, i), &value| {
152        let value = value as f64;
153        let divider = 256_f64.powf(i + 1.);
154        let partial_result = value / divider as f64;
155        (result + partial_result, i + 1.)
156    });
157    float
158}
159
160// TODO: use that function everywhere we are picking a number in a range
161impl ProvablyFairRNG<f64> {
162    // get a random number in [start, end[ range
163    pub fn range(&mut self, start: usize, end: usize) -> usize {
164        assert!(end > start);
165        let range = (end as i32 - start as i32) as usize;
166        (self.next().unwrap() * range as f64) as usize + start as usize
167    }
168}
169
170#[cfg(test)]
171mod test {
172    use super::*;
173
174    #[test]
175    fn provably_fair_rng() {
176        let client_seed = "some client seed";
177        let server_seed = "some server seed";
178        let nonce = 1;
179        let mut rng: ProvablyFairRNG<u8> = ProvablyFairRNG::new(client_seed, server_seed, nonce);
180        let expected_values = vec![
181            151, 136, 121, 135, 209, 159, 189, 233, 43, 248, 146, 253, 71, 34, 215, 176, 139, 160,
182            47, 225, 233, 221, 169, 198, 187, 103, 171, 31, 87, 118, 23, 138, 198, 14, 60, 130,
183            130, 198, 153, 83,
184        ];
185        for val in expected_values {
186            assert_eq!(rng.next(), Some(val));
187        }
188    }
189
190    #[test]
191    fn provably_fair_rng_float() {
192        let client_seed = "some client seed";
193        let server_seed = "some server seed";
194        let nonce = 1;
195        let mut rng: ProvablyFairRNG<f64> = ProvablyFairRNG::new(client_seed, server_seed, nonce);
196        let expected_values = vec![
197            0.5919261889066547,
198            0.81884371698834,
199            0.17176169087179005,
200            0.277875404804945,
201            0.5454130100551993,
202            0.913538561668247,
203            0.732050604885444,
204            0.34164569014683366,
205            0.7736547295935452,
206            0.5108428790699691,
207        ];
208        for val in expected_values {
209            let actual = rng.next();
210            // println!("{} == {} ?", actual.unwrap(), val);
211            assert_eq!(actual, Some(val));
212        }
213    }
214
215    #[test]
216    fn test_rng_float_starts_with_0() {
217        let client_seed = "83e27f682128eb1852b048203dfd6931";
218        let server_seed = "e8df2cc3b9ccb583ce5ea92336842387";
219        let nonce = 1942124;
220        let mut rng: ProvablyFairRNG<f64> = ProvablyFairRNG::new(client_seed, server_seed, nonce);
221        assert_eq!(rng.next().unwrap(), 0.00000025122426450252533);
222    }
223}