stealth_lib/hash/mimc.rs
1//! MiMC-Feistel sponge hash function.
2//!
3//! MiMC (Minimal Multiplicative Complexity) is a hash function designed for
4//! efficient evaluation inside arithmetic circuits, particularly in ZK-SNARKs.
5//!
6//! # Algorithm
7//!
8//! This implementation uses the MiMC-Feistel-Sponge construction with:
9//! - Feistel network structure for the permutation
10//! - Sponge construction for variable-length input
11//! - Exponent of 5 (x^5) for the round function
12//!
13//! # Constants
14//!
15//! The round constants are derived deterministically. The default configuration
16//! uses constants compatible with circomlib/Tornado Cash implementations.
17//!
18//! # References
19//!
20//! - [MiMC Paper](https://eprint.iacr.org/2016/492.pdf)
21//! - [circomlib implementation](https://github.com/iden3/circomlib/blob/master/circuits/mimcsponge.circom)
22//!
23//! # Example
24//!
25//! ```
26//! use stealth_lib::hash::MimcHasher;
27//!
28//! let hasher = MimcHasher::default();
29//! let hash = hasher.hash(123, 456);
30//! println!("MiMC hash: {}", hash);
31//! ```
32//!
33//! # Security Note
34//!
35//! This implementation is designed for use in ZK circuits. It is:
36//! - **NOT constant-time** (do not use where timing attacks are a concern)
37//! - **NOT suitable for password hashing** (use argon2, bcrypt, or scrypt instead)
38
39/// MiMC round constants.
40///
41/// These constants are derived deterministically and are compatible with
42/// the circomlib/Tornado Cash MiMC implementation.
43///
44/// The constants are computed as: `keccak256("mimcsponge" || i)` truncated to
45/// fit the field, where `i` is the round index.
46const MIMC_CONSTANTS: [u128; 20] = [
47 0,
48 25823191961023811529686723375255045,
49 48376936063113800887806988124358800,
50 75580405153655082660116863095114839,
51 66651710483985382365580181188706173,
52 45887003413921204775397977044284378,
53 14399999722617037892747232478295923,
54 29376176727758177809204424209125257,
55 13768859312518298840937540532277016,
56 54749662990362840569021981534456448,
57 25161436470718351277017231215227846,
58 90370030464179443930112165274275271,
59 92014788260850167582827910417652439,
60 40376490640073034398204558905403523,
61 90379224439153137712327643289289624,
62 11220341520269979188892857030918685,
63 11480168113674888067906254878279274,
64 11144081894867681653997893051446803,
65 64965960071752809090438003157362764,
66 98428510787134995495896453413714864,
67];
68
69/// Default field prime (2^128 - 1).
70///
71/// This is the maximum value for u128, used as the modulus for field arithmetic.
72const DEFAULT_FIELD_PRIME: u128 = 340282366920938463463374607431768211455;
73
74/// Default number of rounds for MiMC-Feistel.
75const DEFAULT_ROUNDS: usize = 10;
76
77/// MiMC-Feistel sponge hasher.
78///
79/// This struct provides the MiMC hash function with configurable parameters.
80/// For most use cases, use [`MimcHasher::default()`] which provides parameters
81/// compatible with Tornado Cash / circomlib.
82///
83/// # Example
84///
85/// ```
86/// use stealth_lib::hash::MimcHasher;
87///
88/// // Use default parameters (compatible with Tornado Cash)
89/// let hasher = MimcHasher::default();
90/// let hash = hasher.hash(123, 456);
91///
92/// // Hash is deterministic
93/// assert_eq!(hasher.hash(123, 456), hasher.hash(123, 456));
94///
95/// // Different inputs produce different outputs
96/// assert_ne!(hasher.hash(123, 456), hasher.hash(123, 789));
97/// ```
98#[derive(Debug, Clone)]
99pub struct MimcHasher {
100 /// Field prime (modulus for all arithmetic operations).
101 field_prime: u128,
102 /// Number of rounds in the Feistel network.
103 num_rounds: usize,
104 /// Round constants.
105 constants: Vec<u128>,
106}
107
108impl Default for MimcHasher {
109 /// Creates a MimcHasher with default parameters compatible with Tornado Cash.
110 ///
111 /// - Field prime: 2^128 - 1
112 /// - Rounds: 10
113 /// - Constants: circomlib-compatible
114 fn default() -> Self {
115 MimcHasher {
116 field_prime: DEFAULT_FIELD_PRIME,
117 num_rounds: DEFAULT_ROUNDS,
118 constants: MIMC_CONSTANTS.to_vec(),
119 }
120 }
121}
122
123impl MimcHasher {
124 /// Creates a new MimcHasher with custom parameters.
125 ///
126 /// # Arguments
127 ///
128 /// * `field_prime` - The field modulus for arithmetic operations
129 /// * `num_rounds` - Number of Feistel rounds
130 /// * `constants` - Round constants (must have at least `num_rounds * 2` elements)
131 ///
132 /// # Example
133 ///
134 /// ```
135 /// use stealth_lib::hash::MimcHasher;
136 ///
137 /// let constants = vec![0u128; 20];
138 /// let hasher = MimcHasher::new(
139 /// 340282366920938463463374607431768211455, // 2^128 - 1
140 /// 10,
141 /// constants,
142 /// );
143 /// ```
144 pub fn new(field_prime: u128, num_rounds: usize, constants: Vec<u128>) -> Self {
145 MimcHasher {
146 field_prime,
147 num_rounds,
148 constants,
149 }
150 }
151
152 /// Returns the field prime used by this hasher.
153 #[inline]
154 pub fn field_prime(&self) -> u128 {
155 self.field_prime
156 }
157
158 /// Returns the number of rounds used by this hasher.
159 #[inline]
160 pub fn num_rounds(&self) -> usize {
161 self.num_rounds
162 }
163
164 /// MiMC-Feistel permutation.
165 ///
166 /// Applies the Feistel network to the input pair (left, right) with the given key.
167 ///
168 /// # Arguments
169 ///
170 /// * `left` - Left input value
171 /// * `right` - Right input value
172 /// * `key` - Permutation key
173 ///
174 /// # Returns
175 ///
176 /// A tuple `(new_left, new_right)` after applying the Feistel permutation.
177 fn mimc_feistel(&self, left: u128, right: u128, key: u128) -> (u128, u128) {
178 let mut last_l = left;
179 let mut last_r = right;
180
181 for i in 0..self.num_rounds {
182 // mask = (right + key + c[i]) mod p
183 let mask = last_r
184 .wrapping_add(key)
185 .wrapping_rem(self.field_prime)
186 .wrapping_add(self.constants[i])
187 .wrapping_rem(self.field_prime);
188
189 // mask^5 mod p (using square-and-multiply)
190 let mask2 = mask.wrapping_mul(mask).wrapping_rem(self.field_prime);
191 let mask4 = mask2.wrapping_mul(mask2).wrapping_rem(self.field_prime);
192 let mask5 = mask4.wrapping_mul(mask).wrapping_rem(self.field_prime);
193
194 // Feistel swap
195 let temp = last_r;
196 last_r = last_l.wrapping_add(mask5).wrapping_rem(self.field_prime);
197 last_l = temp;
198 }
199
200 (last_l, last_r)
201 }
202
203 /// MiMC sponge hash function.
204 ///
205 /// Computes the MiMC-Feistel-Sponge hash of two input values.
206 /// This is the primary hash function used for Merkle tree construction.
207 ///
208 /// # Arguments
209 ///
210 /// * `left` - First input value
211 /// * `right` - Second input value
212 ///
213 /// # Returns
214 ///
215 /// The hash output as a `u128`.
216 ///
217 /// # Example
218 ///
219 /// ```
220 /// use stealth_lib::hash::MimcHasher;
221 ///
222 /// let hasher = MimcHasher::default();
223 ///
224 /// // Hash two values
225 /// let hash = hasher.hash(123, 456);
226 ///
227 /// // Hash is deterministic
228 /// assert_eq!(hash, hasher.hash(123, 456));
229 /// ```
230 pub fn hash(&self, left: u128, right: u128) -> u128 {
231 self.mimc_sponge(left, right, self.field_prime)
232 }
233
234 /// MiMC sponge with explicit key parameter.
235 ///
236 /// Lower-level function that allows specifying a custom key.
237 /// Most users should use [`hash`](Self::hash) instead.
238 ///
239 /// # Arguments
240 ///
241 /// * `left` - First input value
242 /// * `right` - Second input value
243 /// * `key` - Sponge key
244 ///
245 /// # Returns
246 ///
247 /// The hash output as a `u128`.
248 pub fn mimc_sponge(&self, left: u128, right: u128, key: u128) -> u128 {
249 let mut last_r = left;
250 let mut last_l = right;
251
252 for _ in 0..self.num_rounds {
253 let (new_last_r, new_last_l) = self.mimc_feistel(last_r, last_l, key);
254
255 last_r = new_last_r.wrapping_add(1).wrapping_rem(self.field_prime);
256 last_l = new_last_l;
257 }
258
259 last_r
260 }
261
262 /// Hash a single value.
263 ///
264 /// Convenience method to hash a single input by pairing it with zero.
265 ///
266 /// # Arguments
267 ///
268 /// * `input` - The value to hash
269 ///
270 /// # Returns
271 ///
272 /// The hash output as a `u128`.
273 ///
274 /// # Example
275 ///
276 /// ```
277 /// use stealth_lib::hash::MimcHasher;
278 ///
279 /// let hasher = MimcHasher::default();
280 /// let hash = hasher.hash_single(12345);
281 /// ```
282 pub fn hash_single(&self, input: u128) -> u128 {
283 self.hash(input, 0)
284 }
285}
286
287// Legacy API support - these functions maintain backwards compatibility
288// with the original Hasher struct API.
289
290/// Legacy Hasher struct for backwards compatibility.
291///
292/// # Deprecated
293///
294/// This struct is provided for backwards compatibility only.
295/// New code should use [`MimcHasher`] instead.
296#[deprecated(since = "1.0.0", note = "Use MimcHasher instead")]
297pub struct Hasher;
298
299#[allow(deprecated)]
300impl Hasher {
301 /// MiMC sponge hash (legacy API).
302 ///
303 /// # Deprecated
304 ///
305 /// Use [`MimcHasher::mimc_sponge`] instead.
306 #[deprecated(since = "1.0.0", note = "Use MimcHasher::mimc_sponge instead")]
307 pub fn mimc_sponge(left: u128, right: u128, k: u128) -> u128 {
308 MimcHasher::default().mimc_sponge(left, right, k)
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn test_mimc_hash_deterministic() {
318 let hasher = MimcHasher::default();
319 let hash1 = hasher.hash(123, 456);
320 let hash2 = hasher.hash(123, 456);
321 assert_eq!(hash1, hash2);
322 }
323
324 #[test]
325 fn test_mimc_hash_different_inputs() {
326 let hasher = MimcHasher::default();
327 let hash1 = hasher.hash(123, 456);
328 let hash2 = hasher.hash(123, 789);
329 let hash3 = hasher.hash(456, 123);
330 assert_ne!(hash1, hash2);
331 assert_ne!(hash1, hash3);
332 }
333
334 #[test]
335 fn test_mimc_sponge_with_zero() {
336 let hasher = MimcHasher::default();
337 let hash1 = hasher.hash(0, 0);
338 let hash2 = hasher.hash(0, 1);
339 assert_ne!(hash1, hash2);
340 }
341
342 #[test]
343 fn test_mimc_hash_single() {
344 let hasher = MimcHasher::default();
345 let hash1 = hasher.hash_single(12345);
346 let hash2 = hasher.hash(12345, 0);
347 assert_eq!(hash1, hash2);
348 }
349
350 #[test]
351 fn test_mimc_feistel_permutation() {
352 let hasher = MimcHasher::default();
353 let (l1, r1) = hasher.mimc_feistel(100, 200, hasher.field_prime);
354 let (l2, r2) = hasher.mimc_feistel(100, 200, hasher.field_prime);
355 assert_eq!((l1, r1), (l2, r2));
356 }
357
358 #[test]
359 fn test_default_hasher_params() {
360 let hasher = MimcHasher::default();
361 assert_eq!(hasher.field_prime(), DEFAULT_FIELD_PRIME);
362 assert_eq!(hasher.num_rounds(), DEFAULT_ROUNDS);
363 }
364
365 #[test]
366 fn test_custom_hasher() {
367 let constants = vec![0u128; 20];
368 let hasher = MimcHasher::new(1000, 5, constants);
369 assert_eq!(hasher.field_prime(), 1000);
370 assert_eq!(hasher.num_rounds(), 5);
371 }
372
373 // Legacy API tests
374 #[test]
375 #[allow(deprecated)]
376 fn test_legacy_hasher_compatibility() {
377 let legacy_hash = Hasher::mimc_sponge(123, 456, DEFAULT_FIELD_PRIME);
378 let new_hash = MimcHasher::default().mimc_sponge(123, 456, DEFAULT_FIELD_PRIME);
379 assert_eq!(legacy_hash, new_hash);
380 }
381}