Skip to main content

arcana/rsa/
bigint.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
3
4// Index-based loops in this module deliberately mirror the reference
5// algorithm (parallel-array access `a[i]`/`b[i]`, or a position-indexed
6// packing/reduction state) and keep the constant-time data layout explicit;
7// the `iter().enumerate()` rewrite would obscure it. Documented, single-lint
8// allow per CLAUDE.md §6 (scoped to this module, not a blanket suppression).
9#![allow(clippy::needless_range_loop)]
10
11//! Big integer arithmetic for RSA (up to ~4096-bit numbers).
12//!
13//! Represents large integers as little-endian `Vec<u64>` limbs.
14//! Provides addition, subtraction, multiplication, division, modular
15//! exponentiation (Montgomery ladder), extended GCD, and Miller-Rabin
16//! primality. `BigInt` is the underlying storage for every component
17//! of [`super::rsa::RsaPublicKey`] and [`super::rsa::RsaSecretKey`]
18//! and the workhorse of every operation in [`super::pkcs1`],
19//! [`super::oaep`] and [`super::pss`].
20//!
21//! # Arithmetic layer (expert bricks — read before use)
22//!
23//! Part of arcana's public **arithmetic layer** (with
24//! [`crate::ecc::field`] and [`crate::ecc::curve`]) — structurally public
25//! because `BigInt` backs the fields of [`super::rsa::RsaPublicKey`] /
26//! [`super::rsa::RsaSecretKey`]. It is RSA-shaped glue, not a
27//! general-purpose bigint: heap `Vec<u64>` limbs, and — see the table
28//! below — **variable-time operations. Do not use it on secret values**
29//! outside the audited RSA paths until `T1-E` lands. Stability is
30//! best-effort pre-1.0 (`T1-E` will rework the core operations).
31//!
32//! # Side-channel posture
33//!
34//! Roadmap item **`T1-E`** (see `arcana/doc/sca/countermeasures/
35//! rsa.rst`): the operations below need a CT audit before the
36//! evaluation pass, with the same `core::hint::black_box` shielding
37//! pattern as [`super::super::ecc::field`] (commit `76191c1`).
38//!
39//! | Operation              | Risk                                                 | Action  |
40//! |------------------------|------------------------------------------------------|---------|
41//! | `cmp_to`               | Variable-iteration limb-by-limb compare leaks bits   | Rewrite to borrow-only branchless pattern |
42//! | `mont_mul`             | Conditional final subtract leaks (Walter 2002)       | Apply `black_box` mask shielding |
43//! | `mod_exp`              | Square-and-multiply must be square-always            | Validate Fermat ladder structure + `black_box` |
44//! | `mod_inv` (extended GCD)| Variable-time GCD historically Minerva target       | Prefer Fermat (`a^(p-2) mod p`) for prime moduli |
45//! | `sub` / `add`          | Borrow / carry propagation                           | Confirm fixed iteration count |
46//!
47//! Once `T1-E` lands the layers above (RSA-CRT decrypt, PKCS#1,
48//! OAEP, PSS) inherit a CT bigint base; combined with `T1-C`
49//! Aumüller and `T2-I` message blinding it gives the full
50//! evaluation-grade RSA stack.
51
52use core::cmp::Ordering;
53
54/// A big unsigned integer stored as little-endian 64-bit limbs.
55#[derive(Clone, Debug)]
56pub struct BigInt {
57    /// Limbs in little-endian order (`limbs[0]` is least significant).
58    pub limbs: Vec<u64>,
59}
60
61// ---------------------------------------------------------------------------
62// Construction helpers
63// ---------------------------------------------------------------------------
64
65impl BigInt {
66    /// Zero value.
67    pub fn zero() -> Self {
68        Self { limbs: vec![0] }
69    }
70
71    /// From a single u64.
72    pub fn from_u64(v: u64) -> Self {
73        Self { limbs: vec![v] }
74    }
75
76    /// From big-endian bytes (as in RSA wire format).
77    pub fn from_be_bytes(bytes: &[u8]) -> Self {
78        if bytes.is_empty() {
79            return Self::zero();
80        }
81        // Pad to multiple of 8
82        let padded_len = bytes.len().div_ceil(8) * 8;
83        let mut padded = vec![0u8; padded_len];
84        padded[padded_len - bytes.len()..].copy_from_slice(bytes);
85
86        let n_limbs = padded_len / 8;
87        let mut limbs = Vec::with_capacity(n_limbs);
88        for i in (0..n_limbs).rev() {
89            let off = i * 8;
90            let limb = u64::from_be_bytes([
91                padded[off],
92                padded[off + 1],
93                padded[off + 2],
94                padded[off + 3],
95                padded[off + 4],
96                padded[off + 5],
97                padded[off + 6],
98                padded[off + 7],
99            ]);
100            limbs.push(limb);
101        }
102        let mut r = Self { limbs };
103        r.trim();
104        r
105    }
106
107    /// Convert to big-endian bytes, padded to at least `min_len` bytes.
108    pub fn to_be_bytes(&self, min_len: usize) -> Vec<u8> {
109        let n = self.limbs.len();
110        let byte_len = n * 8;
111        let mut buf = vec![0u8; byte_len];
112        for (i, &limb) in self.limbs.iter().enumerate() {
113            let off = byte_len - (i + 1) * 8;
114            buf[off..off + 8].copy_from_slice(&limb.to_be_bytes());
115        }
116        // Strip leading zeros
117        let start = buf.iter().position(|&b| b != 0).unwrap_or(buf.len());
118        let significant = &buf[start..];
119        if significant.len() >= min_len {
120            significant.to_vec()
121        } else {
122            let mut out = vec![0u8; min_len];
123            out[min_len - significant.len()..].copy_from_slice(significant);
124            out
125        }
126    }
127
128    /// Number of significant bits.
129    pub fn bit_len(&self) -> usize {
130        let n = self.limbs.len();
131        if n == 0 {
132            return 0;
133        }
134        let top = self.limbs[n - 1];
135        if top == 0 && n == 1 {
136            return 0;
137        }
138        (n - 1) * 64 + (64 - top.leading_zeros() as usize)
139    }
140
141    /// Test whether bit `i` is set.
142    pub fn bit(&self, i: usize) -> bool {
143        let limb_idx = i / 64;
144        let bit_idx = i % 64;
145        if limb_idx >= self.limbs.len() {
146            false
147        } else {
148            (self.limbs[limb_idx] >> bit_idx) & 1 == 1
149        }
150    }
151
152    /// Set bit `i`.
153    pub fn set_bit(&mut self, i: usize) {
154        let limb_idx = i / 64;
155        let bit_idx = i % 64;
156        while self.limbs.len() <= limb_idx {
157            self.limbs.push(0);
158        }
159        self.limbs[limb_idx] |= 1u64 << bit_idx;
160    }
161
162    /// Is this number zero?
163    pub fn is_zero(&self) -> bool {
164        self.limbs.iter().all(|&l| l == 0)
165    }
166
167    /// Is this number even?
168    pub fn is_even(&self) -> bool {
169        self.limbs.first().is_none_or(|&l| l & 1 == 0)
170    }
171
172    /// Is this number odd?
173    pub fn is_odd(&self) -> bool {
174        !self.is_even()
175    }
176
177    /// Remove leading zero limbs (keep at least one).
178    fn trim(&mut self) {
179        while self.limbs.len() > 1 && *self.limbs.last().unwrap() == 0 {
180            self.limbs.pop();
181        }
182    }
183
184    /// Byte length of the modulus (for RSA octet-string conversion).
185    pub fn byte_len(&self) -> usize {
186        self.bit_len().div_ceil(8)
187    }
188
189    /// Generate a random BigInt with exactly `bits` bits using the provided RNG callback.
190    pub fn random(bits: usize, rng: &mut dyn FnMut(&mut [u8])) -> Self {
191        let byte_len = bits.div_ceil(8);
192        let mut buf = vec![0u8; byte_len];
193        rng(&mut buf);
194        // Set the top bit to ensure we get exactly `bits` bits.
195        let top_bit = (bits - 1) % 8;
196        // Clear bits above top_bit. When top_bit == 7, keep all 8 bits.
197        if top_bit < 7 {
198            buf[0] &= (1u8 << (top_bit + 1)) - 1;
199        }
200        buf[0] |= 1u8 << top_bit; // set top bit
201        Self::from_be_bytes(&buf)
202    }
203
204    /// Generate a random odd BigInt with exactly `bits` bits.
205    pub fn random_odd(bits: usize, rng: &mut dyn FnMut(&mut [u8])) -> Self {
206        let mut n = Self::random(bits, rng);
207        n.limbs[0] |= 1; // force odd
208        n
209    }
210}
211
212// ---------------------------------------------------------------------------
213// Comparison
214// ---------------------------------------------------------------------------
215
216impl PartialEq for BigInt {
217    fn eq(&self, other: &Self) -> bool {
218        self.cmp_to(other) == Ordering::Equal
219    }
220}
221impl Eq for BigInt {}
222
223impl PartialOrd for BigInt {
224    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
225        Some(self.cmp(other))
226    }
227}
228
229impl Ord for BigInt {
230    fn cmp(&self, other: &Self) -> Ordering {
231        self.cmp_to(other)
232    }
233}
234
235impl BigInt {
236    /// Compare `self` to `other` as unsigned big integers. Used by
237    /// the `Ord` / `PartialOrd` impls and exposed publicly so callers
238    /// can perform a comparison without allocating an `Ordering` via
239    /// the trait machinery in tight loops.
240    pub fn cmp_to(&self, other: &Self) -> Ordering {
241        let a_len = self.limbs.len();
242        let b_len = other.limbs.len();
243        let max_len = a_len.max(b_len);
244        for i in (0..max_len).rev() {
245            let a = if i < a_len { self.limbs[i] } else { 0 };
246            let b = if i < b_len { other.limbs[i] } else { 0 };
247            match a.cmp(&b) {
248                Ordering::Equal => continue,
249                ord => return ord,
250            }
251        }
252        Ordering::Equal
253    }
254}
255
256// ---------------------------------------------------------------------------
257// Addition
258// ---------------------------------------------------------------------------
259
260impl BigInt {
261    /// self + other
262    pub fn add(&self, other: &BigInt) -> BigInt {
263        let max_len = self.limbs.len().max(other.limbs.len());
264        let mut result = Vec::with_capacity(max_len + 1);
265        let mut carry: u64 = 0;
266        for i in 0..max_len {
267            let a = if i < self.limbs.len() { self.limbs[i] } else { 0 };
268            let b = if i < other.limbs.len() { other.limbs[i] } else { 0 };
269            let (sum1, c1) = a.overflowing_add(b);
270            let (sum2, c2) = sum1.overflowing_add(carry);
271            result.push(sum2);
272            carry = (c1 as u64) + (c2 as u64);
273        }
274        if carry > 0 {
275            result.push(carry);
276        }
277        let mut r = BigInt { limbs: result };
278        r.trim();
279        r
280    }
281
282    /// self + small (u64)
283    pub fn add_u64(&self, v: u64) -> BigInt {
284        self.add(&BigInt::from_u64(v))
285    }
286}
287
288// ---------------------------------------------------------------------------
289// Subtraction (assumes self >= other)
290// ---------------------------------------------------------------------------
291
292impl BigInt {
293    /// self - other  (panics if result would be negative)
294    pub fn sub(&self, other: &BigInt) -> BigInt {
295        debug_assert!(self >= other, "BigInt::sub: underflow");
296        let mut result = Vec::with_capacity(self.limbs.len());
297        let mut borrow: u64 = 0;
298        for i in 0..self.limbs.len() {
299            let a = self.limbs[i];
300            let b = if i < other.limbs.len() { other.limbs[i] } else { 0 };
301            let (diff1, b1) = a.overflowing_sub(b);
302            let (diff2, b2) = diff1.overflowing_sub(borrow);
303            result.push(diff2);
304            borrow = (b1 as u64) + (b2 as u64);
305        }
306        let mut r = BigInt { limbs: result };
307        r.trim();
308        r
309    }
310
311    /// self - 1
312    pub fn sub_one(&self) -> BigInt {
313        self.sub(&BigInt::from_u64(1))
314    }
315}
316
317// ---------------------------------------------------------------------------
318// Multiplication (schoolbook)
319// ---------------------------------------------------------------------------
320
321impl BigInt {
322    /// self * other (schoolbook O(n^2))
323    pub fn mul(&self, other: &BigInt) -> BigInt {
324        let n = self.limbs.len();
325        let m = other.limbs.len();
326        let mut result = vec![0u64; n + m];
327        for i in 0..n {
328            let mut carry: u64 = 0;
329            for j in 0..m {
330                let (lo, hi) = mul_u64(self.limbs[i], other.limbs[j]);
331                let (s1, c1) = result[i + j].overflowing_add(lo);
332                let (s2, c2) = s1.overflowing_add(carry);
333                result[i + j] = s2;
334                carry = hi + (c1 as u64) + (c2 as u64);
335            }
336            result[i + m] = carry;
337        }
338        let mut r = BigInt { limbs: result };
339        r.trim();
340        r
341    }
342}
343
344/// Multiply two u64 values, returning (lo, hi).
345#[inline]
346fn mul_u64(a: u64, b: u64) -> (u64, u64) {
347    let full = (a as u128) * (b as u128);
348    (full as u64, (full >> 64) as u64)
349}
350
351// ---------------------------------------------------------------------------
352// Division with remainder
353// ---------------------------------------------------------------------------
354
355impl BigInt {
356    /// (quotient, remainder) = self / divisor
357    /// Uses long division.
358    pub fn div_rem(&self, divisor: &BigInt) -> (BigInt, BigInt) {
359        assert!(!divisor.is_zero(), "BigInt: division by zero");
360        if self < divisor {
361            return (BigInt::zero(), self.clone());
362        }
363        if divisor.limbs.len() == 1 {
364            return self.div_rem_u64(divisor.limbs[0]);
365        }
366        self.div_rem_knuth(divisor)
367    }
368
369    /// Division by a single u64 limb.
370    fn div_rem_u64(&self, d: u64) -> (BigInt, BigInt) {
371        let mut rem: u128 = 0;
372        let mut quotient = vec![0u64; self.limbs.len()];
373        for i in (0..self.limbs.len()).rev() {
374            rem = (rem << 64) | (self.limbs[i] as u128);
375            quotient[i] = (rem / d as u128) as u64;
376            rem %= d as u128;
377        }
378        let mut q = BigInt { limbs: quotient };
379        q.trim();
380        (q, BigInt::from_u64(rem as u64))
381    }
382
383    /// Knuth's Algorithm D for multi-word division.
384    fn div_rem_knuth(&self, divisor: &BigInt) -> (BigInt, BigInt) {
385        // Normalize: shift so that top bit of divisor's leading limb is set.
386        let shift = divisor.limbs.last().unwrap().leading_zeros() as usize;
387        let a = self.shl(shift);
388        let b = divisor.shl(shift);
389
390        let n = b.limbs.len();
391        let m = a.limbs.len() - n;
392
393        let mut q_limbs = vec![0u64; m + 1];
394        // Working copy of dividend with extra limb
395        let mut u = a.limbs.clone();
396        if u.len() <= n + m {
397            u.resize(n + m + 1, 0);
398        }
399
400        let b_top = *b.limbs.last().unwrap() as u128;
401
402        for j in (0..=m).rev() {
403            // Estimate q_hat
404            let u_hi = ((u[j + n] as u128) << 64) | (u[j + n - 1] as u128);
405            let mut q_hat = u_hi / b_top;
406            let mut r_hat = u_hi % b_top;
407
408            // Refine estimate
409            if n >= 2 {
410                let b_second = b.limbs[n - 2] as u128;
411                while q_hat >= (1u128 << 64) || q_hat * b_second > (r_hat << 64) | (u[j + n - 2] as u128) {
412                    q_hat -= 1;
413                    r_hat += b_top;
414                    if r_hat >= (1u128 << 64) {
415                        break;
416                    }
417                }
418            }
419
420            // Multiply and subtract
421            let mut borrow: i128 = 0;
422            for i in 0..n {
423                let prod = q_hat * (b.limbs[i] as u128);
424                let diff = (u[j + i] as i128) - borrow - (prod as u64 as i128);
425                u[j + i] = diff as u64;
426                borrow = (prod >> 64) as i128 - (diff >> 64);
427            }
428            let diff = (u[j + n] as i128) - borrow;
429            u[j + n] = diff as u64;
430
431            q_limbs[j] = q_hat as u64;
432
433            // If we subtracted too much, add back
434            if diff < 0 {
435                q_limbs[j] -= 1;
436                let mut carry: u64 = 0;
437                for i in 0..n {
438                    let (s1, c1) = u[j + i].overflowing_add(b.limbs[i]);
439                    let (s2, c2) = s1.overflowing_add(carry);
440                    u[j + i] = s2;
441                    carry = (c1 as u64) + (c2 as u64);
442                }
443                u[j + n] = u[j + n].wrapping_add(carry);
444            }
445        }
446
447        let mut q = BigInt { limbs: q_limbs };
448        q.trim();
449        // Remainder: take first n limbs of u and shift back
450        u.truncate(n);
451        let mut r = BigInt { limbs: u };
452        r.trim();
453        r = r.shr(shift);
454        (q, r)
455    }
456
457    /// Left shift by `bits` bit positions.
458    pub fn shl(&self, bits: usize) -> BigInt {
459        if bits == 0 {
460            return self.clone();
461        }
462        let limb_shift = bits / 64;
463        let bit_shift = bits % 64;
464        let mut result = vec![0u64; self.limbs.len() + limb_shift + 1];
465        let mut carry: u64 = 0;
466        for i in 0..self.limbs.len() {
467            if bit_shift == 0 {
468                result[i + limb_shift] = self.limbs[i];
469            } else {
470                result[i + limb_shift] = (self.limbs[i] << bit_shift) | carry;
471                carry = self.limbs[i] >> (64 - bit_shift);
472            }
473        }
474        if carry != 0 {
475            result[self.limbs.len() + limb_shift] = carry;
476        }
477        let mut r = BigInt { limbs: result };
478        r.trim();
479        r
480    }
481
482    /// Right shift by `bits` bit positions.
483    pub fn shr(&self, bits: usize) -> BigInt {
484        if bits == 0 {
485            return self.clone();
486        }
487        let limb_shift = bits / 64;
488        let bit_shift = bits % 64;
489        if limb_shift >= self.limbs.len() {
490            return BigInt::zero();
491        }
492        let new_len = self.limbs.len() - limb_shift;
493        let mut result = vec![0u64; new_len];
494        for i in 0..new_len {
495            let src = i + limb_shift;
496            result[i] = if bit_shift == 0 {
497                self.limbs[src]
498            } else {
499                let lo = self.limbs[src] >> bit_shift;
500                let hi = if src + 1 < self.limbs.len() {
501                    self.limbs[src + 1] << (64 - bit_shift)
502                } else {
503                    0
504                };
505                lo | hi
506            };
507        }
508        let mut r = BigInt { limbs: result };
509        r.trim();
510        r
511    }
512
513    /// self mod other
514    pub fn rem(&self, modulus: &BigInt) -> BigInt {
515        self.div_rem(modulus).1
516    }
517}
518
519// ---------------------------------------------------------------------------
520// Montgomery multiplication
521// ---------------------------------------------------------------------------
522
523/// Parameters for Montgomery modular arithmetic.
524pub struct MontParams {
525    /// The modulus n.
526    pub n: BigInt,
527    /// Number of limbs.
528    pub n_limbs: usize,
529    /// -n^{-1} mod 2^64.
530    pub n_inv_neg: u64,
531    /// R = 2^{64*n_limbs} (not stored, implicit).
532    /// R mod n.
533    pub r_mod_n: BigInt,
534    /// R^2 mod n.
535    pub r2_mod_n: BigInt,
536}
537
538impl MontParams {
539    /// Create Montgomery parameters for modulus n.
540    pub fn new(n: &BigInt) -> Self {
541        let n_limbs = n.limbs.len();
542
543        // Compute -n^{-1} mod 2^64 using Newton's method.
544        // n must be odd for Montgomery to work.
545        debug_assert!(n.is_odd(), "Montgomery requires odd modulus");
546        let n0 = n.limbs[0];
547        let n_inv_neg = mod_inv_u64_neg(n0);
548
549        // R = 2^{64*n_limbs}, compute R mod n and R^2 mod n.
550        // R mod n: we can compute by (1 << (64*n_limbs)) mod n
551        let mut r_val = BigInt::zero();
552        r_val.set_bit(64 * n_limbs);
553        let r_mod_n = r_val.rem(n);
554
555        let r2_val = r_val.mul(&r_val);
556        let r2_mod_n = r2_val.rem(n);
557
558        MontParams {
559            n: n.clone(),
560            n_limbs,
561            n_inv_neg,
562            r_mod_n,
563            r2_mod_n,
564        }
565    }
566
567    /// Convert a into Montgomery form: a * R mod n.
568    pub fn to_mont(&self, a: &BigInt) -> BigInt {
569        self.mont_mul(a, &self.r2_mod_n)
570    }
571
572    /// Convert from Montgomery form: a * R^{-1} mod n.
573    pub fn from_mont(&self, a: &BigInt) -> BigInt {
574        self.mont_mul(a, &BigInt::from_u64(1))
575    }
576
577    /// Montgomery multiplication: (a * b * R^{-1}) mod n.
578    /// Uses the CIOS (Coarsely Integrated Operand Scanning) method.
579    pub fn mont_mul(&self, a: &BigInt, b: &BigInt) -> BigInt {
580        let n = self.n_limbs;
581        // Working array t with n+2 limbs (extra for carries).
582        let mut t = vec![0u64; n + 2];
583
584        for i in 0..n {
585            let bi = if i < b.limbs.len() { b.limbs[i] } else { 0 };
586            // t = t + a * b[i]
587            let mut carry: u64 = 0;
588            for j in 0..n {
589                let aj = if j < a.limbs.len() { a.limbs[j] } else { 0 };
590                let (lo, hi) = mul_u64(aj, bi);
591                let (s1, c1) = t[j].overflowing_add(lo);
592                let (s2, c2) = s1.overflowing_add(carry);
593                t[j] = s2;
594                carry = hi + (c1 as u64) + (c2 as u64);
595            }
596            let (s, c) = t[n].overflowing_add(carry);
597            t[n] = s;
598            t[n + 1] = c as u64;
599
600            // m = t[0] * n_inv_neg mod 2^64
601            let m = t[0].wrapping_mul(self.n_inv_neg);
602
603            // t = (t + m * N) >> 64
604            let mut carry: u64 = 0;
605            {
606                let (lo, hi) = mul_u64(m, self.n.limbs[0]);
607                let (s1, c1) = t[0].overflowing_add(lo);
608                let (_s2, c2) = s1.overflowing_add(carry);
609                // We discard t[0] (shifting right by 64).
610                carry = hi + (c1 as u64) + (c2 as u64);
611            }
612            for j in 1..n {
613                let nj = self.n.limbs[j];
614                let (lo, hi) = mul_u64(m, nj);
615                let (s1, c1) = t[j].overflowing_add(lo);
616                let (s2, c2) = s1.overflowing_add(carry);
617                t[j - 1] = s2;
618                carry = hi + (c1 as u64) + (c2 as u64);
619            }
620            let (s1, c1) = t[n].overflowing_add(carry);
621            t[n - 1] = s1;
622            t[n] = t[n + 1] + (c1 as u64);
623            t[n + 1] = 0;
624        }
625
626        // Result in t[0..n], may need final subtraction.
627        t.truncate(n + 1);
628        let mut result = BigInt { limbs: t };
629        result.trim();
630        if result >= self.n {
631            result = result.sub(&self.n);
632        }
633        result.trim();
634        result
635    }
636
637    /// Modular exponentiation via a Montgomery ladder. Computes
638    /// `base^exp mod n`.
639    ///
640    /// **Not constant-time yet.** The ladder is square-always in
641    /// *structure*, but it branches on the secret exponent bit
642    /// (`exp.bit(i)`) rather than using a constant-time conditional
643    /// swap, and `mont_mul`'s conditional final subtract is not
644    /// `black_box`-shielded (Walter 2002). Per the module header, do
645    /// not use on secret exponents until `T1-E` lands the CT rework.
646    /// `[CESTI]`
647    pub fn mod_exp(&self, base: &BigInt, exp: &BigInt) -> BigInt {
648        let base_mont = self.to_mont(base);
649        let bits = exp.bit_len();
650        if bits == 0 {
651            return BigInt::from_u64(1);
652        }
653
654        // Montgomery ladder (square-always in structure). NOT yet CT:
655        // the `if exp.bit(i)` below is a secret-dependent branch, not a
656        // constant-time swap — see the fn doc and `T1-E`.
657        let mut r0 = self.r_mod_n.clone(); // 1 in Montgomery form = R mod n
658        let mut r1 = base_mont.clone();
659
660        for i in (0..bits).rev() {
661            if exp.bit(i) {
662                r0 = self.mont_mul(&r0, &r1);
663                r1 = self.mont_mul(&r1, &r1);
664            } else {
665                r1 = self.mont_mul(&r0, &r1);
666                r0 = self.mont_mul(&r0, &r0);
667            }
668        }
669
670        self.from_mont(&r0)
671    }
672}
673
674/// Compute -(n^{-1}) mod 2^64 using Newton's method.
675fn mod_inv_u64_neg(n0: u64) -> u64 {
676    // We want x such that n0 * x ≡ -1 (mod 2^64), i.e., n0 * x + 1 ≡ 0 (mod 2^64).
677    // Newton: x_{i+1} = x_i * (2 - n0 * x_i) converges to n0^{-1} mod 2^64.
678    let mut x: u64 = 1; // initial guess: n0^{-1} ≡ 1 mod 2 (n0 is odd)
679    for _ in 0..6 {
680        // 6 iterations is enough for 64-bit convergence.
681        x = x.wrapping_mul(2u64.wrapping_sub(n0.wrapping_mul(x)));
682    }
683    // We want -n^{-1} mod 2^64.
684    x.wrapping_neg()
685}
686
687// ---------------------------------------------------------------------------
688// Modular exponentiation (convenience, non-Montgomery)
689// ---------------------------------------------------------------------------
690
691impl BigInt {
692    /// Modular exponentiation: `self^exp mod modulus`, via Montgomery
693    /// multiplication. **Not constant-time** on the exponent yet — see
694    /// [`MontParams::mod_exp`] and the module header (`T1-E`). `[CESTI]`
695    pub fn mod_exp(&self, exp: &BigInt, modulus: &BigInt) -> BigInt {
696        let params = MontParams::new(modulus);
697        params.mod_exp(self, exp)
698    }
699
700    /// Modular inverse: self^{-1} mod modulus, using extended GCD.
701    /// Returns None if gcd(self, modulus) != 1.
702    pub fn mod_inv(&self, modulus: &BigInt) -> Option<BigInt> {
703        // Extended Euclidean algorithm with signed coefficients.
704        let (g, x, _neg) = extended_gcd(self, modulus);
705        if g != BigInt::from_u64(1) {
706            return None;
707        }
708        Some(x)
709    }
710}
711
712/// Extended GCD returning (gcd, x, y) where a*x + b*y = gcd.
713/// The returned x is in range [0, b).
714fn extended_gcd(a: &BigInt, b: &BigInt) -> (BigInt, BigInt, bool) {
715    // We use an iterative version with signed bookkeeping.
716    // Instead of true signed BigInts, we track sign bits separately.
717
718    if a.is_zero() {
719        return (b.clone(), BigInt::zero(), false);
720    }
721
722    // Iterative extended GCD
723    let mut old_r = a.clone();
724    let mut r = b.clone();
725    let mut old_s = BigInt::from_u64(1);
726    let mut s = BigInt::zero();
727    let mut old_s_neg = false; // sign of old_s
728    let mut s_neg = false; // sign of s
729
730    while !r.is_zero() {
731        let (q, remainder) = old_r.div_rem(&r);
732
733        old_r = r;
734        r = remainder;
735
736        // new_s = old_s - q * s
737        let qs = q.mul(&s);
738        // We need signed subtraction: old_s_sign * old_s - s_sign * s * q
739        let (new_s, new_s_neg) = signed_sub(&old_s, old_s_neg, &qs, s_neg);
740        old_s = s;
741        old_s_neg = s_neg;
742        s = new_s;
743        s_neg = new_s_neg;
744    }
745
746    // old_s might be negative; if so, add b to get into range [0, b).
747    let x = if old_s_neg { b.sub(&old_s.rem(b)) } else { old_s.rem(b) };
748
749    (old_r, x, false)
750}
751
752/// Signed subtraction: (|a|, a_neg) - (|b|, b_neg) = (|result|, result_neg)
753fn signed_sub(a: &BigInt, a_neg: bool, b: &BigInt, b_neg: bool) -> (BigInt, bool) {
754    // a_val - b_val where a_val = (-1)^a_neg * a, b_val = (-1)^b_neg * b
755    // = (-1)^a_neg * a + (-1)^(!b_neg) * b
756    if a_neg == b_neg {
757        // Same sign: subtract magnitudes
758        if a >= b { (a.sub(b), a_neg) } else { (b.sub(a), !a_neg) }
759    } else {
760        // Different signs: add magnitudes, sign is a's sign
761        (a.add(b), a_neg)
762    }
763}
764
765// ---------------------------------------------------------------------------
766// Miller-Rabin primality test
767// ---------------------------------------------------------------------------
768
769impl BigInt {
770    /// Miller-Rabin primality test with `rounds` iterations.
771    /// Uses the provided RNG to generate random witnesses.
772    pub fn is_probably_prime(&self, rounds: usize, rng: &mut dyn FnMut(&mut [u8])) -> bool {
773        // Handle small cases.
774        if self.limbs.len() == 1 {
775            let v = self.limbs[0];
776            if v < 2 {
777                return false;
778            }
779            if v == 2 || v == 3 {
780                return true;
781            }
782            if v % 2 == 0 {
783                return false;
784            }
785        }
786        if self.is_even() {
787            return false;
788        }
789
790        let one = BigInt::from_u64(1);
791        let two = BigInt::from_u64(2);
792        let n_minus_1 = self.sub(&one);
793        let n_minus_2 = self.sub(&two);
794
795        // Write n-1 = 2^s * d with d odd.
796        let mut d = n_minus_1.clone();
797        let mut s: usize = 0;
798        while d.is_even() {
799            d = d.shr(1);
800            s += 1;
801        }
802
803        let mont = MontParams::new(self);
804
805        'next_round: for _ in 0..rounds {
806            // Pick random a in [2, n-2].
807            let a = loop {
808                let candidate = BigInt::random(self.bit_len(), rng);
809                if candidate >= two && candidate <= n_minus_2 {
810                    break candidate;
811                }
812            };
813
814            let mut x = mont.mod_exp(&a, &d);
815
816            if x == one || x == n_minus_1 {
817                continue 'next_round;
818            }
819
820            for _ in 0..s - 1 {
821                x = mont.mod_exp(&x, &two);
822                if x == n_minus_1 {
823                    continue 'next_round;
824                }
825            }
826
827            return false; // composite
828        }
829
830        true // probably prime
831    }
832
833    /// Generate a random probable prime of `bits` bits.
834    pub fn random_prime(bits: usize, rng: &mut dyn FnMut(&mut [u8])) -> BigInt {
835        loop {
836            let candidate = BigInt::random_odd(bits, rng);
837            // Quick trial division for small primes.
838            let small_primes: &[u64] = &[
839                3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103,
840                107, 109, 113,
841            ];
842            let mut skip = false;
843            for &p in small_primes {
844                let (_, rem) = candidate.div_rem(&BigInt::from_u64(p));
845                if rem.is_zero() {
846                    if candidate == BigInt::from_u64(p) {
847                        return candidate;
848                    }
849                    skip = true;
850                    break;
851                }
852            }
853            if skip {
854                continue;
855            }
856
857            // Miller-Rabin with sufficient rounds for the bit size.
858            let rounds = if bits >= 1024 { 4 } else { 8 };
859            if candidate.is_probably_prime(rounds, rng) {
860                return candidate;
861            }
862        }
863    }
864}
865
866// ---------------------------------------------------------------------------
867// Tests
868// ---------------------------------------------------------------------------
869
870#[cfg(test)]
871mod tests {
872    use super::*;
873
874    #[test]
875    fn test_add_sub() {
876        let a = BigInt::from_u64(u64::MAX);
877        let b = BigInt::from_u64(1);
878        let c = a.add(&b);
879        assert_eq!(c.limbs.len(), 2);
880        assert_eq!(c.limbs[0], 0);
881        assert_eq!(c.limbs[1], 1);
882        let d = c.sub(&b);
883        assert_eq!(d, a);
884    }
885
886    #[test]
887    fn test_mul() {
888        let a = BigInt::from_u64(0xFFFFFFFF);
889        let b = BigInt::from_u64(0xFFFFFFFF);
890        let c = a.mul(&b);
891        // 0xFFFFFFFF * 0xFFFFFFFF = 0xFFFFFFFE00000001
892        assert_eq!(c.limbs[0], 0xFFFFFFFE00000001);
893    }
894
895    #[test]
896    fn test_div_rem() {
897        let a = BigInt::from_u64(100);
898        let b = BigInt::from_u64(7);
899        let (q, r) = a.div_rem(&b);
900        assert_eq!(q, BigInt::from_u64(14));
901        assert_eq!(r, BigInt::from_u64(2));
902    }
903
904    #[test]
905    fn test_mod_exp() {
906        // 3^10 mod 7 = 59049 mod 7 = 4
907        let base = BigInt::from_u64(3);
908        let exp = BigInt::from_u64(10);
909        let modulus = BigInt::from_u64(7);
910        let result = base.mod_exp(&exp, &modulus);
911        assert_eq!(result, BigInt::from_u64(4));
912    }
913
914    #[test]
915    fn test_mod_inv() {
916        // 3^{-1} mod 7 = 5 (since 3*5 = 15 ≡ 1 mod 7)
917        let a = BigInt::from_u64(3);
918        let m = BigInt::from_u64(7);
919        let inv = a.mod_inv(&m).unwrap();
920        assert_eq!(inv, BigInt::from_u64(5));
921    }
922
923    #[test]
924    fn test_from_be_bytes_roundtrip() {
925        let bytes = vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01];
926        let n = BigInt::from_be_bytes(&bytes);
927        let out = n.to_be_bytes(8);
928        assert_eq!(out, bytes);
929    }
930
931    #[test]
932    fn test_bit_ops() {
933        let mut n = BigInt::zero();
934        n.set_bit(65);
935        assert!(n.bit(65));
936        assert!(!n.bit(64));
937        assert_eq!(n.bit_len(), 66);
938    }
939
940    fn test_rng() -> impl FnMut(&mut [u8]) {
941        let mut state: u64 = 0xdeadbeefcafebabe;
942        move |buf: &mut [u8]| {
943            for b in buf.iter_mut() {
944                state = state
945                    .wrapping_mul(6364136223846793005)
946                    .wrapping_add(1442695040888963407);
947                *b = (state >> 33) as u8;
948            }
949        }
950    }
951
952    #[test]
953    fn test_primality() {
954        let mut rng = test_rng();
955        // 7 is prime
956        assert!(BigInt::from_u64(7).is_probably_prime(10, &mut rng));
957        // 15 is not prime
958        assert!(!BigInt::from_u64(15).is_probably_prime(10, &mut rng));
959        // 104729 is prime
960        assert!(BigInt::from_u64(104729).is_probably_prime(10, &mut rng));
961    }
962}