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}