Skip to main content

happy_cracking/crypto/
mathtools.rs

1#![allow(clippy::manual_is_multiple_of)]
2use anyhow::{Context, Result};
3use clap::Subcommand;
4use num_bigint::{BigInt, BigUint};
5use num_integer::Integer;
6use num_traits::One;
7
8#[derive(Subcommand)]
9pub enum MathAction {
10    #[command(about = "Calculate greatest common divisor")]
11    Gcd {
12        #[arg(help = "First number")]
13        a: String,
14        #[arg(help = "Second number")]
15        b: String,
16    },
17    #[command(about = "Calculate least common multiple")]
18    Lcm {
19        #[arg(help = "First number")]
20        a: String,
21        #[arg(help = "Second number")]
22        b: String,
23    },
24    #[command(about = "Calculate modular inverse (a^-1 mod m)")]
25    Modinv {
26        #[arg(help = "Value")]
27        a: String,
28        #[arg(help = "Modulus")]
29        m: String,
30    },
31    #[command(about = "Calculate modular exponentiation (base^exp mod m)")]
32    Modpow {
33        #[arg(help = "Base")]
34        base: String,
35        #[arg(help = "Exponent")]
36        exp: String,
37        #[arg(help = "Modulus")]
38        m: String,
39    },
40}
41
42pub fn run(action: MathAction) -> Result<()> {
43    match action {
44        MathAction::Gcd { a, b } => {
45            let a = a.parse::<u128>().context("Invalid number for a")?;
46            let b = b.parse::<u128>().context("Invalid number for b")?;
47            println!("{}", gcd(a, b));
48        }
49        MathAction::Lcm { a, b } => {
50            let a = a.parse::<u128>().context("Invalid number for a")?;
51            let b = b.parse::<u128>().context("Invalid number for b")?;
52            println!("{}", lcm(a, b)?);
53        }
54        MathAction::Modinv { a, m } => {
55            let a = a.parse::<u128>().context("Invalid number for a")?;
56            let m = m.parse::<u128>().context("Invalid number for m")?;
57            println!("{}", modinv(a, m)?);
58        }
59        MathAction::Modpow { base, exp, m } => {
60            let base = base.parse::<u128>().context("Invalid number for base")?;
61            let exp = exp.parse::<u128>().context("Invalid number for exp")?;
62            let m = m.parse::<u128>().context("Invalid number for m")?;
63            println!("{}", modpow(base, exp, m)?);
64        }
65    }
66    Ok(())
67}
68
69// Euclidean algorithm for greatest common divisor.
70pub fn gcd(mut a: u128, mut b: u128) -> u128 {
71    while b != 0 {
72        let t = b;
73        b = a % b;
74        a = t;
75    }
76    a
77}
78
79// Least common multiple via GCD.
80pub fn lcm(a: u128, b: u128) -> Result<u128> {
81    if a == 0 || b == 0 {
82        return Ok(0);
83    }
84    let g = gcd(a, b);
85    (a / g)
86        .checked_mul(b)
87        .ok_or_else(|| anyhow::anyhow!("LCM overflow"))
88}
89
90// Extended Euclidean algorithm for modular inverse.
91// Returns a^-1 mod m such that (a * result) mod m == 1.
92pub fn modinv(a: u128, m: u128) -> Result<u128> {
93    if m == 0 {
94        anyhow::bail!("Modulus must be non-zero");
95    }
96    if m == 1 {
97        return Ok(0);
98    }
99
100    // Use BigInt to prevent overflow when casting to i128,
101    // which happens if a or m > i128::MAX (2^127-1).
102    let a_big = BigInt::from(a);
103    let m_big = BigInt::from(m);
104
105    let extended = a_big.extended_gcd(&m_big);
106
107    if extended.gcd != BigInt::one() {
108        anyhow::bail!(
109            "Modular inverse does not exist (gcd({}, {}) = {})",
110            a,
111            m,
112            extended.gcd
113        );
114    }
115
116    // x might be negative, ensure positive result in [0, m)
117    let res = (extended.x % &m_big + &m_big) % &m_big;
118
119    // Result fits in u128 because m fits in u128
120    Ok(res.try_into().unwrap())
121}
122
123// Binary exponentiation for modular power.
124// Computes base^exp mod m.
125pub fn modpow(base: u128, exp: u128, m: u128) -> Result<u128> {
126    if m == 0 {
127        anyhow::bail!("Modulus must be non-zero");
128    }
129    if m == 1 {
130        return Ok(0);
131    }
132
133    // Optimization: if m fits in u64, use u128 for intermediate calculations
134    // to avoid BigUint allocation overhead.
135    if m <= u64::MAX as u128 {
136        return Ok(modpow_u64(base, exp, m as u64) as u128);
137    }
138
139    // Optimization: if m is odd and fits in u128 (but > u64::MAX), use Montgomery
140    // to avoid BigUint allocation overhead.
141    if m % 2 != 0 {
142        let mont = Montgomery::new(m)?;
143        let base_mont = mont.transform(base);
144        let res_mont = mont.pow(base_mont, exp);
145        let res = mont.reduce_from(res_mont);
146        return Ok(res);
147    }
148
149    // Use BigUint to prevent overflow during intermediate calculations (base * base)
150    let base_big = BigUint::from(base);
151    let exp_big = BigUint::from(exp);
152    let m_big = BigUint::from(m);
153
154    let res = base_big.modpow(&exp_big, &m_big);
155
156    // Result fits in u128 because res < m <= u128::MAX
157    Ok(res.try_into().unwrap())
158}
159
160// Optimized modular exponentiation for u64 modulus using u128 arithmetic.
161fn modpow_u64(base: u128, mut exp: u128, m: u64) -> u64 {
162    // If m is odd, use Montgomery multiplication to avoid slow division in the loop
163    if m % 2 != 0 {
164        let mont = Montgomery64::new(m).expect("m is odd");
165        // We need base mod m first
166        let base_val = (base % (m as u128)) as u64;
167        let base_mont = mont.transform(base_val);
168        let res_mont = mont.pow(base_mont, exp);
169        return mont.reduce_from(res_mont);
170    }
171
172    let m_u128 = m as u128;
173    let mut res: u128 = 1;
174    let mut base = base % m_u128;
175
176    while exp > 0 {
177        if exp & 1 == 1 {
178            res = (res * base) % m_u128;
179        }
180        base = (base * base) % m_u128;
181        exp >>= 1;
182    }
183    res as u64
184}
185
186#[derive(Debug, Clone, Copy)]
187pub struct Montgomery64 {
188    m: u64,
189    m_prime: u64, // -m^-1 mod 2^64
190    r2: u64,      // R^2 mod m
191}
192
193impl Montgomery64 {
194    pub fn new(m: u64) -> Result<Self> {
195        if m % 2 == 0 {
196            anyhow::bail!("Modulus must be odd");
197        }
198
199        // Calculate -m^-1 mod 2^64 using Newton's method
200        // Start with 1. 2^64 has 64 bits.
201        // x_{i+1} = x_i * (2 - m * x_i) mod 2^64
202        // 6 iterations: 1 -> 2 -> 4 -> 8 -> 16 -> 32 -> 64
203        let mut inv = 1u64;
204        for _ in 0..6 {
205            inv = inv.wrapping_mul(2u64.wrapping_sub(m.wrapping_mul(inv)));
206        }
207        let m_prime = 0u64.wrapping_sub(inv);
208
209        // Calculate R^2 mod m where R = 2^64
210        // R % m = (2^64) % m = (u64::MAX % m + 1) % m
211        let r_mod_m = (u64::MAX % m).wrapping_add(1) % m;
212        let r2 = ((r_mod_m as u128 * r_mod_m as u128) % m as u128) as u64;
213
214        Ok(Self { m, m_prime, r2 })
215    }
216
217    // Montgomery reduction: computes T * R^-1 mod m
218    // T is u128 product.
219    #[inline(always)]
220    pub fn reduce(&self, t: u128) -> u64 {
221        let m = self.m;
222        let m_prime = self.m_prime;
223
224        // m_factor = (t mod R) * m_prime mod R
225        let m_factor = (t as u64).wrapping_mul(m_prime);
226
227        // t_correction = m_factor * m
228        let t_correction = (m_factor as u128) * (m as u128);
229
230        // val = t + t_correction
231        // Since t < m*m and t_correction < R*m, sum fits in u128?
232        // Actually, t < m*R (if reducing product of two numbers < m, then t < m*m < m*R).
233        // But t could be larger if not from strict mul.
234        // Assuming strict mul (inputs < m), t < m^2.
235        // t_correction < R*m.
236        // sum < m^2 + R*m < 2*R*m.
237        // We divide by R (shift 64). Result < 2m.
238
239        let (val, overflow) = t.overflowing_add(t_correction);
240
241        // res = val / R = val >> 64
242        let mut res = (val >> 64) as u64;
243
244        // If val overflowed u128, it means there is a carry to bit 128.
245        // Divided by R (2^64), this carry adds 2^64 to the quotient.
246        // So real result is res + 2^64.
247        // Since we want result mod m, and m < 2^64, we can subtract m.
248        // res + 2^64 - m = res + (2^64 - m).
249        // In u64 arithmetic, this is res.wrapping_sub(m).
250        if overflow {
251            res = res.wrapping_sub(m);
252        } else if res >= m {
253            res -= m;
254        }
255        res
256    }
257
258    #[inline(always)]
259    pub fn mul(&self, a: u64, b: u64) -> u64 {
260        let prod = (a as u128) * (b as u128);
261        self.reduce(prod)
262    }
263
264    pub fn transform(&self, a: u64) -> u64 {
265        self.mul(a, self.r2)
266    }
267
268    #[allow(dead_code)]
269    pub fn reduce_from(&self, a: u64) -> u64 {
270        // To reduce from Montgomery form X*R, we compute X*R * R^-1 = X.
271        // reduce takes u128 T.
272        // We pass a as u128.
273        self.reduce(a as u128)
274    }
275
276    pub fn pow(&self, mut base: u64, mut exp: u128) -> u64 {
277        let mut res = self.transform(1);
278        while exp > 0 {
279            if exp % 2 == 1 {
280                res = self.mul(res, base);
281            }
282            base = self.mul(base, base);
283            exp /= 2;
284        }
285        res
286    }
287}
288
289// Helper for 128-bit widening multiplication: (a * b) -> (lo, hi)
290fn widening_mul_u128(a: u128, b: u128) -> (u128, u128) {
291    // Optimized for u64 decomposition to leverage hardware MUL if possible.
292    // This explicitly uses 64-bit multiplications extended to 128-bit,
293    // avoiding potential full 128-bit multiplication overheads in the compiler.
294    let al = a as u64;
295    let ah = (a >> 64) as u64;
296    let bl = b as u64;
297    let bh = (b >> 64) as u64;
298
299    let t0 = (al as u128) * (bl as u128);
300    let t1 = (al as u128) * (bh as u128);
301    let t2 = (ah as u128) * (bl as u128);
302    let t3 = (ah as u128) * (bh as u128);
303
304    let (mid, carry_mid) = t1.overflowing_add(t2);
305    // mid represents bits 64..192
306    let mid_lo = mid << 64; // bits 64..128
307    let mid_hi = mid >> 64; // bits 128..192
308
309    let (lo, carry_lo) = t0.overflowing_add(mid_lo);
310
311    let mut hi = t3 + mid_hi;
312    if carry_mid {
313        hi += 1 << 64;
314    }
315    if carry_lo {
316        hi += 1;
317    }
318
319    (lo, hi)
320}
321
322#[derive(Debug)]
323pub struct Montgomery {
324    m: u128,
325    m_prime: u128, // -m^-1 mod 2^128
326    r2: u128,      // R^2 mod m
327}
328
329impl Montgomery {
330    pub fn new(m: u128) -> Result<Self> {
331        if m % 2 == 0 {
332            anyhow::bail!("Modulus must be odd for Montgomery arithmetic");
333        }
334
335        // Calculate -m^-1 mod 2^128 using Newton's method
336        let mut inv = 1u128;
337        for _ in 0..7 {
338            inv = inv.wrapping_mul(2u128.wrapping_sub(m.wrapping_mul(inv)));
339        }
340        let m_prime = 0u128.wrapping_sub(inv);
341
342        // Calculate R^2 mod m where R = 2^128
343        // We compute R^2 mod m by doubling R mod m 128 times
344        let mut r2 = (u128::MAX % m + 1) % m; // R mod m
345
346        for _ in 0..128 {
347            // r2 = (r2 * 2) % m
348            let (mut val, overflow) = r2.overflowing_shl(1);
349            if overflow {
350                // val = (r2 * 2) - 2^128
351                // We want (r2 * 2) - m = val + 2^128 - m
352                val = val.wrapping_add(0u128.wrapping_sub(m));
353            } else if val >= m {
354                val -= m;
355            }
356            r2 = val;
357        }
358
359        Ok(Self { m, m_prime, r2 })
360    }
361
362    // Montgomery reduction: computes T * R^-1 mod m
363    pub fn reduce(&self, lo: u128, hi: u128) -> u128 {
364        let m = self.m;
365        let m_prime = self.m_prime;
366
367        let m_factor = lo.wrapping_mul(m_prime);
368        let (prod_lo, prod_hi) = widening_mul_u128(m_factor, m);
369
370        let (_, carry_lo) = lo.overflowing_add(prod_lo);
371        let (sum, carry1) = hi.overflowing_add(prod_hi);
372        let (mut res, carry2) = sum.overflowing_add(if carry_lo { 1 } else { 0 });
373        let carry = carry1 || carry2;
374
375        if carry {
376            res = res.wrapping_sub(m);
377        } else if res >= m {
378            res -= m;
379        }
380        res
381    }
382
383    // Multiplication: a * b * R^-1 mod m
384    pub fn mul(&self, a: u128, b: u128) -> u128 {
385        let (lo, hi) = widening_mul_u128(a, b);
386        self.reduce(lo, hi)
387    }
388
389    // Transform to Montgomery form: a * R mod m
390    pub fn transform(&self, a: u128) -> u128 {
391        self.mul(a, self.r2)
392    }
393
394    // Transform from Montgomery form: a * R^-1 mod m
395    // Only used for final result, so we can pass 0 for high part
396    #[allow(dead_code)]
397    pub fn reduce_from(&self, a: u128) -> u128 {
398        self.reduce(a, 0)
399    }
400
401    pub fn pow(&self, mut base: u128, mut exp: u128) -> u128 {
402        let mut res = self.transform(1);
403        while exp > 0 {
404            if exp % 2 == 1 {
405                res = self.mul(res, base);
406            }
407            base = self.mul(base, base);
408            exp /= 2;
409        }
410        res
411    }
412}