proof_fair/
lib.rs

1use hmac::{Hmac, Mac};
2use rand::{thread_rng, Rng};
3use sha2::{Digest, Sha256, Sha512};
4
5// Server seed is generated by the server and is kept secret until the game is over.
6// Client seed is generated by the client and is also kept secret if there is more than one player for the game.
7// Nonce is incremented by one for each game round, to ensure that each game round is unique and cannot be repeated or manipulated.
8// It's important to note that the nonce should never be predictable or repeatable, and it should be kept secret from players to prevent any attempts to manipulate the game outcome.
9// These combined are used to generate a HMAC-SHA512 hash.
10const ALPHA: &str = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
11
12/// Proof represents a provably fair random number generator (RNG) using HMAC-SHA512 hash.
13///
14/// This struct allows users to create a new RNG instance, log its state, roll the RNG,
15/// calculate random numbers, and verify the validity of generated numbers.
16///
17/// # Examples
18///
19/// ```
20/// use hmac_rng::Proof;
21///
22/// let mut proof = Proof::new(None, None, 0);
23/// proof.log_state();
24/// let result = proof.roll();
25/// match result {
26///     Ok(random_number) => println!("Random number: {}", random_number),
27///     Err(err) => eprintln!("Error: {}", err),
28/// }
29/// ```
30
31#[derive(Debug)]
32pub struct Proof {
33    pub client_seed: Vec<u8>,
34    pub server_seed: Vec<u8>,
35    pub blinded_server_seed: Vec<u8>,
36    pub nonce: i64,
37}
38
39impl Proof {
40    /// Creates a new instance of the Proof struct with the provided or random server seed,
41    /// client seed, and nonce value.
42    ///
43    /// # Arguments
44    ///
45    /// * `client_seed` - An optional client seed.
46    /// * `server_seed` - An optional server seed.
47    /// * `nonce` - The nonce value.
48    ///
49    /// # Examples
50    ///
51    /// ```
52    /// use hmac_rng::Proof;
53    ///
54    /// let mut proof = Proof::new(None, None, 0);
55    /// ```
56    pub fn new(client_seed: Option<Vec<u8>>, server_seed: Option<Vec<u8>>, nonce: i64) -> Self {
57        // Generate a client seed if one isn't provided
58        let client_seed = match client_seed {
59            Some(seed) => seed,
60            None => new_seed(64),
61        };
62
63        // Generate a random server seed if one isn't provided
64        let server_seed = match server_seed {
65            Some(seed) => seed,
66            None => new_seed(64),
67        };
68
69        // Hash the serverSeed to show the client
70        let blinded_seed = Sha256::digest(&server_seed);
71
72        Proof {
73            nonce,
74            client_seed,
75            server_seed,
76            blinded_server_seed: blinded_seed.to_vec(),
77        }
78    }
79
80    // prints the current state of the proof to the console
81    pub fn log_state(&self) {
82        let wnr = self.calculate().unwrap();
83        println!("[Proof] Random number: {}", wnr);
84        println!(
85            "[Proof] Client Seed (public) : {}",
86            String::from_utf8_lossy(&self.client_seed)
87        );
88        println!(
89            "[Proof] Server Seed (secret): {}",
90            String::from_utf8_lossy(&self.server_seed)
91        );
92        println!(
93            "[Proof] Server Seed (public): {}",
94            hex::encode(&self.blinded_server_seed)
95        );
96        println!("[Proof] Nonce: {}", self.nonce);
97    }
98
99    /// Increments the nonce value and calculates the random number for the current
100    /// state of the Proof struct.
101    ///
102    /// This method ensures that the first nonce used is 0.
103    ///
104    /// # Returns
105    ///
106    /// * `Ok(random_number)` - The generated random number.
107    /// * `Err(err)` - An error message if the calculation fails.
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// use hmac_rng::Proof;
113    ///
114    /// let mut proof = Proof::new(None, None, 0);
115    /// let result = proof.roll();
116    /// match result {
117    ///     Ok(random_number) => println!("Random number: {}", random_number),
118    ///     Err(err) => eprintln!("Error: {}", err),
119    /// }
120    /// ```
121    pub fn roll(&mut self) -> Result<f64, String> {
122        self.nonce += 1;
123        self.calculate()
124    }
125
126    /// Calculates the current value from the current state of the Proof struct.
127    ///
128    /// This method does not advance the state in any way. Calling Calculate multiple times
129    /// with the same nonce will always result in the same value.
130    ///
131    /// # Returns
132    ///
133    /// * `Ok(random_number)` - The calculated random number.
134    /// * `Err(err)` - An error message if the calculation fails.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// use hmac_rng::Proof;
140    ///
141    /// let proof = Proof::new(None, None, 0);
142    /// let result = proof.calculate();
143    /// match result {
144    ///     Ok(random_number) => println!("Random number: {}", random_number),
145    ///     Err(err) => eprintln!("Error: {}", err),
146    /// }
147    /// ```
148    pub fn calculate(&self) -> Result<f64, String> {
149        let hmac = self.calculate_hmac();
150        let our_hmac = hex::encode(hmac);
151
152        for i in 0..(our_hmac.len() - 5) {
153            let idx = i * 5;
154            if our_hmac.len() < (idx + 5) {
155                break;
156            }
157            let hex_segment = &our_hmac[idx..idx + 5];
158            let rand_num = u64::from_str_radix(hex_segment, 16).map_err(|e| e.to_string())?;
159            if rand_num <= 999_999 {
160                return Ok((rand_num % 10_000) as f64 / 100.0);
161            }
162        }
163
164        Err("Invalid Nonce".to_string())
165    }
166
167    /// Creates a SHA-512 HMAC object with the server seed as the secret key and updates it
168    /// with the client seed and nonce concatenated with a '-' in between.
169    ///
170    /// # Returns
171    ///
172    /// The calculated HMAC as a vector of bytes.
173    fn calculate_hmac(&self) -> Vec<u8> {
174        let mut mac = Hmac::<Sha512>::new_from_slice(&self.server_seed).unwrap();
175        let nonce_str = self.nonce.to_string();
176        let mut message = self.client_seed.clone();
177        message.push(b'-');
178        message.extend_from_slice(nonce_str.as_bytes());
179        mac.update(&message);
180        mac.finalize().into_bytes().to_vec()
181    }
182
183    /// Verifies that the given random number is valid for the given client seed, server seed,
184    /// and nonce values by recreating the Proof instance and comparing the calculated random
185    /// number with the provided random number.
186    ///
187    /// # Arguments
188    ///
189    /// * `client_seed` - The client seed.
190    /// * `server_seed` - An optional server seed. Pass `None` if not provided.
191    /// * `nonce` - The nonce value.
192    /// * `rand_num` - The random number to verify.
193    ///
194    /// # Returns
195    ///
196    /// * `Ok(valid)` - `true` if the random number is valid, `false` otherwise.
197    /// * `Err(err)` - An error message if the verification fails.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use hmac_rng::Proof;
203    ///
204    /// let client_seed = vec![1, 2, 3];
205    /// let server_seed = vec![4, 5, 6];
206    /// let nonce = 0;
207    /// let random_number = 0.42;
208    /// let result = Proof::verify(&client_seed, Some(&server_seed), nonce, random_number);
209    /// match result {
210    ///     Ok(valid) => println!("Valid: {}", valid),
211    ///     Err(err) => eprintln!("Error: {}", err),
212    /// }
213    /// ```
214    pub fn verify(
215        client_seed: &[u8],
216        server_seed: Option<&[u8]>,
217        nonce: i64,
218        rand_num: f64,
219    ) -> Result<bool, String> {
220        let proof = Proof::new(
221            Some(client_seed.to_vec()),
222            server_seed.map(|seed| seed.to_vec()),
223            nonce,
224        );
225        match proof.calculate() {
226            Ok(calculated_num) => Ok((calculated_num - rand_num).abs() < f64::EPSILON),
227            Err(_) => Err(String::from("Proof calculation failed.")),
228        }
229    }
230}
231/// Generates a new seed of the specified size using characters from the ALPHA character set.
232///
233/// # Arguments
234///
235/// * `size` - The size (length) of the generated seed.
236///
237/// # Returns
238///
239/// A vector of bytes representing the generated seed.
240///
241/// # Examples
242///
243/// ```
244/// use hmac_rng::new_seed;
245///
246/// let seed = new_seed(64);
247/// println!("Generated Seed: {:?}", seed);
248/// ```
249///
250/// # Note
251///
252/// The ALPHA character set used for generating the seed includes alphanumeric characters
253/// (excluding some potentially confusing characters like '0', 'O', '1', 'l', etc.), which
254/// is commonly used in cryptographic applications.
255fn new_seed(size: usize) -> Vec<u8> {
256    let mut rng = thread_rng();
257    (0..size)
258        .map(|_| ALPHA.chars().nth(rng.gen_range(0..ALPHA.len())).unwrap() as u8)
259        .collect()
260}