• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kaidokert / modmath-rs / 28725835473

05 Jul 2026 01:35AM UTC coverage: 98.698% (+0.1%) from 98.55%
28725835473

Pull #23

github

web-flow
Merge 3d73b5eb0 into 32599a022
Pull Request #23: v0.4.0: const-num-traits migration, CT typestate discipline, safegcd CT inverse

1735 of 1761 new or added lines in 14 files covered. (98.52%)

15 existing lines in 2 files now uncovered.

5989 of 6068 relevant lines covered (98.7%)

3089.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.9
/modmath/src/montgomery/basic_mont.rs
1
// Basic Montgomery arithmetic functions
2
// These require Copy trait but have minimal constraints
3

4
use crate::inv::basic_mod_inv;
5
use crate::parity::Parity;
6
use crate::wide_mul::WideMul;
7
use const_num_traits::Odd;
8
use subtle::Choice;
9

10
/// Methods for computing N' in Montgomery parameter computation
11
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12
pub enum NPrimeMethod {
13
    /// Trial search - O(R) complexity, simple but slow for large R.
14
    /// Only works when R fits in the type (R > N, smallest power of 2).
15
    TrialSearch,
16
    /// Extended Euclidean Algorithm - O(log R) complexity.
17
    /// Only works when R fits in the type (R > N, smallest power of 2).
18
    ExtendedEuclidean,
19
    /// Hensel's lifting - O(log R) complexity, optimized for R = 2^k.
20
    /// Only works when R fits in the type (R > N, smallest power of 2).
21
    HenselsLifting,
22
    /// Newton's method - O(log W) complexity, works with R = 2^W (full type width).
23
    /// Uses wrapping arithmetic so R never needs to be represented explicitly.
24
    /// The default. The wide-REDC path uses Newton unconditionally (it's the
25
    /// only N' method that fits R = 2^W). On the R>N path (where R is the
26
    /// smallest power of 2 above N rather than the full type width),
27
    /// `*_compute_montgomery_params_with_method` maps `Newton` to
28
    /// `ExtendedEuclidean` since Newton's R = 2^W assumption doesn't hold there.
29
    #[default]
30
    Newton,
31
}
32

33
/// Compute N' using trial search method - O(R) complexity
34
/// Finds N' such that modulus * N' ≡ -1 (mod R)
35
/// Returns None if N' cannot be found
36
fn compute_n_prime_trial_search<T>(modulus: T, r: T) -> Option<T>
22✔
37
where
22✔
38
    T: Copy
22✔
39
        + const_num_traits::Zero
22✔
40
        + const_num_traits::One
22✔
41
        + PartialEq
22✔
42
        + PartialOrd
22✔
43
        + core::ops::Add<Output = T>
22✔
44
        + core::ops::Sub<Output = T>
22✔
45
        + core::ops::Mul<Output = T>
22✔
46
        + core::ops::Rem<Output = T>,
22✔
47
{
48
    // We need to find N' where modulus * N' ≡ R - 1 (mod R)
49
    let target = r - T::one(); // This is -1 mod R
22✔
50

51
    // Simple O(R) trial search for N'. Adequate for small moduli; the
52
    // ExtendedEuclidean and HenselsLifting NPrimeMethod variants offer
53
    // O(log R) alternatives for larger moduli.
54
    let mut n_prime = T::one();
22✔
55
    loop {
56
        if (modulus * n_prime) % r == target {
238✔
57
            return Some(n_prime);
21✔
58
        }
217✔
59
        n_prime = n_prime + T::one();
217✔
60

61
        // Safety check to avoid infinite loop
62
        if n_prime >= r {
217✔
63
            return None; // Could not find N' - should not happen for valid inputs
1✔
64
        }
216✔
65
    }
66
}
22✔
67

68
/// Compute N' using Extended Euclidean Algorithm - O(log R) complexity
69
/// Finds N' such that modulus * N' ≡ -1 (mod R)
70
/// This is equivalent to N' ≡ -modulus^(-1) (mod R)
71
/// Returns None if modular inverse cannot be found
72
fn compute_n_prime_extended_euclidean<T>(modulus: T, r: T) -> Option<T>
36✔
73
where
36✔
74
    T: Copy
36✔
75
        + const_num_traits::Zero
36✔
76
        + const_num_traits::One
36✔
77
        + PartialEq
36✔
78
        + PartialOrd
36✔
79
        + core::ops::Add<Output = T>
36✔
80
        + core::ops::Sub<Output = T>
36✔
81
        + core::ops::Mul<Output = T>
36✔
82
        + core::ops::Rem<Output = T>
36✔
83
        + core::ops::Div<Output = T>,
36✔
84
{
85
    // We need to solve: modulus * N' ≡ -1 (mod R)
86
    // This is equivalent to: modulus * N' ≡ R - 1 (mod R)
87
    // So: N' ≡ (R - 1) * modulus^(-1) (mod R)
88
    // Or: N' ≡ -modulus^(-1) (mod R)
89

90
    // Use basic_mod_inv to find modulus^(-1) mod R
91
    if let Some(modulus_inv) = basic_mod_inv(modulus, r) {
36✔
92
        // N' = -modulus^(-1) mod R = R - modulus^(-1) mod R
93
        if modulus_inv == T::zero() {
31✔
94
            Some(r - T::one()) // Handle edge case where inverse is 0
×
95
        } else {
96
            Some(r - modulus_inv)
31✔
97
        }
98
    } else {
99
        None // Could not find modular inverse - gcd(modulus, R) should be 1 for valid Montgomery parameters
5✔
100
    }
101
}
36✔
102

103
/// Compute N' using Hensel's lifting - O(log R) complexity, optimized for R = 2^k
104
/// Finds N' such that modulus * N' ≡ -1 (mod R)
105
/// Uses Newton's method to iteratively lift from small powers to full R
106
/// Returns None if Hensel's lifting fails
107
fn compute_n_prime_hensels_lifting<T>(modulus: T, r: T, r_bits: usize) -> Option<T>
13✔
108
where
13✔
109
    T: Copy
13✔
110
        + const_num_traits::Zero
13✔
111
        + const_num_traits::One
13✔
112
        + PartialEq
13✔
113
        + PartialOrd
13✔
114
        + core::ops::Add<Output = T>
13✔
115
        + core::ops::Sub<Output = T>
13✔
116
        + core::ops::Mul<Output = T>
13✔
117
        + core::ops::Rem<Output = T>
13✔
118
        + core::ops::Shl<usize, Output = T>
13✔
119
        + core::ops::BitAnd<Output = T>,
13✔
120
{
121
    // Hensel's lifting for N' computation when R = 2^k
122
    // Start with base case: find N' such that modulus * N' ≡ -1 (mod 2)
123
    // Then iteratively lift to larger powers of 2
124

125
    // Base case: modulus * N' ≡ -1 ≡ 1 (mod 2)
126
    // Since modulus is odd (required for Montgomery), modulus ≡ 1 (mod 2)
127
    // So we need N' ≡ 1 (mod 2), hence N' starts as 1
128
    let mut n_prime = T::one();
13✔
129

130
    // Lift from 2^1 to 2^r_bits using Newton's method
131
    for k in 2..=r_bits {
44✔
132
        // We have: modulus * n_prime ≡ -1 (mod 2^(k-1))
133
        // We want: modulus * n_prime_new ≡ -1 (mod 2^k)
134

135
        // Newton iteration: x_new = x - f(x)/f'(x)
136
        // Where f(x) = modulus * x + 1
137
        // f'(x) = modulus
138
        // So: x_new = x - (modulus * x + 1) / modulus
139
        //     x_new = x - x - 1/modulus  (but we work mod powers of 2)
140

141
        let target_mod = T::one() << k; // 2^k
44✔
142
        let check_val = (modulus * n_prime + T::one()) % target_mod;
44✔
143

144
        if check_val != T::zero() {
44✔
145
            // Need to adjust n_prime
146
            // If modulus * n_prime + 1 = t * 2^(k-1) for odd t, add 2^(k-1) to n_prime
147
            let prev_power = T::one() << (k - 1); // 2^(k-1)
22✔
148

149
            if check_val == prev_power {
22✔
150
                n_prime = n_prime + prev_power;
22✔
151
            }
22✔
152
        }
22✔
153
    }
154

155
    // Final check and adjustment to ensure modulus * N' ≡ -1 (mod R)
156
    let final_check = (modulus * n_prime) % r;
13✔
157
    let target = r - T::one(); // -1 mod R
13✔
158

159
    if final_check != target {
13✔
160
        // This shouldn't happen with correct Hensel lifting, but safety check
161
        None // Hensel lifting failed to produce correct N'
×
162
    } else {
163
        // N' is already in [0, R) after Hensel lifting (starts at 1, accumulates powers of 2)
164
        Some(n_prime)
13✔
165
    }
166
}
13✔
167

168
/// Montgomery parameter computation (Basic)
169
/// Computes R, R^(-1) mod N, N', and R bit length for Montgomery arithmetic
170
/// Returns None if parameter computation fails
171
pub fn basic_compute_montgomery_params_with_method<T>(
71✔
172
    modulus: T,
71✔
173
    method: NPrimeMethod,
71✔
174
) -> Option<(T, T, T, usize)>
71✔
175
where
71✔
176
    T: Copy
71✔
177
        + const_num_traits::Zero
71✔
178
        + const_num_traits::One
71✔
179
        + PartialEq
71✔
180
        + PartialOrd
71✔
181
        + core::ops::Shl<usize, Output = T>
71✔
182
        + core::ops::Div<Output = T>
71✔
183
        + core::ops::Sub<Output = T>
71✔
184
        + core::ops::Mul<Output = T>
71✔
185
        + core::ops::Rem<Output = T>
71✔
186
        + core::ops::Add<Output = T>
71✔
187
        + core::ops::BitAnd<Output = T>,
71✔
188
{
189
    // Step 1: Find R = 2^k where R > modulus
190
    let mut r = T::one();
71✔
191
    let mut r_bits = 0usize;
71✔
192

193
    while r <= modulus {
372✔
194
        r = r << 1; // r *= 2
301✔
195
        r_bits += 1;
301✔
196
    }
301✔
197

198
    // Step 2: Compute R^(-1) mod modulus
199
    let r_inv = basic_mod_inv(r, modulus)?;
71✔
200

201
    // Step 3: Compute N' such that N * N' ≡ -1 (mod R) using selected method
202
    let n_prime = match method {
65✔
203
        NPrimeMethod::TrialSearch => compute_n_prime_trial_search(modulus, r)?,
21✔
204
        NPrimeMethod::ExtendedEuclidean => compute_n_prime_extended_euclidean(modulus, r)?,
19✔
205
        NPrimeMethod::HenselsLifting => compute_n_prime_hensels_lifting(modulus, r, r_bits)?,
13✔
206
        NPrimeMethod::Newton => {
207
            // In this R > N param path (smallest power of 2 above N),
208
            // delegate to ExtendedEuclidean which computes -N^{-1} mod R
209
            // using the same mathematical identity.
210
            compute_n_prime_extended_euclidean(modulus, r)?
12✔
211
        }
212
    };
213

214
    Some((r, r_inv, n_prime, r_bits))
65✔
215
}
71✔
216

217
/// Montgomery parameter computation (Basic) with default method
218
/// Computes R, R^(-1) mod N, N', and R bit length for Montgomery arithmetic
219
/// Uses the default NPrimeMethod (Newton).
220
/// Returns None if parameter computation fails
221
pub fn basic_compute_montgomery_params<T>(modulus: T) -> Option<(T, T, T, usize)>
14✔
222
where
14✔
223
    T: Copy
14✔
224
        + const_num_traits::Zero
14✔
225
        + const_num_traits::One
14✔
226
        + PartialEq
14✔
227
        + PartialOrd
14✔
228
        + core::ops::Shl<usize, Output = T>
14✔
229
        + core::ops::Div<Output = T>
14✔
230
        + core::ops::Sub<Output = T>
14✔
231
        + core::ops::Mul<Output = T>
14✔
232
        + core::ops::Rem<Output = T>
14✔
233
        + core::ops::Add<Output = T>
14✔
234
        + core::ops::BitAnd<Output = T>,
14✔
235
{
236
    basic_compute_montgomery_params_with_method(modulus, NPrimeMethod::default())
14✔
237
}
14✔
238

239
/// Convert to Montgomery form (Basic): a -> (a * R) mod N
240
pub fn basic_to_montgomery<T>(a: T, modulus: T, r: T) -> T
60✔
241
where
60✔
242
    T: core::cmp::PartialOrd
60✔
243
        + Copy
60✔
244
        + const_num_traits::Zero
60✔
245
        + const_num_traits::One
60✔
246
        + const_num_traits::ops::wrapping::WrappingAdd
60✔
247
        + const_num_traits::ops::wrapping::WrappingSub
60✔
248
        + core::ops::Add<Output = T>
60✔
249
        + core::ops::Sub<Output = T>
60✔
250
        + core::ops::Shr<usize, Output = T>
60✔
251
        + core::ops::Rem<Output = T>
60✔
252
        + crate::parity::Parity
60✔
253
        + crate::NonCt,
60✔
254
{
255
    crate::mul::basic_mod_mul(a, r, modulus)
60✔
256
}
60✔
257

258
/// Convert to Montgomery form (Basic, pre-reduced): a -> (a * R) mod N
259
/// Precondition: `a < modulus` and `r < modulus`. No `Rem` bound.
260
pub fn basic_to_montgomery_pr<T>(a: T, modulus: T, r: T) -> T
×
261
where
×
262
    T: core::cmp::PartialOrd
×
263
        + Copy
×
NEW
264
        + const_num_traits::Zero
×
NEW
265
        + const_num_traits::One
×
NEW
266
        + const_num_traits::ops::wrapping::WrappingAdd
×
NEW
267
        + const_num_traits::ops::wrapping::WrappingSub
×
NEW
268
        + core::ops::Add<Output = T>
×
NEW
269
        + core::ops::Sub<Output = T>
×
270
        + core::ops::Shr<usize, Output = T>
×
NEW
271
        + crate::parity::Parity
×
NEW
272
        + crate::NonCt,
×
273
{
274
    crate::mul::basic_mod_mul_pr(a, r, modulus)
×
275
}
×
276

277
/// Convert from Montgomery form (Basic): (a * R) -> a mod N
278
/// Uses Montgomery reduction algorithm with R > N semantics.
279
///
280
/// **Warning**: This function can overflow for large moduli where m * N exceeds
281
/// the type width. For overflow-free reduction, use [`wide_redc`] (call as
282
/// `wide_redc(a_mont, T::zero(), modulus, n_prime)`) which uses wide-REDC
283
/// with R = 2^W.
284
pub fn basic_from_montgomery<T>(a_mont: T, modulus: T, n_prime: T, r_bits: usize) -> T
42✔
285
where
42✔
286
    T: Copy
42✔
287
        + const_num_traits::Zero
42✔
288
        + const_num_traits::One
42✔
289
        + PartialOrd
42✔
290
        + core::ops::Mul<Output = T>
42✔
291
        + core::ops::Add<Output = T>
42✔
292
        + core::ops::Sub<Output = T>
42✔
293
        + core::ops::Shr<usize, Output = T>
42✔
294
        + core::ops::Shl<usize, Output = T>
42✔
295
        + core::ops::BitAnd<Output = T>,
42✔
296
{
297
    // Montgomery reduction algorithm:
298
    // Input: a_mont (Montgomery form), N (modulus), N', r_bits
299
    // 1. mask = 2^r_bits - 1
300
    // 2. m = ((a_mont & mask) * N') & mask  [only low bits, no expensive modulo!]
301
    // 3. t = (a_mont + m * N) >> r_bits     [bit shift, no division!]
302
    // 4. if t >= N then return t - N else return t
303

304
    // Fast path for R=1 (r_bits == 0): Montgomery reduction simplifies to conditional subtraction
305
    if r_bits == 0 {
42✔
306
        return if a_mont >= modulus {
×
307
            a_mont - modulus
×
308
        } else {
309
            a_mont
×
310
        };
311
    }
42✔
312

313
    let mask = (T::one() << r_bits) - T::one(); // mask = 2^r_bits - 1
42✔
314

315
    // Step 1: m = ((a_mont & mask) * N') & mask
316
    let m = ((a_mont & mask) * n_prime) & mask;
42✔
317

318
    // Step 2: t = (a_mont + m * N) >> r_bits
319
    // WARNING: m * N can overflow for large moduli (m < R, N < R, so
320
    // m*N can reach R^2). Use wide_redc(a_mont, T::zero(), modulus, n_prime)
321
    // for overflow-free reduction.
322
    let t = (a_mont + m * modulus) >> r_bits;
42✔
323

324
    // Step 3: Final reduction
325
    if t >= modulus { t - modulus } else { t }
42✔
326
}
42✔
327

328
/// Montgomery multiplication (Basic): (a * R) * (b * R) -> (a * b * R) mod N
329
///
330
/// **Warning**: This building-block function uses the R > N reduction path which
331
/// can overflow for large moduli (see [`basic_from_montgomery`] warning). For
332
/// overflow-free multiplication, use [`crate::basic::montgomery::mod_mul`]
333
/// which uses wide-REDC internally.
334
pub fn basic_montgomery_mul<T>(a_mont: T, b_mont: T, modulus: T, n_prime: T, r_bits: usize) -> T
4✔
335
where
4✔
336
    T: Copy
4✔
337
        + const_num_traits::Zero
4✔
338
        + const_num_traits::One
4✔
339
        + PartialOrd
4✔
340
        + core::ops::Mul<Output = T>
4✔
341
        + core::ops::Add<Output = T>
4✔
342
        + core::ops::Sub<Output = T>
4✔
343
        + core::ops::Rem<Output = T>
4✔
344
        + core::ops::Shr<usize, Output = T>
4✔
345
        + core::ops::Shl<usize, Output = T>
4✔
346
        + core::ops::BitAnd<Output = T>
4✔
347
        + const_num_traits::ops::wrapping::WrappingAdd
4✔
348
        + const_num_traits::ops::wrapping::WrappingSub
4✔
349
        + core::ops::Add<Output = T>
4✔
350
        + core::ops::Sub<Output = T>
4✔
351
        + crate::parity::Parity
4✔
352
        + crate::NonCt,
4✔
353
{
354
    // Montgomery multiplication algorithm:
355
    // Input: a_mont, b_mont (both in Montgomery form), modulus N, N', r_bits
356
    // 1. Compute product = a_mont * b_mont (mod N)
357
    // 2. Apply Montgomery reduction to get (a * b * R) mod N
358

359
    // Note: this R>N path uses double-and-add mod_mul (O(k)), which
360
    // defeats Montgomery's perf purpose; the m*N intermediate in
361
    // from_montgomery can also overflow at key sizes. For overflow-free
362
    // multiplication, route through wide-REDC / CIOS instead.
363
    let product = crate::mul::basic_mod_mul(a_mont, b_mont, modulus);
4✔
364
    basic_from_montgomery(product, modulus, n_prime, r_bits)
4✔
365
}
4✔
366

367
/// Montgomery multiplication (Basic, pre-reduced)
368
/// Precondition: `a_mont < modulus` and `b_mont < modulus`. No `Rem` bound.
369
pub fn basic_montgomery_mul_pr<T>(a_mont: T, b_mont: T, modulus: T, n_prime: T, r_bits: usize) -> T
×
370
where
×
371
    T: Copy
×
NEW
372
        + const_num_traits::Zero
×
NEW
373
        + const_num_traits::One
×
374
        + PartialOrd
×
375
        + core::ops::Mul<Output = T>
×
376
        + core::ops::Add<Output = T>
×
377
        + core::ops::Sub<Output = T>
×
378
        + core::ops::Shr<usize, Output = T>
×
379
        + core::ops::Shl<usize, Output = T>
×
380
        + core::ops::BitAnd<Output = T>
×
NEW
381
        + const_num_traits::ops::wrapping::WrappingAdd
×
NEW
382
        + const_num_traits::ops::wrapping::WrappingSub
×
NEW
383
        + core::ops::Add<Output = T>
×
NEW
384
        + core::ops::Sub<Output = T>
×
NEW
385
        + crate::parity::Parity
×
NEW
386
        + crate::NonCt,
×
387
{
388
    let product = crate::mul::basic_mod_mul_pr(a_mont, b_mont, modulus);
×
389
    basic_from_montgomery(product, modulus, n_prime, r_bits)
×
390
}
×
391

392
// ---------------------------------------------------------------------------
393
// Wide-REDC private helpers (R = 2^W, full type width)
394
// ---------------------------------------------------------------------------
395

396
/// Bit width of type T (e.g. 32 for u32, 128 for a 4×32-limb bigint).
397
pub const fn type_bit_width<T>() -> usize {
3,069✔
398
    core::mem::size_of::<T>() * 8
3,069✔
399
}
3,069✔
400

401
/// Modular doubling: (val + val) mod modulus, handling overflow.
402
///
403
/// **Variable-time.** Branches on the `overflow || doubled >= modulus`
404
/// magnitude check; used by [`compute_r_mod_n`] / [`compute_r2_mod_n`]
405
/// for the Nct precompute path where the modulus is public. For the Ct
406
/// path (secret modulus), see [`mod_double_ct`].
407
fn mod_double<T>(val: T, modulus: T) -> T
172,204✔
408
where
172,204✔
409
    T: Copy
172,204✔
410
        + PartialOrd
172,204✔
411
        + const_num_traits::ops::overflowing::OverflowingAdd
172,204✔
412
        + const_num_traits::WrappingSub
172,204✔
413
        + core::ops::Add<Output = T>
172,204✔
414
        + core::ops::Sub<Output = T>,
172,204✔
415
{
416
    let (doubled, overflow) = val.overflowing_add(val);
172,204✔
417
    if overflow || doubled >= modulus {
172,204✔
418
        doubled.wrapping_sub(modulus)
77,413✔
419
    } else {
420
        doubled
94,791✔
421
    }
422
}
172,204✔
423

424
/// CT modular doubling: `(val + val) mod modulus`, no value-dependent
425
/// branches. Always computes both the post-sub and pre-sub candidates,
426
/// selects via `subtle::Choice`.
427
///
428
/// Used by [`compute_r_mod_n_ct`] / [`compute_r2_mod_n_ct`] for the Ct
429
/// precompute path called from [`Field::new_odd_ct`](crate::Field::new_odd_ct).
430
fn mod_double_ct<T>(val: T, modulus: T) -> T
960✔
431
where
960✔
432
    T: Copy
960✔
433
        + const_num_traits::ops::overflowing::OverflowingAdd
960✔
434
        + const_num_traits::WrappingSub
960✔
435
        + core::ops::Add<Output = T>
960✔
436
        + core::ops::Sub<Output = T>
960✔
437
        + subtle::ConditionallySelectable
960✔
438
        + subtle::ConstantTimeLess,
960✔
439
{
440
    let (doubled, overflow) = val.overflowing_add(val);
960✔
441
    let sub_result = doubled.wrapping_sub(modulus);
960✔
442
    let doubled_ge_modulus = !doubled.ct_lt(&modulus);
960✔
443
    let needs_sub = Choice::from(overflow as u8) | doubled_ge_modulus;
960✔
444
    T::conditional_select(&doubled, &sub_result, needs_sub)
960✔
445
}
960✔
446

447
/// Newton's method for N' = -N^{-1} mod 2^W.
448
///
449
/// Invariant: after each iteration, `modulus * x ≡ 1 (mod 2^precision)`.
450
/// We return `0 - x` so that `modulus * N' ≡ -1 (mod 2^W)`.
451
pub fn compute_n_prime_newton<T>(modulus: T, w: usize) -> T
2,975✔
452
where
2,975✔
453
    T: Copy
2,975✔
454
        + const_num_traits::One
2,975✔
455
        + const_num_traits::Zero
2,975✔
456
        + const_num_traits::WrappingMul
2,975✔
457
        + const_num_traits::WrappingSub
2,975✔
458
        + const_num_traits::WrappingAdd
2,975✔
459
        + core::ops::Add<Output = T>
2,975✔
460
        + core::ops::Sub<Output = T>
2,975✔
461
        + core::ops::Mul<Output = T>,
2,975✔
462
{
463
    let two = T::one().wrapping_add(T::one());
2,975✔
464
    let mut x = T::one(); // modulus * 1 ≡ 1 (mod 2) for odd modulus
2,975✔
465
    let mut precision = 1usize;
2,975✔
466
    while precision < w {
16,985✔
467
        // x = x * (2 - modulus * x)   mod 2^(2*precision)
14,010✔
468
        x = x.wrapping_mul(two.wrapping_sub(modulus.wrapping_mul(x)));
14,010✔
469
        precision *= 2;
14,010✔
470
    }
14,010✔
471
    // N' = -x mod 2^W  (wrapping_sub from 0 gives two's complement negation)
472
    T::zero().wrapping_sub(x)
2,975✔
473
}
2,975✔
474

475
/// Compute (val * 2^w) mod N via w modular doublings.
476
///
477
/// **Variable-time** (delegates to [`mod_double`]). Used for the Nct
478
/// precompute path; for the Ct path see [`mod_exp2_ct`].
479
fn mod_exp2<T>(val: T, modulus: T, w: usize) -> T
5,886✔
480
where
5,886✔
481
    T: Copy
5,886✔
482
        + PartialEq
5,886✔
483
        + PartialOrd
5,886✔
484
        + const_num_traits::Zero
5,886✔
485
        + const_num_traits::One
5,886✔
486
        + const_num_traits::ops::overflowing::OverflowingAdd
5,886✔
487
        + const_num_traits::WrappingSub
5,886✔
488
        + core::ops::Add<Output = T>
5,886✔
489
        + core::ops::Sub<Output = T>,
5,886✔
490
{
491
    // For modulus == 1, any value mod 1 == 0
492
    if modulus == T::one() {
5,886✔
493
        return T::zero();
×
494
    }
5,886✔
495
    let mut result = val;
5,886✔
496
    for _ in 0..w {
172,200✔
497
        result = mod_double(result, modulus);
172,200✔
498
    }
172,200✔
499
    result
5,886✔
500
}
5,886✔
501

502
/// CT modular doubling iterated `w` times: `val · 2^w mod modulus`.
503
/// Constant-time over `val` and `modulus`. The `modulus == 1` edge
504
/// case (every value reduces to 0) is handled branchlessly via a
505
/// final `conditional_select` rather than the variable-time early
506
/// return in [`mod_exp2`].
507
fn mod_exp2_ct<T>(val: T, modulus: T, w: usize) -> T
24✔
508
where
24✔
509
    T: Copy
24✔
510
        + const_num_traits::Zero
24✔
511
        + const_num_traits::One
24✔
512
        + const_num_traits::ops::overflowing::OverflowingAdd
24✔
513
        + const_num_traits::WrappingSub
24✔
514
        + core::ops::Add<Output = T>
24✔
515
        + core::ops::Sub<Output = T>
24✔
516
        + subtle::ConditionallySelectable
24✔
517
        + subtle::ConstantTimeEq
24✔
518
        + subtle::ConstantTimeLess,
24✔
519
{
520
    let mut result = val;
24✔
521
    for _ in 0..w {
960✔
522
        result = mod_double_ct(result, modulus);
960✔
523
    }
960✔
524
    let m_is_one = modulus.ct_eq(&T::one());
24✔
525
    T::conditional_select(&result, &T::zero(), m_is_one)
24✔
526
}
24✔
527

528
/// Compute R mod N = 2^W mod N via W modular doublings starting from 1.
529
///
530
/// **Variable-time.** Used by [`Field::new_odd`](crate::Field::new_odd) for the Nct precompute
531
/// path (public modulus); the CT sibling [`compute_r_mod_n_ct`] is
532
/// used by [`Field::new_odd_ct`](crate::Field::new_odd_ct) when the modulus is secret.
533
pub fn compute_r_mod_n<T>(modulus: T, w: usize) -> T
2,945✔
534
where
2,945✔
535
    T: Copy
2,945✔
536
        + PartialEq
2,945✔
537
        + PartialOrd
2,945✔
538
        + const_num_traits::Zero
2,945✔
539
        + const_num_traits::One
2,945✔
540
        + const_num_traits::ops::overflowing::OverflowingAdd
2,945✔
541
        + const_num_traits::WrappingSub
2,945✔
542
        + core::ops::Add<Output = T>
2,945✔
543
        + core::ops::Sub<Output = T>,
2,945✔
544
{
545
    mod_exp2(T::one(), modulus, w)
2,945✔
546
}
2,945✔
547

548
/// CT version of [`compute_r_mod_n`]: `2^W mod modulus` with no
549
/// value-dependent branches. Used by [`Field::new_odd_ct`](crate::Field::new_odd_ct) for the
550
/// secret-modulus precompute path.
551
pub fn compute_r_mod_n_ct<T>(modulus: T, w: usize) -> T
12✔
552
where
12✔
553
    T: Copy
12✔
554
        + const_num_traits::Zero
12✔
555
        + const_num_traits::One
12✔
556
        + const_num_traits::ops::overflowing::OverflowingAdd
12✔
557
        + const_num_traits::WrappingSub
12✔
558
        + core::ops::Add<Output = T>
12✔
559
        + core::ops::Sub<Output = T>
12✔
560
        + subtle::ConditionallySelectable
12✔
561
        + subtle::ConstantTimeEq
12✔
562
        + subtle::ConstantTimeLess,
12✔
563
{
564
    mod_exp2_ct(T::one(), modulus, w)
12✔
565
}
12✔
566

567
/// Compute R^2 mod N via W more modular doublings from (R mod N).
568
///
569
/// **Variable-time.** See [`compute_r2_mod_n_ct`] for the CT sibling.
570
pub fn compute_r2_mod_n<T>(r_mod_n: T, modulus: T, w: usize) -> T
2,941✔
571
where
2,941✔
572
    T: Copy
2,941✔
573
        + PartialEq
2,941✔
574
        + PartialOrd
2,941✔
575
        + const_num_traits::Zero
2,941✔
576
        + const_num_traits::One
2,941✔
577
        + const_num_traits::ops::overflowing::OverflowingAdd
2,941✔
578
        + const_num_traits::WrappingSub
2,941✔
579
        + core::ops::Add<Output = T>
2,941✔
580
        + core::ops::Sub<Output = T>,
2,941✔
581
{
582
    mod_exp2(r_mod_n, modulus, w)
2,941✔
583
}
2,941✔
584

585
/// CT version of [`compute_r2_mod_n`]: `R² mod modulus` via W modular
586
/// doublings from `R mod N`. No value-dependent branches.
587
pub fn compute_r2_mod_n_ct<T>(r_mod_n: T, modulus: T, w: usize) -> T
12✔
588
where
12✔
589
    T: Copy
12✔
590
        + const_num_traits::Zero
12✔
591
        + const_num_traits::One
12✔
592
        + const_num_traits::ops::overflowing::OverflowingAdd
12✔
593
        + const_num_traits::WrappingSub
12✔
594
        + core::ops::Add<Output = T>
12✔
595
        + core::ops::Sub<Output = T>
12✔
596
        + subtle::ConditionallySelectable
12✔
597
        + subtle::ConstantTimeEq
12✔
598
        + subtle::ConstantTimeLess,
12✔
599
{
600
    mod_exp2_ct(r_mod_n, modulus, w)
12✔
601
}
12✔
602

603
/// Accumulate the high half with carries from low-half addition.
604
///
605
/// Given the low-half carry `carry1`, adds it to `result` and returns the
606
/// combined extra-bit flag (true if there was overflow from any addition).
607
///
608
/// Invariants maintained:
609
/// - `result` is in range [0, modulus + 1] on entry (sum of two values < modulus plus carry)
610
/// - Returns (result + carry1, extra_bit) where extra_bit indicates overflow
611
///
612
/// This is the variable-time path: branches on `carry1` and on `carry3`
613
/// via `||` short-circuit. Used by the NCT REDC functions. For the CT
614
/// REDC path, see `accumulate_high_half_carry_ct`.
615
fn accumulate_high_half_carry<T>(result: T, carry1: bool, carry2: bool) -> (T, bool)
28,524✔
616
where
28,524✔
617
    T: const_num_traits::One
28,524✔
618
        + const_num_traits::ops::overflowing::OverflowingAdd
28,524✔
619
        + core::ops::Add<Output = T>,
28,524✔
620
{
621
    if carry1 {
28,524✔
622
        let (r2, carry3) = result.overflowing_add(T::one());
21,320✔
623
        (r2, carry2 || carry3)
21,320✔
624
    } else {
625
        (result, carry2)
7,204✔
626
    }
627
}
28,524✔
628

629
/// Constant-time analog of [`accumulate_high_half_carry`].
630
///
631
/// Same semantics — adds `carry1` to `result` and returns the combined
632
/// extra-bit flag — but eliminates the value-dependent branches. The
633
/// addition is always performed; the result is selected branchlessly via
634
/// `subtle::ConditionallySelectable`. The boolean carry combination uses
635
/// bitwise ops (`&`, `|`) on `subtle::Choice` rather than `&&` / `||` so
636
/// the new carry computation does not short-circuit on operand value.
637
///
638
/// Called by the CT REDC functions (`wide_redc_ct`, `strict_wide_redc_ct`).
639
fn accumulate_high_half_carry_ct<T>(result: T, carry1: bool, carry2: bool) -> (T, bool)
71,643✔
640
where
71,643✔
641
    T: const_num_traits::One
71,643✔
642
        + const_num_traits::ops::overflowing::OverflowingAdd
71,643✔
643
        + core::ops::Add<Output = T>
71,643✔
644
        + subtle::ConditionallySelectable,
71,643✔
645
{
646
    let c1 = Choice::from(carry1 as u8);
71,643✔
647
    let c2 = Choice::from(carry2 as u8);
71,643✔
648
    // Always compute the addition; branchlessly choose whether to keep it.
649
    let (r2, carry3) = result.overflowing_add(T::one());
71,643✔
650
    let c3 = Choice::from(carry3 as u8);
71,643✔
651
    let chosen = T::conditional_select(&result, &r2, c1);
71,643✔
652
    // new_carry = carry2 | (carry1 & carry3) — bitwise, no short-circuit.
653
    let new_carry: bool = (c2 | (c1 & c3)).into();
71,643✔
654
    (chosen, new_carry)
71,643✔
655
}
71,643✔
656

657
/// REDC on a double-width input (t_lo, t_hi) — variable-time.
658
///
659
/// Computes  (t_lo + t_hi * 2^W) * R^{-1}  mod N.
660
///
661
/// Final reduction is a predicted branch. Timing leaks operand magnitude.
662
/// Use [`wide_redc_ct`] in constant-time-sensitive contexts (signing keys,
663
/// scalar multiplication on secret data). For verify/public-key paths the
664
/// branched version is significantly faster — on Cortex-M3 the branchless
665
/// finalize is ~6× slower because the cond-sub branch is highly predictable
666
/// while the branchless mask construction does limb-wise work every call.
667
///
668
/// Invariants:
669
/// - Inputs t_lo, t_hi represent a value T < N * R (product of two values < N)
670
/// - modulus is odd and non-zero
671
/// - n_prime = -N^{-1} mod 2^W
672
pub fn wide_redc<T>(t_lo: T, t_hi: T, modulus: T, n_prime: T) -> T
24,003✔
673
where
24,003✔
674
    T: Copy
24,003✔
675
        + const_num_traits::Zero
24,003✔
676
        + const_num_traits::One
24,003✔
677
        + PartialOrd
24,003✔
678
        + WideMul
24,003✔
679
        + const_num_traits::ops::overflowing::OverflowingAdd
24,003✔
680
        + const_num_traits::WrappingMul
24,003✔
681
        + const_num_traits::WrappingSub
24,003✔
682
        + core::ops::Add<Output = T>
24,003✔
683
        + core::ops::Sub<Output = T>
24,003✔
684
        + core::ops::Mul<Output = T>,
24,003✔
685
{
686
    // m = t_lo * N'  (mod 2^W -- wrapping mul gives that for free)
687
    let m = t_lo.wrapping_mul(n_prime);
24,003✔
688

689
    // (m_lo, m_hi) = m * modulus  (full double-width product)
690
    let (m_lo, m_hi) = m.wide_mul(&modulus);
24,003✔
691

692
    // low half:  t_lo + m_lo  -- the low W bits cancel by construction,
693
    // we only need the carry.
694
    let (_discard_lo, carry1) = t_lo.overflowing_add(m_lo);
24,003✔
695

696
    // high half:  t_hi + m_hi + carry1
697
    let (result, carry2) = t_hi.overflowing_add(m_hi);
24,003✔
698
    let (result, extra_bit) = accumulate_high_half_carry(result, carry1, carry2);
24,003✔
699

700
    if extra_bit || result >= modulus {
24,003✔
701
        result.wrapping_sub(modulus)
3,391✔
702
    } else {
703
        result
20,612✔
704
    }
705
}
24,003✔
706

707
/// REDC on a double-width input — variable-time, reference-based inputs.
708
///
709
/// Same algorithm as [`wide_redc`] but takes all operands by reference,
710
/// avoiding the per-call value copy. For `Copy` types the compiler
711
/// register-passes either way; the by-ref form matters for non-`Copy`
712
/// bigint backends where each value pass would clone.
713
///
714
/// Drops the `Copy` bound — usable with backends that don't impl it.
715
pub fn strict_wide_redc<T>(t_lo: &T, t_hi: &T, modulus: &T, n_prime: &T) -> T
3,497✔
716
where
3,497✔
717
    T: Copy
3,497✔
718
        + const_num_traits::Zero
3,497✔
719
        + const_num_traits::One
3,497✔
720
        + PartialOrd
3,497✔
721
        + WideMul
3,497✔
722
        + const_num_traits::ops::overflowing::OverflowingAdd
3,497✔
723
        + const_num_traits::WrappingMul
3,497✔
724
        + const_num_traits::WrappingSub
3,497✔
725
        + core::ops::Add<Output = T>
3,497✔
726
        + core::ops::Sub<Output = T>
3,497✔
727
        + core::ops::Mul<Output = T>,
3,497✔
728
{
729
    let m = (*t_lo).wrapping_mul(*n_prime);
3,497✔
730
    let (m_lo, m_hi) = m.wide_mul(modulus);
3,497✔
731
    let (_discard_lo, carry1) = (*t_lo).overflowing_add(m_lo);
3,497✔
732
    let (result, carry2) = (*t_hi).overflowing_add(m_hi);
3,497✔
733
    let (result, extra_bit) = accumulate_high_half_carry(result, carry1, carry2);
3,497✔
734

735
    if extra_bit || &result >= modulus {
3,497✔
736
        result.wrapping_sub(*modulus)
1,665✔
737
    } else {
738
        result
1,832✔
739
    }
740
}
3,497✔
741

742
/// REDC on a double-width input (t_lo, t_hi) — constant-time finalize.
743
///
744
/// Same algorithm as [`wide_redc`] but performs the final reduction step
745
/// branchlessly via `subtle::ConditionallySelectable` and
746
/// `subtle::ConstantTimeLess`, removing the operand-magnitude side-channel
747
/// on the `result >= modulus` comparison.
748
///
749
/// Use this in CT-sensitive paths (private-key operations, secret scalar
750
/// multiplication). On platforms with branch prediction the branchless
751
/// finalize is a few percent slower than [`wide_redc`]; on simple cores
752
/// (Cortex-M0/M3) the gap is much larger (~5–6×). Choose at the call site.
753
pub fn wide_redc_ct<T>(t_lo: T, t_hi: T, modulus: T, n_prime: T) -> T
67,122✔
754
where
67,122✔
755
    T: Copy
67,122✔
756
        + const_num_traits::Zero
67,122✔
757
        + const_num_traits::One
67,122✔
758
        + WideMul
67,122✔
759
        + const_num_traits::ops::overflowing::OverflowingAdd
67,122✔
760
        + const_num_traits::WrappingMul
67,122✔
761
        + const_num_traits::WrappingSub
67,122✔
762
        + core::ops::Add<Output = T>
67,122✔
763
        + core::ops::Sub<Output = T>
67,122✔
764
        + core::ops::Mul<Output = T>
67,122✔
765
        + subtle::ConditionallySelectable
67,122✔
766
        + subtle::ConstantTimeLess,
67,122✔
767
{
768
    let m = t_lo.wrapping_mul(n_prime);
67,122✔
769
    let (m_lo, m_hi) = m.wide_mul(&modulus);
67,122✔
770
    let (_discard_lo, carry1) = t_lo.overflowing_add(m_lo);
67,122✔
771
    let (result, carry2) = t_hi.overflowing_add(m_hi);
67,122✔
772
    let (result, extra_bit) = accumulate_high_half_carry_ct(result, carry1, carry2);
67,122✔
773

774
    // Branchless final reduction: needs_sub = extra_bit | !(result < modulus)
775
    let sub_result = result.wrapping_sub(modulus);
67,122✔
776
    let result_lt_modulus = result.ct_lt(&modulus);
67,122✔
777
    let needs_sub = Choice::from(extra_bit as u8) | !result_lt_modulus;
67,122✔
778
    T::conditional_select(&result, &sub_result, needs_sub)
67,122✔
779
}
67,122✔
780

781
/// REDC on a double-width input — constant-time finalize, reference-based inputs.
782
///
783
/// Same algorithm as [`wide_redc_ct`] but takes all operands by reference,
784
/// avoiding the per-call value copy. See [`strict_wide_redc`] for the
785
/// rationale on dropping `Copy`.
786
pub fn strict_wide_redc_ct<T>(t_lo: &T, t_hi: &T, modulus: &T, n_prime: &T) -> T
3,497✔
787
where
3,497✔
788
    T: Copy
3,497✔
789
        + const_num_traits::Zero
3,497✔
790
        + const_num_traits::One
3,497✔
791
        + WideMul
3,497✔
792
        + const_num_traits::ops::overflowing::OverflowingAdd
3,497✔
793
        + const_num_traits::WrappingMul
3,497✔
794
        + const_num_traits::WrappingSub
3,497✔
795
        + core::ops::Add<Output = T>
3,497✔
796
        + core::ops::Sub<Output = T>
3,497✔
797
        + core::ops::Mul<Output = T>
3,497✔
798
        + subtle::ConditionallySelectable
3,497✔
799
        + subtle::ConstantTimeLess,
3,497✔
800
{
801
    let m = (*t_lo).wrapping_mul(*n_prime);
3,497✔
802
    let (m_lo, m_hi) = m.wide_mul(modulus);
3,497✔
803
    let (_discard_lo, carry1) = (*t_lo).overflowing_add(m_lo);
3,497✔
804
    let (result, carry2) = (*t_hi).overflowing_add(m_hi);
3,497✔
805
    let (result, extra_bit) = accumulate_high_half_carry_ct(result, carry1, carry2);
3,497✔
806

807
    let sub_result = result.wrapping_sub(*modulus);
3,497✔
808
    let result_lt_modulus = result.ct_lt(modulus);
3,497✔
809
    let needs_sub = Choice::from(extra_bit as u8) | !result_lt_modulus;
3,497✔
810
    T::conditional_select(&result, &sub_result, needs_sub)
3,497✔
811
}
3,497✔
812

813
// ---------------------------------------------------------------------------
814
// Wide-REDC Montgomery multiply helper
815
// ---------------------------------------------------------------------------
816

817
/// Montgomery multiplication using wide REDC: REDC(a_mont * b_mont).
818
pub fn wide_montgomery_mul<T>(a_mont: T, b_mont: T, modulus: T, n_prime: T) -> T
9,859✔
819
where
9,859✔
820
    T: Copy
9,859✔
821
        + const_num_traits::Zero
9,859✔
822
        + const_num_traits::One
9,859✔
823
        + PartialOrd
9,859✔
824
        + WideMul
9,859✔
825
        + const_num_traits::ops::overflowing::OverflowingAdd
9,859✔
826
        + const_num_traits::WrappingMul
9,859✔
827
        + const_num_traits::WrappingSub
9,859✔
828
        + core::ops::Add<Output = T>
9,859✔
829
        + core::ops::Sub<Output = T>
9,859✔
830
        + core::ops::Mul<Output = T>,
9,859✔
831
{
832
    let (lo, hi) = a_mont.wide_mul(&b_mont);
9,859✔
833
    wide_redc(lo, hi, modulus, n_prime)
9,859✔
834
}
9,859✔
835

836
/// Montgomery multiplication using wide REDC — constant-time finalize.
837
///
838
/// Same shape as [`wide_montgomery_mul`] but routes through [`wide_redc_ct`].
839
/// Use in CT-sensitive paths.
840
pub fn wide_montgomery_mul_ct<T>(a_mont: T, b_mont: T, modulus: T, n_prime: T) -> T
58,415✔
841
where
58,415✔
842
    T: Copy
58,415✔
843
        + const_num_traits::Zero
58,415✔
844
        + const_num_traits::One
58,415✔
845
        + WideMul
58,415✔
846
        + const_num_traits::ops::overflowing::OverflowingAdd
58,415✔
847
        + const_num_traits::WrappingMul
58,415✔
848
        + const_num_traits::WrappingSub
58,415✔
849
        + core::ops::Add<Output = T>
58,415✔
850
        + core::ops::Sub<Output = T>
58,415✔
851
        + core::ops::Mul<Output = T>
58,415✔
852
        + subtle::ConditionallySelectable
58,415✔
853
        + subtle::ConstantTimeLess,
58,415✔
854
{
855
    let (lo, hi) = a_mont.wide_mul(&b_mont);
58,415✔
856
    wide_redc_ct(lo, hi, modulus, n_prime)
58,415✔
857
}
58,415✔
858

859
/// Montgomery multiplication using wide REDC — reference-based inputs.
860
///
861
/// Same shape as [`wide_montgomery_mul`] but takes operands by reference,
862
/// avoiding per-call copies on non-Copy bigint backends. Delegates to
863
/// [`strict_wide_redc`] for the reduction.
864
pub fn strict_wide_montgomery_mul<T>(a_mont: &T, b_mont: &T, modulus: &T, n_prime: &T) -> T
169✔
865
where
169✔
866
    T: Copy
169✔
867
        + const_num_traits::Zero
169✔
868
        + const_num_traits::One
169✔
869
        + PartialOrd
169✔
870
        + WideMul
169✔
871
        + const_num_traits::ops::overflowing::OverflowingAdd
169✔
872
        + const_num_traits::WrappingMul
169✔
873
        + const_num_traits::WrappingSub
169✔
874
        + core::ops::Add<Output = T>
169✔
875
        + core::ops::Sub<Output = T>
169✔
876
        + core::ops::Mul<Output = T>,
169✔
877
{
878
    let (lo, hi) = a_mont.wide_mul(b_mont);
169✔
879
    strict_wide_redc(&lo, &hi, modulus, n_prime)
169✔
880
}
169✔
881

882
/// Montgomery multiplication using wide REDC — constant-time finalize,
883
/// reference-based inputs.
884
///
885
/// Same shape as [`wide_montgomery_mul_ct`] but takes operands by reference.
886
/// Delegates to [`strict_wide_redc_ct`].
887
pub fn strict_wide_montgomery_mul_ct<T>(a_mont: &T, b_mont: &T, modulus: &T, n_prime: &T) -> T
169✔
888
where
169✔
889
    T: Copy
169✔
890
        + const_num_traits::Zero
169✔
891
        + const_num_traits::One
169✔
892
        + WideMul
169✔
893
        + const_num_traits::ops::overflowing::OverflowingAdd
169✔
894
        + const_num_traits::WrappingMul
169✔
895
        + const_num_traits::WrappingSub
169✔
896
        + core::ops::Add<Output = T>
169✔
897
        + core::ops::Sub<Output = T>
169✔
898
        + core::ops::Mul<Output = T>
169✔
899
        + subtle::ConditionallySelectable
169✔
900
        + subtle::ConstantTimeLess,
169✔
901
{
902
    let (lo, hi) = a_mont.wide_mul(b_mont);
169✔
903
    strict_wide_redc_ct(&lo, &hi, modulus, n_prime)
169✔
904
}
169✔
905

906
/// Wide multiply-accumulate: `(acc_lo, acc_hi) += a * b`.
907
///
908
/// Produces `a * b` as a double-width pair and folds it into the
909
/// caller-held accumulator. Result is **not reduced** — call
910
/// [`wide_redc`] once at the end of accumulation.
911
///
912
/// # Bound
913
///
914
/// Caller must keep the accumulator within `2·WIDTH(T)`. With Mont-form
915
/// `a, b ∈ [0, q)`, summing `N` products requires `N ≤ R/q` where
916
/// `R = 2^WIDTH(T)`. Out-of-bound use silently wraps the high word.
917
pub fn wide_montgomery_mul_acc<T>(acc_lo: T, acc_hi: T, a: T, b: T) -> (T, T)
7,786✔
918
where
7,786✔
919
    T: Copy
7,786✔
920
        + const_num_traits::One
7,786✔
921
        + WideMul
7,786✔
922
        + const_num_traits::ops::overflowing::OverflowingAdd
7,786✔
923
        + core::ops::Add<Output = T>,
7,786✔
924
{
925
    let (m_lo, m_hi) = a.wide_mul(&b);
7,786✔
926
    let (new_lo, carry1) = acc_lo.overflowing_add(m_lo);
7,786✔
927
    let (sum_hi, _) = acc_hi.overflowing_add(m_hi);
7,786✔
928
    // Carry-out is discarded under the documented `N ≤ R/q` bound.
929
    let new_hi = if carry1 {
7,786✔
930
        let (r, _) = sum_hi.overflowing_add(T::one());
1,324✔
931
        r
1,324✔
932
    } else {
933
        sum_hi
6,462✔
934
    };
935
    (new_lo, new_hi)
7,786✔
936
}
7,786✔
937

938
/// Wide multiply-accumulate — constant-time carry propagation.
939
///
940
/// Same shape as [`wide_montgomery_mul_acc`] but routes the carry
941
/// combination through `accumulate_high_half_carry_ct` so the
942
/// per-term cost has no value-dependent branches. Use in CT-sensitive
943
/// paths; pair with [`wide_redc_ct`] for the final reduction.
944
///
945
/// Same bound as [`wide_montgomery_mul_acc`] — caller-verified `N ≤ R/q`.
946
pub fn wide_montgomery_mul_acc_ct<T>(acc_lo: T, acc_hi: T, a: T, b: T) -> (T, T)
7,605✔
947
where
7,605✔
948
    T: Copy
7,605✔
949
        + const_num_traits::One
7,605✔
950
        + WideMul
7,605✔
951
        + const_num_traits::ops::overflowing::OverflowingAdd
7,605✔
952
        + core::ops::Add<Output = T>
7,605✔
953
        + subtle::ConditionallySelectable,
7,605✔
954
{
955
    let (m_lo, m_hi) = a.wide_mul(&b);
7,605✔
956
    let (new_lo, carry1) = acc_lo.overflowing_add(m_lo);
7,605✔
957
    let (sum_hi, _) = acc_hi.overflowing_add(m_hi);
7,605✔
958
    let (r, _) = sum_hi.overflowing_add(T::one());
7,605✔
959
    let new_hi = T::conditional_select(&sum_hi, &r, subtle::Choice::from(carry1 as u8));
7,605✔
960
    (new_lo, new_hi)
7,605✔
961
}
7,605✔
962

963
/// Wide multiply-accumulate — reference-based inputs.
964
///
965
/// Same shape as [`wide_montgomery_mul_acc`] but takes operands by
966
/// reference, for non-`Copy` bigint backends. Pair with
967
/// [`strict_wide_redc`] for the final reduction.
968
pub fn strict_wide_montgomery_mul_acc<T>(acc_lo: &T, acc_hi: &T, a: &T, b: &T) -> (T, T)
3,380✔
969
where
3,380✔
970
    T: Copy
3,380✔
971
        + const_num_traits::One
3,380✔
972
        + WideMul
3,380✔
973
        + const_num_traits::ops::overflowing::OverflowingAdd
3,380✔
974
        + core::ops::Add<Output = T>,
3,380✔
975
{
976
    let (m_lo, m_hi) = a.wide_mul(b);
3,380✔
977
    let (new_lo, carry1) = (*acc_lo).overflowing_add(m_lo);
3,380✔
978
    let (sum_hi, _) = (*acc_hi).overflowing_add(m_hi);
3,380✔
979
    let new_hi = if carry1 {
3,380✔
980
        let (r, _) = sum_hi.overflowing_add(T::one());
576✔
981
        r
576✔
982
    } else {
983
        sum_hi
2,804✔
984
    };
985
    (new_lo, new_hi)
3,380✔
986
}
3,380✔
987

988
/// Wide multiply-accumulate — constant-time carry, reference-based inputs.
989
///
990
/// Same shape as [`wide_montgomery_mul_acc_ct`] but reference-based.
991
/// Pair with [`strict_wide_redc_ct`] for the final reduction.
992
pub fn strict_wide_montgomery_mul_acc_ct<T>(acc_lo: &T, acc_hi: &T, a: &T, b: &T) -> (T, T)
3,380✔
993
where
3,380✔
994
    T: Copy
3,380✔
995
        + const_num_traits::One
3,380✔
996
        + WideMul
3,380✔
997
        + const_num_traits::ops::overflowing::OverflowingAdd
3,380✔
998
        + core::ops::Add<Output = T>
3,380✔
999
        + subtle::ConditionallySelectable,
3,380✔
1000
{
1001
    let (m_lo, m_hi) = a.wide_mul(b);
3,380✔
1002
    let (new_lo, carry1) = (*acc_lo).overflowing_add(m_lo);
3,380✔
1003
    let (sum_hi, _) = (*acc_hi).overflowing_add(m_hi);
3,380✔
1004
    let (r, _) = sum_hi.overflowing_add(T::one());
3,380✔
1005
    let new_hi = T::conditional_select(&sum_hi, &r, subtle::Choice::from(carry1 as u8));
3,380✔
1006
    (new_lo, new_hi)
3,380✔
1007
}
3,380✔
1008

1009
// ---------------------------------------------------------------------------
1010
// Public API: mod_mul / mod_exp using wide REDC
1011
// ---------------------------------------------------------------------------
1012

1013
/// Reduce value modulo modulus.
1014
///
1015
/// **Note**: This function assumes unsigned types. For signed types, the `%`
1016
/// operator may return negative values, which would violate the `[0, modulus)`
1017
/// range required by Montgomery arithmetic.
1018
fn reduce_mod<T>(val: T, modulus: T) -> T
2,138✔
1019
where
2,138✔
1020
    T: Copy + core::ops::Rem<Output = T>,
2,138✔
1021
{
1022
    val % modulus
2,138✔
1023
}
2,138✔
1024

1025
/// Complete Montgomery modular multiplication (Basic): A * B mod N
1026
///
1027
/// Uses wide REDC (R = 2^W) with Newton's method for N'.
1028
/// Inputs are reduced modulo N before conversion to Montgomery form.
1029
/// Returns None if modulus is even or zero.
1030
pub fn basic_montgomery_mod_mul_odd<T>(a: T, b: T, modulus: Odd<T>) -> T
637✔
1031
where
637✔
1032
    T: Copy
637✔
1033
        + const_num_traits::Zero
637✔
1034
        + const_num_traits::One
637✔
1035
        + PartialEq
637✔
1036
        + PartialOrd
637✔
1037
        + WideMul
637✔
1038
        + const_num_traits::ops::overflowing::OverflowingAdd
637✔
1039
        + const_num_traits::WrappingMul
637✔
1040
        + const_num_traits::WrappingAdd
637✔
1041
        + const_num_traits::WrappingSub
637✔
1042
        + core::ops::Add<Output = T>
637✔
1043
        + core::ops::Sub<Output = T>
637✔
1044
        + core::ops::Mul<Output = T>
637✔
1045
        + core::ops::Rem<Output = T>,
637✔
1046
{
1047
    let m = modulus.get();
637✔
1048
    basic_montgomery_mod_mul_pr_odd(reduce_mod(a, m), reduce_mod(b, m), modulus)
637✔
1049
}
637✔
1050

1051
pub fn basic_montgomery_mod_mul<T>(a: T, b: T, modulus: T) -> Option<T>
612✔
1052
where
612✔
1053
    T: Copy
612✔
1054
        + const_num_traits::Zero
612✔
1055
        + const_num_traits::One
612✔
1056
        + PartialEq
612✔
1057
        + PartialOrd
612✔
1058
        + WideMul
612✔
1059
        + const_num_traits::ops::overflowing::OverflowingAdd
612✔
1060
        + const_num_traits::WrappingMul
612✔
1061
        + const_num_traits::WrappingAdd
612✔
1062
        + const_num_traits::WrappingSub
612✔
1063
        + Parity
612✔
1064
        + core::ops::Add<Output = T>
612✔
1065
        + core::ops::Sub<Output = T>
612✔
1066
        + core::ops::Mul<Output = T>
612✔
1067
        + core::ops::Rem<Output = T>,
612✔
1068
{
1069
    Odd::new(modulus).map(|m| basic_montgomery_mod_mul_odd(a, b, m))
612✔
1070
}
612✔
1071

1072
/// Complete Montgomery modular multiplication (Basic, pre-reduced,
1073
/// proven-odd modulus): A * B mod N.
1074
///
1075
/// **Infallible.** The `Odd<T>` typestate carries the "modulus is odd and
1076
/// nonzero" precondition; no internal `Option` plumbing or runtime parity
1077
/// check. Use [`basic_montgomery_mod_mul_pr`] if the proof has to be done
1078
/// at runtime.
1079
///
1080
/// Precondition (unchanged from the `Option`-returning sibling): `a < modulus`
1081
/// and `b < modulus`.
1082
pub fn basic_montgomery_mod_mul_pr_odd<T>(a: T, b: T, modulus: Odd<T>) -> T
698✔
1083
where
698✔
1084
    T: Copy
698✔
1085
        + const_num_traits::Zero
698✔
1086
        + const_num_traits::One
698✔
1087
        + PartialEq
698✔
1088
        + PartialOrd
698✔
1089
        + WideMul
698✔
1090
        + const_num_traits::ops::overflowing::OverflowingAdd
698✔
1091
        + const_num_traits::WrappingMul
698✔
1092
        + const_num_traits::WrappingAdd
698✔
1093
        + const_num_traits::WrappingSub
698✔
1094
        + core::ops::Add<Output = T>
698✔
1095
        + core::ops::Sub<Output = T>
698✔
1096
        + core::ops::Mul<Output = T>,
698✔
1097
{
1098
    let modulus = modulus.get();
698✔
1099
    let w = type_bit_width::<T>();
698✔
1100
    let n_prime = compute_n_prime_newton(modulus, w);
698✔
1101
    let r_mod_n = compute_r_mod_n(modulus, w);
698✔
1102
    let r2_mod_n = compute_r2_mod_n(r_mod_n, modulus, w);
698✔
1103

1104
    // Convert to Montgomery form via REDC(a * R^2)
1105
    let (lo, hi) = a.wide_mul(&r2_mod_n);
698✔
1106
    let a_m = wide_redc(lo, hi, modulus, n_prime);
698✔
1107
    let (lo, hi) = b.wide_mul(&r2_mod_n);
698✔
1108
    let b_m = wide_redc(lo, hi, modulus, n_prime);
698✔
1109

1110
    // Multiply in Montgomery domain
1111
    let r_m = wide_montgomery_mul(a_m, b_m, modulus, n_prime);
698✔
1112

1113
    // Convert back: REDC(r_m, 0)
1114
    wide_redc(r_m, T::zero(), modulus, n_prime)
698✔
1115
}
698✔
1116

1117
/// Complete Montgomery modular multiplication (Basic, pre-reduced): A * B mod N
1118
///
1119
/// Precondition: `a < modulus` and `b < modulus`. No `Rem` bound. Returns
1120
/// None only if modulus is even or zero. Thin wrapper around
1121
/// [`basic_montgomery_mod_mul_pr_odd`] that performs the parity proof at
1122
/// runtime — prefer the `_odd` form to keep the panic path out of the
1123
/// linked binary.
1124
pub fn basic_montgomery_mod_mul_pr<T>(a: T, b: T, modulus: T) -> Option<T>
31✔
1125
where
31✔
1126
    T: Copy
31✔
1127
        + const_num_traits::Zero
31✔
1128
        + const_num_traits::One
31✔
1129
        + PartialEq
31✔
1130
        + PartialOrd
31✔
1131
        + WideMul
31✔
1132
        + const_num_traits::ops::overflowing::OverflowingAdd
31✔
1133
        + const_num_traits::WrappingMul
31✔
1134
        + const_num_traits::WrappingAdd
31✔
1135
        + const_num_traits::WrappingSub
31✔
1136
        + Parity
31✔
1137
        + core::ops::Add<Output = T>
31✔
1138
        + core::ops::Sub<Output = T>
31✔
1139
        + core::ops::Mul<Output = T>,
31✔
1140
{
1141
    Odd::new(modulus).map(|m| basic_montgomery_mod_mul_pr_odd(a, b, m))
31✔
1142
}
31✔
1143

1144
/// Montgomery-based modular exponentiation (Basic, proven-odd modulus):
1145
/// base^exponent mod modulus. **Infallible.**
1146
pub fn basic_montgomery_mod_exp_odd<T>(base: T, exponent: T, modulus: Odd<T>) -> T
864✔
1147
where
864✔
1148
    T: Copy
864✔
1149
        + const_num_traits::Zero
864✔
1150
        + const_num_traits::One
864✔
1151
        + PartialEq
864✔
1152
        + PartialOrd
864✔
1153
        + WideMul
864✔
1154
        + const_num_traits::ops::overflowing::OverflowingAdd
864✔
1155
        + const_num_traits::WrappingMul
864✔
1156
        + const_num_traits::WrappingAdd
864✔
1157
        + const_num_traits::WrappingSub
864✔
1158
        + Parity
864✔
1159
        + core::ops::Add<Output = T>
864✔
1160
        + core::ops::Sub<Output = T>
864✔
1161
        + core::ops::Mul<Output = T>
864✔
1162
        + core::ops::Rem<Output = T>
864✔
1163
        + core::ops::Shr<usize, Output = T>
864✔
1164
        + core::ops::ShrAssign<usize>,
864✔
1165
{
1166
    let m = modulus.get();
864✔
1167
    basic_montgomery_mod_exp_pr_odd(reduce_mod(base, m), exponent, modulus)
864✔
1168
}
864✔
1169

1170
/// Montgomery-based modular exponentiation (Basic): base^exponent mod modulus
1171
///
1172
/// Uses wide REDC (R = 2^W) with Newton's method for N'.
1173
/// The base is reduced modulo N before conversion to Montgomery form.
1174
/// Returns None if modulus is even or zero. Thin wrapper around
1175
/// [`basic_montgomery_mod_exp_odd`].
1176
pub fn basic_montgomery_mod_exp<T>(base: T, exponent: T, modulus: T) -> Option<T>
847✔
1177
where
847✔
1178
    T: Copy
847✔
1179
        + const_num_traits::Zero
847✔
1180
        + const_num_traits::One
847✔
1181
        + PartialEq
847✔
1182
        + PartialOrd
847✔
1183
        + WideMul
847✔
1184
        + const_num_traits::ops::overflowing::OverflowingAdd
847✔
1185
        + const_num_traits::WrappingMul
847✔
1186
        + const_num_traits::WrappingAdd
847✔
1187
        + const_num_traits::WrappingSub
847✔
1188
        + Parity
847✔
1189
        + core::ops::Add<Output = T>
847✔
1190
        + core::ops::Sub<Output = T>
847✔
1191
        + core::ops::Mul<Output = T>
847✔
1192
        + core::ops::Rem<Output = T>
847✔
1193
        + core::ops::Shr<usize, Output = T>
847✔
1194
        + core::ops::ShrAssign<usize>,
847✔
1195
{
1196
    Odd::new(modulus).map(|m| basic_montgomery_mod_exp_odd(base, exponent, m))
847✔
1197
}
847✔
1198

1199
/// Complete Montgomery modular exponentiation (Basic, pre-reduced,
1200
/// proven-odd modulus). **Infallible.** Precondition: `base < modulus`.
1201
pub fn basic_montgomery_mod_exp_pr_odd<T>(base: T, exponent: T, modulus: Odd<T>) -> T
1,329✔
1202
where
1,329✔
1203
    T: Copy
1,329✔
1204
        + const_num_traits::Zero
1,329✔
1205
        + const_num_traits::One
1,329✔
1206
        + PartialEq
1,329✔
1207
        + PartialOrd
1,329✔
1208
        + WideMul
1,329✔
1209
        + const_num_traits::ops::overflowing::OverflowingAdd
1,329✔
1210
        + const_num_traits::WrappingMul
1,329✔
1211
        + const_num_traits::WrappingAdd
1,329✔
1212
        + const_num_traits::WrappingSub
1,329✔
1213
        + Parity
1,329✔
1214
        + core::ops::Add<Output = T>
1,329✔
1215
        + core::ops::Sub<Output = T>
1,329✔
1216
        + core::ops::Mul<Output = T>
1,329✔
1217
        + core::ops::ShrAssign<usize>,
1,329✔
1218
{
1219
    let modulus = modulus.get();
1,329✔
1220
    let w = type_bit_width::<T>();
1,329✔
1221
    let n_prime = compute_n_prime_newton(modulus, w);
1,329✔
1222
    let r_mod_n = compute_r_mod_n(modulus, w);
1,329✔
1223
    let r2_mod_n = compute_r2_mod_n(r_mod_n, modulus, w);
1,329✔
1224

1225
    // 1 in Montgomery form = R mod N (since REDC(1 * R²) = 1 * R² * R⁻¹ = R mod N)
1226
    let one_mont = r_mod_n;
1,329✔
1227

1228
    let (lo, hi) = base.wide_mul(&r2_mod_n);
1,329✔
1229
    let mut base_mont = wide_redc(lo, hi, modulus, n_prime);
1,329✔
1230
    let mut result = one_mont;
1,329✔
1231
    let mut exp = exponent;
1,329✔
1232

1233
    while exp > T::zero() {
6,355✔
1234
        if exp.is_odd() {
5,026✔
1235
            result = wide_montgomery_mul(result, base_mont, modulus, n_prime);
3,049✔
1236
        }
3,049✔
1237
        exp >>= 1;
5,026✔
1238
        if exp > T::zero() {
5,026✔
1239
            base_mont = wide_montgomery_mul(base_mont, base_mont, modulus, n_prime);
3,772✔
1240
        }
3,772✔
1241
    }
1242

1243
    wide_redc(result, T::zero(), modulus, n_prime)
1,329✔
1244
}
1,329✔
1245

1246
/// Complete Montgomery modular exponentiation (Basic, pre-reduced): base^exponent mod modulus
1247
///
1248
/// Precondition: `base < modulus`. No `Rem` bound. Returns None only if
1249
/// modulus is even or zero. Thin wrapper around
1250
/// [`basic_montgomery_mod_exp_pr_odd`].
1251
pub fn basic_montgomery_mod_exp_pr<T>(base: T, exponent: T, modulus: T) -> Option<T>
445✔
1252
where
445✔
1253
    T: Copy
445✔
1254
        + const_num_traits::Zero
445✔
1255
        + const_num_traits::One
445✔
1256
        + PartialEq
445✔
1257
        + PartialOrd
445✔
1258
        + WideMul
445✔
1259
        + const_num_traits::ops::overflowing::OverflowingAdd
445✔
1260
        + const_num_traits::WrappingMul
445✔
1261
        + const_num_traits::WrappingAdd
445✔
1262
        + const_num_traits::WrappingSub
445✔
1263
        + Parity
445✔
1264
        + core::ops::Add<Output = T>
445✔
1265
        + core::ops::Sub<Output = T>
445✔
1266
        + core::ops::Mul<Output = T>
445✔
1267
        + core::ops::ShrAssign<usize>,
445✔
1268
{
1269
    Odd::new(modulus).map(|m| basic_montgomery_mod_exp_pr_odd(base, exponent, m))
445✔
1270
}
445✔
1271

1272
/// Complete Montgomery modular exponentiation (Basic, CT, pre-reduced):
1273
/// base^exponent mod modulus, constant-time over `exponent` (and `base`).
1274
///
1275
/// Precondition: `base < modulus`. No `Rem` bound.
1276
///
1277
/// Implements a fixed-iteration constant-time square-and-multiply:
1278
///   - Iterates over **every** bit position of the exponent type (not just
1279
///     significant bits), so the loop count does not leak `bit_length(exp)`.
1280
///   - Performs the squaring and the conditional multiply on **every**
1281
///     iteration, with `subtle::conditional_select` choosing whether to keep
1282
///     the multiplied result based on the current exponent bit — so timing
1283
///     and memory access patterns are independent of the exponent bit
1284
///     pattern.
1285
///   - All Montgomery reductions route through [`wide_redc_ct`].
1286
///
1287
/// Precomputation (`compute_n_prime_newton`, `compute_r_mod_n`,
1288
/// `compute_r2_mod_n`) operates only on the modulus, which is public; using
1289
/// the NCT compute_* helpers there is intentional and does not leak any
1290
/// secret.
1291
///
1292
/// Complete Montgomery modular exponentiation (Basic, CT, pre-reduced,
1293
/// proven-odd modulus). **Infallible.** Precondition: `base < modulus`.
1294
pub fn basic_montgomery_mod_exp_pr_odd_ct<T>(base: T, exponent: T, modulus: Odd<T>) -> T
860✔
1295
where
860✔
1296
    T: Copy
860✔
1297
        + const_num_traits::Zero
860✔
1298
        + const_num_traits::One
860✔
1299
        + PartialEq
860✔
1300
        + PartialOrd
860✔
1301
        + WideMul
860✔
1302
        + const_num_traits::ops::overflowing::OverflowingAdd
860✔
1303
        + const_num_traits::WrappingMul
860✔
1304
        + const_num_traits::WrappingAdd
860✔
1305
        + const_num_traits::WrappingSub
860✔
1306
        + const_num_traits::CtIsZero
860✔
1307
        + Parity
860✔
1308
        + core::ops::Add<Output = T>
860✔
1309
        + core::ops::Sub<Output = T>
860✔
1310
        + core::ops::Mul<Output = T>
860✔
1311
        + core::ops::Shr<usize, Output = T>
860✔
1312
        + core::ops::BitAnd<Output = T>
860✔
1313
        + subtle::ConditionallySelectable
860✔
1314
        + subtle::ConstantTimeEq
860✔
1315
        + subtle::ConstantTimeLess,
860✔
1316
{
1317
    let modulus = modulus.get();
860✔
1318
    let w = type_bit_width::<T>();
860✔
1319
    let n_prime = compute_n_prime_newton(modulus, w);
860✔
1320
    let r_mod_n = compute_r_mod_n(modulus, w);
860✔
1321
    let r2_mod_n = compute_r2_mod_n(r_mod_n, modulus, w);
860✔
1322

1323
    // 1 in Montgomery form = R mod N
1324
    let one_mont = r_mod_n;
860✔
1325

1326
    // Convert base to Montgomery form via REDC(base * R²)
1327
    let base_mont = wide_montgomery_mul_ct(base, r2_mod_n, modulus, n_prime);
860✔
1328

1329
    let mut result = one_mont;
860✔
1330

1331
    // Constant-time square-and-multiply, MSB to LSB, fixed iteration count = w.
1332
    // Always squares; always computes the multiply; conditionally selects.
1333
    let one = T::one();
860✔
1334
    for i in (0..w).rev() {
28,288✔
1335
        // Always square
28,288✔
1336
        result = wide_montgomery_mul_ct(result, result, modulus, n_prime);
28,288✔
1337

28,288✔
1338
        // Always compute the conditional product
28,288✔
1339
        let multiplied = wide_montgomery_mul_ct(result, base_mont, modulus, n_prime);
28,288✔
1340

28,288✔
1341
        // Extract bit i of exponent (i is public; the shift amount is the
28,288✔
1342
        // loop index, not derived from exp). Bit value is secret.
28,288✔
1343
        let bit_t = (exponent >> i) & one;
28,288✔
1344
        let choice = !bit_t.ct_is_zero();
28,288✔
1345
        result = T::conditional_select(&result, &multiplied, choice);
28,288✔
1346
    }
28,288✔
1347

1348
    // Convert back from Montgomery form: REDC(result, 0)
1349
    wide_redc_ct(result, T::zero(), modulus, n_prime)
860✔
1350
}
860✔
1351

1352
/// Returns None if modulus is even or zero. Thin wrapper around
1353
/// [`basic_montgomery_mod_exp_pr_odd_ct`].
1354
pub fn basic_montgomery_mod_exp_pr_ct<T>(base: T, exponent: T, modulus: T) -> Option<T>
840✔
1355
where
840✔
1356
    T: Copy
840✔
1357
        + const_num_traits::Zero
840✔
1358
        + const_num_traits::One
840✔
1359
        + PartialEq
840✔
1360
        + PartialOrd
840✔
1361
        + WideMul
840✔
1362
        + const_num_traits::ops::overflowing::OverflowingAdd
840✔
1363
        + const_num_traits::WrappingMul
840✔
1364
        + const_num_traits::WrappingAdd
840✔
1365
        + const_num_traits::WrappingSub
840✔
1366
        + const_num_traits::CtIsZero
840✔
1367
        + Parity
840✔
1368
        + core::ops::Add<Output = T>
840✔
1369
        + core::ops::Sub<Output = T>
840✔
1370
        + core::ops::Mul<Output = T>
840✔
1371
        + core::ops::Shr<usize, Output = T>
840✔
1372
        + core::ops::BitAnd<Output = T>
840✔
1373
        + subtle::ConditionallySelectable
840✔
1374
        + subtle::ConstantTimeEq
840✔
1375
        + subtle::ConstantTimeLess,
840✔
1376
{
1377
    Odd::new(modulus).map(|m| basic_montgomery_mod_exp_pr_odd_ct(base, exponent, m))
840✔
1378
}
840✔
1379

1380
#[cfg(test)]
1381
mod tests {
1382
    use super::*;
1383
    use const_num_traits::Ct;
1384
    use fixed_bigint::FixedUInt;
1385

1386
    // -- Old basic_mont tests (param computation, N' methods, etc.) ----------
1387

1388
    #[test]
1389
    fn test_basic_compute_n_prime_trial_search_failure() {
1✔
1390
        // Test case where no N' can be found - this happens when gcd(modulus, R) != 1
1391
        // Example: N = 4, R = 8 (both even, so gcd(4, 8) = 4 != 1)
1392
        let modulus = 4u32;
1✔
1393
        let r = 8u32;
1✔
1394
        let result = compute_n_prime_trial_search(modulus, r);
1✔
1395
        assert!(
1✔
1396
            result.is_none(),
1✔
1397
            "Should return None for invalid modulus-R pair"
1398
        );
1399
    }
1✔
1400

1401
    #[test]
1402
    fn test_basic_compute_montgomery_params_failure() {
1✔
1403
        // Test Montgomery parameter computation failure with even modulus
1404
        let even_modulus = 4u32;
1✔
1405
        let result = basic_compute_montgomery_params(even_modulus);
1✔
1406
        assert!(result.is_none(), "Should return None for even modulus");
1✔
1407
    }
1✔
1408

1409
    #[test]
1410
    fn test_basic_compute_montgomery_params_failure_with_method() {
1✔
1411
        // Test all N' computation methods with invalid inputs
1412
        let invalid_modulus = 4u32; // Even modulus
1✔
1413

1414
        // Trial search should fail
1415
        let trial_result =
1✔
1416
            basic_compute_montgomery_params_with_method(invalid_modulus, NPrimeMethod::TrialSearch);
1✔
1417
        assert!(
1✔
1418
            trial_result.is_none(),
1✔
1419
            "Trial search should fail with even modulus"
1420
        );
1421

1422
        // Extended Euclidean should fail
1423
        let euclidean_result = basic_compute_montgomery_params_with_method(
1✔
1424
            invalid_modulus,
1✔
1425
            NPrimeMethod::ExtendedEuclidean,
1✔
1426
        );
1427
        assert!(
1✔
1428
            euclidean_result.is_none(),
1✔
1429
            "Extended Euclidean should fail with even modulus"
1430
        );
1431

1432
        // Hensel's lifting should fail
1433
        let hensels_result = basic_compute_montgomery_params_with_method(
1✔
1434
            invalid_modulus,
1✔
1435
            NPrimeMethod::HenselsLifting,
1✔
1436
        );
1437
        assert!(
1✔
1438
            hensels_result.is_none(),
1✔
1439
            "Hensel's lifting should fail with even modulus"
1440
        );
1441
    }
1✔
1442

1443
    #[test]
1444
    fn test_basic_montgomery_mod_mul_parameter_failure() {
1✔
1445
        // Test that montgomery_mod_mul returns None when parameter computation fails
1446
        let invalid_modulus = 4u32;
1✔
1447
        let a = 2u32;
1✔
1448
        let b = 3u32;
1✔
1449

1450
        let result = basic_montgomery_mod_mul(a, b, invalid_modulus);
1✔
1451
        assert!(
1✔
1452
            result.is_none(),
1✔
1453
            "Montgomery mod_mul should return None for invalid modulus"
1454
        );
1455
    }
1✔
1456

1457
    #[test]
1458
    fn odd_surface_matches_option_surface_mul() {
1✔
1459
        // The `_odd` (infallible) and `Option`-returning entry points must
1460
        // produce identical results when the modulus is in fact odd. This
1461
        // pins the contract that the wrapper is purely a parity-proof
1462
        // adapter — no behavioural divergence.
1463
        let m: u32 = 97;
1✔
1464
        let modulus_odd = Odd::new(m).expect("97 is odd");
1✔
1465
        for a in [0u32, 1, 2, 42, 50, 96] {
6✔
1466
            for b in [0u32, 1, 13, 49, 95] {
30✔
1467
                let via_odd_pr = basic_montgomery_mod_mul_pr_odd(a % m, b % m, modulus_odd);
30✔
1468
                let via_opt_pr = basic_montgomery_mod_mul_pr(a % m, b % m, m).unwrap();
30✔
1469
                assert_eq!(via_odd_pr, via_opt_pr, "_pr divergence at ({a}, {b})");
30✔
1470

1471
                let via_odd = basic_montgomery_mod_mul_odd(a, b, modulus_odd);
30✔
1472
                let via_opt = basic_montgomery_mod_mul(a, b, m).unwrap();
30✔
1473
                assert_eq!(via_odd, via_opt, "mul divergence at ({a}, {b})");
30✔
1474
                assert_eq!(via_odd, (a * b) % m);
30✔
1475
            }
1476
        }
1477
    }
1✔
1478

1479
    #[test]
1480
    fn odd_surface_matches_option_surface_exp() {
1✔
1481
        let m: u32 = 97;
1✔
1482
        let modulus_odd = Odd::new(m).expect("97 is odd");
1✔
1483
        for base in [0u32, 1, 2, 5, 96] {
5✔
1484
            for exp in [0u32, 1, 5, 96] {
20✔
1485
                let via_odd_pr = basic_montgomery_mod_exp_pr_odd(base % m, exp, modulus_odd);
20✔
1486
                let via_opt_pr = basic_montgomery_mod_exp_pr(base % m, exp, m).unwrap();
20✔
1487
                assert_eq!(via_odd_pr, via_opt_pr, "_pr divergence at ({base}, {exp})");
20✔
1488

1489
                let via_odd = basic_montgomery_mod_exp_odd(base, exp, modulus_odd);
20✔
1490
                let via_opt = basic_montgomery_mod_exp(base, exp, m).unwrap();
20✔
1491
                assert_eq!(via_odd, via_opt, "exp divergence at ({base}, {exp})");
20✔
1492

1493
                let via_ct = basic_montgomery_mod_exp_pr_odd_ct(base % m, exp, modulus_odd);
20✔
1494
                assert_eq!(via_ct, via_odd, "ct/nct divergence at ({base}, {exp})");
20✔
1495
            }
1496
        }
1497
    }
1✔
1498

1499
    #[test]
1500
    fn test_basic_montgomery_mod_exp_parameter_failure() {
1✔
1501
        // Test that montgomery_mod_exp returns None when parameter computation fails
1502
        let invalid_modulus = 4u32;
1✔
1503
        let base = 2u32;
1✔
1504
        let exponent = 3u32;
1✔
1505

1506
        let result = basic_montgomery_mod_exp(base, exponent, invalid_modulus);
1✔
1507
        assert!(
1✔
1508
            result.is_none(),
1✔
1509
            "Montgomery mod_exp should return None for invalid modulus"
1510
        );
1511
    }
1✔
1512

1513
    #[test]
1514
    fn test_basic_montgomery_reduction_final_subtraction() {
1✔
1515
        // Test to trigger t >= modulus branch in basic_from_montgomery
1516
        let modulus = 15u32;
1✔
1517
        let (r, _r_inv, n_prime, r_bits) = basic_compute_montgomery_params(modulus).unwrap();
1✔
1518

1519
        // Use values designed to need final subtraction in Montgomery reduction
1520
        let high_value = 14u32; // Near maximum for this modulus
1✔
1521
        let mont_high = basic_to_montgomery(high_value, modulus, r);
1✔
1522
        let result = basic_from_montgomery(mont_high, modulus, n_prime, r_bits);
1✔
1523
        assert_eq!(result, high_value);
1✔
1524

1525
        // Test with maximum value - 1 to stress the >= check
1526
        let mont_max = basic_to_montgomery(modulus - 1, modulus, r);
1✔
1527
        let result_max = basic_from_montgomery(mont_max, modulus, n_prime, r_bits);
1✔
1528
        assert_eq!(result_max, modulus - 1);
1✔
1529
    }
1✔
1530

1531
    #[test]
1532
    fn test_basic_multiplication_edge_cases() {
1✔
1533
        // Test Montgomery multiplication with values that stress the algorithm
1534
        let modulus = 21u32; // Composite modulus
1✔
1535
        let (r, _r_inv, n_prime, r_bits) = basic_compute_montgomery_params(modulus).unwrap();
1✔
1536

1537
        // Test with values that may trigger intermediate results >= modulus
1538
        let a = 20u32; // Near maximum
1✔
1539
        let b = 19u32; // Near maximum
1✔
1540

1541
        let a_mont = basic_to_montgomery(a, modulus, r);
1✔
1542
        let b_mont = basic_to_montgomery(b, modulus, r);
1✔
1543

1544
        // This may hit different code paths in Montgomery multiplication
1545
        let result_mont = basic_montgomery_mul(a_mont, b_mont, modulus, n_prime, r_bits);
1✔
1546
        let result = basic_from_montgomery(result_mont, modulus, n_prime, r_bits);
1✔
1547

1548
        let expected = (a * b) % modulus;
1✔
1549
        assert_eq!(result, expected);
1✔
1550
    }
1✔
1551

1552
    #[test]
1553
    fn test_basic_n_prime_computation_edge_cases() {
1✔
1554
        // Test N' computation with moduli that may hit different branches
1555
        let test_moduli = [9u32, 15u32, 21u32, 25u32, 27u32]; // Various composite odd numbers
1✔
1556

1557
        for &modulus in &test_moduli {
5✔
1558
            // Test all N' computation methods
1559
            let trial_result =
5✔
1560
                basic_compute_montgomery_params_with_method(modulus, NPrimeMethod::TrialSearch);
5✔
1561
            let euclidean_result = basic_compute_montgomery_params_with_method(
5✔
1562
                modulus,
5✔
1563
                NPrimeMethod::ExtendedEuclidean,
5✔
1564
            );
1565
            let hensels_result =
5✔
1566
                basic_compute_montgomery_params_with_method(modulus, NPrimeMethod::HenselsLifting);
5✔
1567

1568
            // All should succeed for valid odd moduli
1569
            assert!(
5✔
1570
                trial_result.is_some(),
5✔
1571
                "Trial search failed for modulus {}",
1572
                modulus
1573
            );
1574
            assert!(
5✔
1575
                euclidean_result.is_some(),
5✔
1576
                "Extended Euclidean failed for modulus {}",
1577
                modulus
1578
            );
1579
            assert!(
5✔
1580
                hensels_result.is_some(),
5✔
1581
                "Hensel's lifting failed for modulus {}",
1582
                modulus
1583
            );
1584

1585
            // And all should produce same results
1586
            assert_eq!(
5✔
1587
                trial_result, euclidean_result,
1588
                "Methods disagree for modulus {}",
1589
                modulus
1590
            );
1591
            assert_eq!(
5✔
1592
                trial_result, hensels_result,
1593
                "Methods disagree for modulus {}",
1594
                modulus
1595
            );
1596
        }
1597
    }
1✔
1598

1599
    #[test]
1600
    fn test_basic_exponentiation_loop_branches() {
1✔
1601
        // Test binary exponentiation with values that hit different loop conditions
1602
        let modulus = 17u32; // Prime modulus
1✔
1603

1604
        // Test exponentiation that exercises specific loop branches
1605
        let base = 16u32; // Base = modulus - 1
1✔
1606
        let exponent = 15u32; // Exponent with specific bit pattern
1✔
1607

1608
        let result = basic_montgomery_mod_exp(base, exponent, modulus).unwrap();
1✔
1609
        let expected = crate::exp::basic_mod_exp(base, exponent, modulus);
1✔
1610
        assert_eq!(result, expected);
1✔
1611

1612
        // Test with power of 2 exponent to hit specific branches
1613
        let exp_pow2 = 16u32; // 2^4, will exercise specific bit patterns
1✔
1614
        let result_pow2 = basic_montgomery_mod_exp(3u32, exp_pow2, modulus).unwrap();
1✔
1615
        let expected_pow2 = crate::exp::basic_mod_exp(3u32, exp_pow2, modulus);
1✔
1616
        assert_eq!(result_pow2, expected_pow2);
1✔
1617

1618
        // Test with odd exponent
1619
        let exp_odd = 255u32; // Large odd exponent
1✔
1620
        let result_odd = basic_montgomery_mod_exp(2u32, exp_odd, modulus).unwrap();
1✔
1621
        let expected_odd = crate::exp::basic_mod_exp(2u32, exp_odd, modulus);
1✔
1622
        assert_eq!(result_odd, expected_odd);
1✔
1623
    }
1✔
1624

1625
    #[test]
1626
    fn test_basic_extended_euclidean_none_case() {
1✔
1627
        // Test the case where basic_mod_inv returns None in compute_n_prime_extended_euclidean
1628
        // This happens when gcd(modulus, r) > 1, i.e., when modulus and r are not coprime
1629

1630
        // Since r is always a power of 2 in Montgomery arithmetic, we need an even modulus
1631
        // to make gcd(modulus, r) > 1
1632
        let even_modulus = 6u32; // Even modulus
1✔
1633
        let r = 8u32; // Power of 2
1✔
1634

1635
        // gcd(6, 8) = 2 > 1, so basic_mod_inv(6, 8) should return None
1636
        assert!(
1✔
1637
            crate::inv::basic_mod_inv(even_modulus, r).is_none(),
1✔
1638
            "basic_mod_inv should return None for non-coprime inputs"
1639
        );
1640

1641
        // This should trigger the None path in compute_n_prime_extended_euclidean
1642
        let result = compute_n_prime_extended_euclidean(even_modulus, r);
1✔
1643
        assert!(
1✔
1644
            result.is_none(),
1✔
1645
            "Should return None when basic_mod_inv fails"
1646
        );
1647

1648
        // Test with other even moduli to ensure the None path is consistently hit
1649
        let test_cases = [
1✔
1650
            (4u32, 8u32),   // gcd(4, 8) = 4
1✔
1651
            (6u32, 12u32),  // gcd(6, 12) = 6
1✔
1652
            (10u32, 16u32), // gcd(10, 16) = 2
1✔
1653
            (12u32, 8u32),  // gcd(12, 8) = 4
1✔
1654
        ];
1✔
1655

1656
        for (modulus, r) in test_cases.iter() {
4✔
1657
            // Verify basic_mod_inv returns None for these non-coprime pairs
1658
            assert!(
4✔
1659
                crate::inv::basic_mod_inv(*modulus, *r).is_none(),
4✔
1660
                "basic_mod_inv({}, {}) should return None",
1661
                modulus,
1662
                r
1663
            );
1664

1665
            // Verify compute_n_prime_extended_euclidean returns None
1666
            let result = compute_n_prime_extended_euclidean(*modulus, *r);
4✔
1667
            assert!(
4✔
1668
                result.is_none(),
4✔
1669
                "compute_n_prime_extended_euclidean({}, {}) should return None",
1670
                modulus,
1671
                r
1672
            );
1673
        }
1674
    }
1✔
1675

1676
    // -- Wide REDC helper tests (moved from wide_mont.rs) --------------------
1677

1678
    #[test]
1679
    fn test_type_bit_width() {
1✔
1680
        assert_eq!(type_bit_width::<u8>(), 8);
1✔
1681
        assert_eq!(type_bit_width::<u16>(), 16);
1✔
1682
        assert_eq!(type_bit_width::<u32>(), 32);
1✔
1683
        assert_eq!(type_bit_width::<u64>(), 64);
1✔
1684
    }
1✔
1685

1686
    #[test]
1687
    fn test_mod_double() {
1✔
1688
        // Normal case: 5+5=10, 10 < 13 -> 10
1689
        assert_eq!(mod_double(5u8, 13), 10);
1✔
1690
        // Needs reduction: 8+8=16 >= 13 -> 3
1691
        assert_eq!(mod_double(8u8, 13), 3);
1✔
1692
        // Overflow case: 200+200 wraps in u8, result should still be correct
1693
        // 400 mod 201 = 400 - 201 = 199
1694
        // In u8: 200+200 = 144 (wrap), overflow=true -> 144 - 201 wraps to 199
1695
        assert_eq!(mod_double(200u8, 201), 199);
1✔
1696
        // Edge: 0 doubled is 0
1697
        assert_eq!(mod_double(0u8, 13), 0);
1✔
1698
    }
1✔
1699

1700
    #[test]
1701
    fn test_compute_n_prime_newton() {
1✔
1702
        // For several small odd moduli, verify N * N' == -1 (mod 2^W)
1703
        let test_moduli: &[u8] = &[3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 255];
1✔
1704
        for &n in test_moduli {
14✔
1705
            let np = compute_n_prime_newton(n, 8);
14✔
1706
            // N * N' should wrap to 0xFF (which is -1 mod 256)
1707
            let product = n.wrapping_mul(np);
14✔
1708
            assert_eq!(
14✔
1709
                product, 0xFF,
1710
                "n={n}: n*n_prime={product:#04x}, expected 0xFF"
1711
            );
1712
        }
1713

1714
        // Also check u32
1715
        let np32 = compute_n_prime_newton(13u32, 32);
1✔
1716
        assert_eq!(13u32.wrapping_mul(np32), u32::MAX);
1✔
1717
    }
1✔
1718

1719
    #[test]
1720
    fn test_compute_r_mod_n() {
1✔
1721
        // R = 2^8 = 256 for u8
1722
        // 256 mod 13 = 256 - 19*13 = 256-247 = 9
1723
        assert_eq!(compute_r_mod_n(13u8, 8), 9);
1✔
1724
        // 256 mod 255 = 1
1725
        assert_eq!(compute_r_mod_n(255u8, 8), 1);
1✔
1726
        // 256 mod 3 = 1
1727
        assert_eq!(compute_r_mod_n(3u8, 8), 1);
1✔
1728

1729
        // R = 2^32 for u32: 2^32 mod 13 = 4294967296 mod 13 = 9
1730
        assert_eq!(compute_r_mod_n(13u32, 32), 9);
1✔
1731
    }
1✔
1732

1733
    #[test]
1734
    fn test_wide_redc() {
1✔
1735
        // With u8, R=256, N=13
1736
        // REDC(35, 0) should give 35 * R^{-1} mod 13
1737
        // R^{-1} mod 13: R=256, 256 mod 13 = 9, inv(9,13)=3 (9*3=27==1 mod 13)
1738
        // So REDC(35,0) = 35*3 mod 13 = 105 mod 13 = 1
1739
        let n: u8 = 13;
1✔
1740
        let n_prime = compute_n_prime_newton(n, 8);
1✔
1741
        let result = wide_redc(35u8, 0u8, n, n_prime);
1✔
1742
        assert_eq!(result, 1);
1✔
1743
    }
1✔
1744

1745
    // -- Round-trip tests ----------------------------------------------------
1746

1747
    #[test]
1748
    fn test_wide_montgomery_roundtrip() {
1✔
1749
        let modulus = 13u8;
1✔
1750
        let w = type_bit_width::<u8>();
1✔
1751
        let n_prime = compute_n_prime_newton(modulus, w);
1✔
1752
        let r_mod_n = compute_r_mod_n(modulus, w);
1✔
1753
        let r2_mod_n = compute_r2_mod_n(r_mod_n, modulus, w);
1✔
1754
        for a in 0u8..13 {
13✔
1755
            let (lo, hi) = a.wide_mul(&r2_mod_n);
13✔
1756
            let a_m = wide_redc(lo, hi, modulus, n_prime);
13✔
1757
            let back = wide_redc(a_m, 0u8, modulus, n_prime);
13✔
1758
            assert_eq!(back, a, "roundtrip failed for a={a}");
13✔
1759
        }
1760
    }
1✔
1761

1762
    #[test]
1763
    fn test_wide_montgomery_roundtrip_u32() {
1✔
1764
        let modulus = 0xFFFF_FFF1u32; // large odd u32
1✔
1765
        let w = type_bit_width::<u32>();
1✔
1766
        let n_prime = compute_n_prime_newton(modulus, w);
1✔
1767
        let r_mod_n = compute_r_mod_n(modulus, w);
1✔
1768
        let r2_mod_n = compute_r2_mod_n(r_mod_n, modulus, w);
1✔
1769
        let test_vals = [0u32, 1, 2, 100, modulus - 1, modulus - 2, 0x7FFF_FFFF];
1✔
1770
        for &a in &test_vals {
7✔
1771
            let (lo, hi) = a.wide_mul(&r2_mod_n);
7✔
1772
            let a_m = wide_redc(lo, hi, modulus, n_prime);
7✔
1773
            let back = wide_redc(a_m, 0u32, modulus, n_prime);
7✔
1774
            assert_eq!(back, a, "roundtrip failed for a={a:#x}");
7✔
1775
        }
1776
    }
1✔
1777

1778
    // -- Mod mul tests -------------------------------------------------------
1779

1780
    #[test]
1781
    fn test_wide_mod_mul_u8_exhaustive() {
1✔
1782
        let modulus = 13u8;
1✔
1783
        for a in 0u8..13 {
13✔
1784
            for b in 0u8..13 {
169✔
1785
                let expected = ((a as u16 * b as u16) % 13) as u8;
169✔
1786
                let got = basic_montgomery_mod_mul(a, b, modulus).unwrap();
169✔
1787
                assert_eq!(
169✔
1788
                    got, expected,
1789
                    "{a}*{b} mod 13: got {got}, expected {expected}"
1790
                );
1791
            }
1792
        }
1793
    }
1✔
1794

1795
    #[test]
1796
    fn test_wide_mod_mul_u32() {
1✔
1797
        // Compare against basic_mod_mul for a range of values with a large modulus
1798
        let modulus = 0xFFFF_FFF1u32;
1✔
1799
        let vals = [0u32, 1, 2, 7, 1000, 0x7FFF_FFFF, modulus - 1, modulus - 2];
1✔
1800
        for &a in &vals {
8✔
1801
            for &b in &vals {
64✔
1802
                let expected = crate::mul::basic_mod_mul(a, b, modulus);
64✔
1803
                let got = basic_montgomery_mod_mul(a, b, modulus).unwrap();
64✔
1804
                assert_eq!(
64✔
1805
                    got, expected,
1806
                    "{a:#x}*{b:#x} mod {modulus:#x}: got {got:#x}, expected {expected:#x}"
1807
                );
1808
            }
1809
        }
1810
    }
1✔
1811

1812
    // -- Mod exp tests -------------------------------------------------------
1813

1814
    #[test]
1815
    fn test_wide_mod_exp_small() {
1✔
1816
        let modulus = 13u8;
1✔
1817
        for base in 0u8..13 {
13✔
1818
            for exp in 0u8..20 {
260✔
1819
                let expected = crate::exp::basic_mod_exp(base, exp, modulus);
260✔
1820
                let got = basic_montgomery_mod_exp(base, exp, modulus).unwrap();
260✔
1821
                assert_eq!(
260✔
1822
                    got, expected,
1823
                    "{base}^{exp} mod 13: got {got}, expected {expected}"
1824
                );
1825
            }
1826
        }
1827
    }
1✔
1828

1829
    #[test]
1830
    fn test_wide_mod_exp_u32() {
1✔
1831
        let modulus = 0xFFFF_FFF1u32;
1✔
1832
        // 2^100 mod modulus
1833
        let expected = crate::exp::basic_mod_exp(2u32, 100, modulus);
1✔
1834
        let got = basic_montgomery_mod_exp(2, 100, modulus).unwrap();
1✔
1835
        assert_eq!(got, expected);
1✔
1836

1837
        // 3^1000 mod modulus
1838
        let expected = crate::exp::basic_mod_exp(3u32, 1000, modulus);
1✔
1839
        let got = basic_montgomery_mod_exp(3, 1000, modulus).unwrap();
1✔
1840
        assert_eq!(got, expected);
1✔
1841

1842
        // (modulus-1)^(modulus-2) mod modulus  (Fermat inverse if prime-ish)
1843
        let expected = crate::exp::basic_mod_exp(modulus - 1, modulus - 2, modulus);
1✔
1844
        let got = basic_montgomery_mod_exp(modulus - 1, modulus - 2, modulus).unwrap();
1✔
1845
        assert_eq!(got, expected);
1✔
1846
    }
1✔
1847

1848
    #[test]
1849
    fn test_wide_mod_exp_large_u64() {
1✔
1850
        // Values that would overflow with the old mod_mul-based approach
1851
        let modulus = 0xFFFF_FFFF_FFFF_FFC5u64; // large odd u64
1✔
1852
        let base = 0xDEAD_BEEF_CAFE_BABEu64;
1✔
1853
        let exp = 0x1234_5678u64;
1✔
1854
        let expected = crate::exp::basic_mod_exp(base, exp, modulus);
1✔
1855
        let got = basic_montgomery_mod_exp(base, exp, modulus).unwrap();
1✔
1856
        assert_eq!(got, expected);
1✔
1857
    }
1✔
1858

1859
    // -- Error paths ---------------------------------------------------------
1860

1861
    #[test]
1862
    fn test_wide_params_even_modulus() {
1✔
1863
        assert!(basic_montgomery_mod_mul(2u32, 3u32, 4u32).is_none());
1✔
1864
        assert!(basic_montgomery_mod_mul(2u32, 3u32, 0u32).is_none());
1✔
1865
    }
1✔
1866

1867
    #[test]
1868
    fn test_wide_mod_mul_even_modulus() {
1✔
1869
        assert!(basic_montgomery_mod_mul(2u32, 3u32, 4u32).is_none());
1✔
1870
    }
1✔
1871

1872
    #[test]
1873
    fn test_wide_mod_exp_even_modulus() {
1✔
1874
        assert!(basic_montgomery_mod_exp(2u32, 3u32, 4u32).is_none());
1✔
1875
    }
1✔
1876

1877
    // -- FixedUInt test ------------------------------------------------------
1878

1879
    /// Sibling of [`test_wide_fixed_bigint_pr`] for the Rem-bound wrappers
1880
    /// (`basic_mod_mul`, `basic_mod_exp`). Default (Nct-personality)
1881
    /// FixedUInt provides Rem and so these wrappers are reachable here.
1882
    /// A Ct-typed FixedUInt would fail to satisfy the Rem bound — which is
1883
    /// the correct outcome, since reduce-then-multiply is variable-time.
1884
    #[test]
1885
    fn test_wide_fixed_bigint() {
1✔
1886
        type U128 = FixedUInt<u32, 4>;
1887

1888
        let modulus = !U128::from(0u64) - U128::from(58u64); // 2^128 - 59 (odd)
1✔
1889

1890
        let a = U128::from(0xDEAD_BEEF_u64);
1✔
1891
        let b = U128::from(0xCAFE_BABE_u64);
1✔
1892
        let expected = crate::mul::basic_mod_mul(a, b, modulus);
1✔
1893
        let got = basic_montgomery_mod_mul(a, b, modulus).unwrap();
1✔
1894
        assert_eq!(got, expected);
1✔
1895

1896
        let base = U128::from(42u64);
1✔
1897
        let exp = U128::from(1000u64);
1✔
1898
        let expected = crate::exp::basic_mod_exp(base, exp, modulus);
1✔
1899
        let got = basic_montgomery_mod_exp(base, exp, modulus).unwrap();
1✔
1900
        assert_eq!(got, expected);
1✔
1901
    }
1✔
1902

1903
    #[test]
1904
    fn test_wide_fixed_bigint_pr() {
1✔
1905
        type U128 = FixedUInt<u32, 4>;
1906

1907
        // Pick a 128-bit-ish odd modulus close to type max
1908
        let modulus = !U128::from(0u64) - U128::from(58u64); // 2^128 - 59 (odd)
1✔
1909

1910
        // Round-trip test via wide helpers directly
1911
        let w = type_bit_width::<U128>();
1✔
1912
        let n_prime = compute_n_prime_newton(modulus, w);
1✔
1913
        let r_mod_n = compute_r_mod_n(modulus, w);
1✔
1914
        let r2_mod_n = compute_r2_mod_n(r_mod_n, modulus, w);
1✔
1915

1916
        // All test inputs are tiny (< 2^32) and modulus is ~2^128, so the
1917
        // pre-reduced precondition is naturally satisfied.
1918
        let a = U128::from(0xDEAD_BEEF_u64);
1✔
1919
        let (lo, hi) = a.wide_mul(&r2_mod_n);
1✔
1920
        let a_m = wide_redc(lo, hi, modulus, n_prime);
1✔
1921
        let back = wide_redc(a_m, U128::from(0u64), modulus, n_prime);
1✔
1922
        assert_eq!(back, a);
1✔
1923

1924
        // Multiplication: compare _pr Montgomery against _pr double-and-add.
1925
        let b = U128::from(0xCAFE_BABE_u64);
1✔
1926
        let expected = crate::mul::basic_mod_mul_pr(a, b, modulus);
1✔
1927
        let got = basic_montgomery_mod_mul_pr(a, b, modulus).unwrap();
1✔
1928
        assert_eq!(got, expected);
1✔
1929

1930
        // Exponentiation
1931
        let base = U128::from(42u64);
1✔
1932
        let exp = U128::from(1000u64);
1✔
1933
        let expected = crate::exp::basic_mod_exp_pr(base, exp, modulus);
1✔
1934
        let got = basic_montgomery_mod_exp_pr(base, exp, modulus).unwrap();
1✔
1935
        assert_eq!(got, expected);
1✔
1936
    }
1✔
1937

1938
    // -- Input reduction tests -----------------------------------------------
1939

1940
    #[test]
1941
    fn test_input_reduction_mod_mul() {
1✔
1942
        // Test that inputs >= modulus are handled correctly via reduction
1943
        let modulus = 13u32;
1✔
1944

1945
        // Inputs larger than modulus should be reduced before Montgomery conversion
1946
        let a = 27u32; // 27 mod 13 = 1
1✔
1947
        let b = 39u32; // 39 mod 13 = 0
1✔
1948
        let got = basic_montgomery_mod_mul(a, b, modulus).unwrap();
1✔
1949
        assert_eq!(got, 0, "27 * 39 mod 13 should be 0");
1✔
1950

1951
        // Another case: both inputs need reduction
1952
        let a = 100u32; // 100 mod 13 = 9
1✔
1953
        let b = 200u32; // 200 mod 13 = 5
1✔
1954
        let expected = (9 * 5) % 13; // 45 mod 13 = 6
1✔
1955
        let got = basic_montgomery_mod_mul(a, b, modulus).unwrap();
1✔
1956
        assert_eq!(got, expected);
1✔
1957

1958
        // Edge case: input equals modulus (should reduce to 0)
1959
        let got = basic_montgomery_mod_mul(13u32, 5u32, modulus).unwrap();
1✔
1960
        assert_eq!(got, 0, "modulus * 5 mod modulus should be 0");
1✔
1961
    }
1✔
1962

1963
    #[test]
1964
    fn test_input_reduction_mod_exp() {
1✔
1965
        // Test that base >= modulus is handled correctly
1966
        let modulus = 13u32;
1✔
1967

1968
        // Base larger than modulus
1969
        let base = 27u32; // 27 mod 13 = 1
1✔
1970
        let exp = 100u32;
1✔
1971
        let expected = 1u32; // 1^100 = 1
1✔
1972
        let got = basic_montgomery_mod_exp(base, exp, modulus).unwrap();
1✔
1973
        assert_eq!(got, expected);
1✔
1974

1975
        // Another case
1976
        let base = 100u32; // 100 mod 13 = 9
1✔
1977
        let exp = 3u32;
1✔
1978
        let expected = crate::exp::basic_mod_exp(9u32, 3, modulus); // 729 mod 13 = 1
1✔
1979
        let got = basic_montgomery_mod_exp(base, exp, modulus).unwrap();
1✔
1980
        assert_eq!(got, expected);
1✔
1981
    }
1✔
1982

1983
    /// wide_redc_ct must produce the same output as wide_redc for all inputs.
1984
    /// Exhaustive over a small u8 modulus to cover both the "needs subtraction"
1985
    /// and "doesn't need subtraction" branches plus the extra_bit case.
1986
    #[test]
1987
    fn test_wide_redc_ct_matches_nct_u8() {
1✔
1988
        let modulus = 13u8;
1✔
1989
        for t_lo in 0u8..=255 {
256✔
1990
            for t_hi in 0u8..modulus {
3,328✔
1991
                // Pick an arbitrary odd n_prime — exact value doesn't matter for
1992
                // comparing the two implementations; they share inputs.
1993
                let n_prime = 11u8;
3,328✔
1994
                let nct = wide_redc(t_lo, t_hi, modulus, n_prime);
3,328✔
1995
                let ct = wide_redc_ct(t_lo, t_hi, modulus, n_prime);
3,328✔
1996
                assert_eq!(nct, ct, "wide_redc_ct mismatch at t_lo={t_lo} t_hi={t_hi}");
3,328✔
1997
            }
1998
        }
1999
    }
1✔
2000

2001
    /// All four `strict_wide_*` functions must produce identical outputs to
2002
    /// their by-value siblings on Copy types. Same input grid as the
2003
    /// `wide_redc_ct` equivalence test; local `basic_*` aliases keep the
2004
    /// comparison readable.
2005
    #[test]
2006
    fn test_strict_wide_matches_basic_u8() {
1✔
2007
        // Local aliases for "the by-value (basic-flavor) version" purely so
2008
        // the asserts read symmetrically.
2009
        use super::{
2010
            wide_montgomery_mul as basic_wide_montgomery_mul,
2011
            wide_montgomery_mul_ct as basic_wide_montgomery_mul_ct, wide_redc as basic_wide_redc,
2012
            wide_redc_ct as basic_wide_redc_ct,
2013
        };
2014

2015
        let modulus = 13u8;
1✔
2016
        let n_prime = 11u8;
1✔
2017
        for t_lo in 0u8..=255 {
256✔
2018
            for t_hi in 0u8..modulus {
3,328✔
2019
                assert_eq!(
3,328✔
2020
                    basic_wide_redc(t_lo, t_hi, modulus, n_prime),
3,328✔
2021
                    strict_wide_redc(&t_lo, &t_hi, &modulus, &n_prime),
3,328✔
2022
                    "strict_wide_redc mismatch at t_lo={t_lo} t_hi={t_hi}"
2023
                );
2024
                assert_eq!(
3,328✔
2025
                    basic_wide_redc_ct(t_lo, t_hi, modulus, n_prime),
3,328✔
2026
                    strict_wide_redc_ct(&t_lo, &t_hi, &modulus, &n_prime),
3,328✔
2027
                    "strict_wide_redc_ct mismatch at t_lo={t_lo} t_hi={t_hi}"
2028
                );
2029
            }
2030
        }
2031

2032
        // mul wrappers exercise a different code path (wide_mul + redc).
2033
        for a in 0u8..modulus {
13✔
2034
            for b in 0u8..modulus {
169✔
2035
                assert_eq!(
169✔
2036
                    basic_wide_montgomery_mul(a, b, modulus, n_prime),
169✔
2037
                    strict_wide_montgomery_mul(&a, &b, &modulus, &n_prime),
169✔
2038
                    "strict_wide_montgomery_mul mismatch at a={a} b={b}"
2039
                );
2040
                assert_eq!(
169✔
2041
                    basic_wide_montgomery_mul_ct(a, b, modulus, n_prime),
169✔
2042
                    strict_wide_montgomery_mul_ct(&a, &b, &modulus, &n_prime),
169✔
2043
                    "strict_wide_montgomery_mul_ct mismatch at a={a} b={b}"
2044
                );
2045
            }
2046
        }
2047
    }
1✔
2048

2049
    /// `mul_acc` with a zero accumulator + `redc` must produce the same
2050
    /// result as the existing fused `wide_montgomery_mul`. Identity check
2051
    /// on the new primitive.
2052
    #[test]
2053
    fn test_wide_mul_acc_identity_u8() {
1✔
2054
        let modulus = 13u8;
1✔
2055
        let n_prime = compute_n_prime_newton(modulus, 8);
1✔
2056
        for a in 0u8..modulus {
13✔
2057
            for b in 0u8..modulus {
169✔
2058
                let direct = wide_montgomery_mul(a, b, modulus, n_prime);
169✔
2059
                let (lo, hi) = wide_montgomery_mul_acc(0u8, 0u8, a, b);
169✔
2060
                let via_acc = wide_redc(lo, hi, modulus, n_prime);
169✔
2061
                assert_eq!(direct, via_acc, "identity mismatch at a={a} b={b}");
169✔
2062
            }
2063
        }
2064
    }
1✔
2065

2066
    /// Σ a_i * b_i computed via `mul_acc` per term + one `redc` at the
2067
    /// end must equal the direct residue-domain sum of products. Proves
2068
    /// the accumulation semantics — the core promise of the primitive.
2069
    #[test]
2070
    fn test_wide_mul_acc_dot_product_u8() {
1✔
2071
        let modulus = 13u8;
1✔
2072
        let n_prime = compute_n_prime_newton(modulus, 8);
1✔
2073
        let r_mod_n = compute_r_mod_n(modulus, 8);
1✔
2074
        let r2 = compute_r2_mod_n(r_mod_n, modulus, 8);
1✔
2075
        let to_mont = |x: u8| {
8✔
2076
            let (lo, hi) = x.wide_mul(&r2);
8✔
2077
            wide_redc(lo, hi, modulus, n_prime)
8✔
2078
        };
8✔
2079
        let pairs: &[(u8, u8)] = &[(2, 3), (5, 7), (11, 4), (1, 12)];
1✔
2080

2081
        let (mut acc_lo, mut acc_hi) = (0u8, 0u8);
1✔
2082
        for &(a, b) in pairs {
4✔
2083
            let (l, h) = wide_montgomery_mul_acc(acc_lo, acc_hi, to_mont(a), to_mont(b));
4✔
2084
            acc_lo = l;
4✔
2085
            acc_hi = h;
4✔
2086
        }
4✔
2087
        let result_mont = wide_redc(acc_lo, acc_hi, modulus, n_prime);
1✔
2088
        let result = wide_redc(result_mont, 0u8, modulus, n_prime);
1✔
2089

2090
        let expected = pairs
1✔
2091
            .iter()
1✔
2092
            .fold(0u8, |acc, &(a, b)| (acc + (a * b) % modulus) % modulus);
4✔
2093
        assert_eq!(result, expected);
1✔
2094
    }
1✔
2095

2096
    /// CT and NCT `mul_acc` variants must produce bit-identical output
2097
    /// over a representative input grid.
2098
    #[test]
2099
    fn test_wide_mul_acc_ct_matches_nct_u8() {
1✔
2100
        let modulus = 13u8;
1✔
2101
        for a in 0u8..modulus {
13✔
2102
            for b in 0u8..modulus {
169✔
2103
                for acc_lo in [0u8, 1, 50, 100, 200, 255] {
1,014✔
2104
                    for acc_hi in [0u8, 1, 5, modulus - 1] {
4,056✔
2105
                        let nct = wide_montgomery_mul_acc(acc_lo, acc_hi, a, b);
4,056✔
2106
                        let ct = wide_montgomery_mul_acc_ct(acc_lo, acc_hi, a, b);
4,056✔
2107
                        assert_eq!(
4,056✔
2108
                            nct, ct,
2109
                            "ct/nct mismatch at lo={acc_lo} hi={acc_hi} a={a} b={b}"
2110
                        );
2111
                    }
2112
                }
2113
            }
2114
        }
2115
    }
1✔
2116

2117
    /// Reference-based `strict_*_mul_acc` siblings must match the
2118
    /// by-value forms on `Copy` types.
2119
    #[test]
2120
    fn test_strict_wide_mul_acc_matches_basic_u8() {
1✔
2121
        let modulus = 13u8;
1✔
2122
        for a in 0u8..modulus {
13✔
2123
            for b in 0u8..modulus {
169✔
2124
                for acc_lo in [0u8, 1, 50, 100, 255] {
845✔
2125
                    for acc_hi in [0u8, 1, 5, modulus - 1] {
3,380✔
2126
                        assert_eq!(
3,380✔
2127
                            wide_montgomery_mul_acc(acc_lo, acc_hi, a, b),
3,380✔
2128
                            strict_wide_montgomery_mul_acc(&acc_lo, &acc_hi, &a, &b),
3,380✔
2129
                            "strict mul_acc mismatch at lo={acc_lo} hi={acc_hi} a={a} b={b}"
2130
                        );
2131
                        assert_eq!(
3,380✔
2132
                            wide_montgomery_mul_acc_ct(acc_lo, acc_hi, a, b),
3,380✔
2133
                            strict_wide_montgomery_mul_acc_ct(&acc_lo, &acc_hi, &a, &b),
3,380✔
2134
                            "strict mul_acc_ct mismatch at lo={acc_lo} hi={acc_hi} a={a} b={b}"
2135
                        );
2136
                    }
2137
                }
2138
            }
2139
        }
2140
    }
1✔
2141

2142
    /// ML-KEM-shaped dot product at `q=3329`, `T=u32`. Exercises the
2143
    /// actual use-case: 4 Mont products summed via `mul_acc`, single
2144
    /// `redc` at the end.
2145
    #[test]
2146
    fn test_wide_mul_acc_dot_product_u32_mlkem_q() {
1✔
2147
        let modulus = 3329u32;
1✔
2148
        let w = type_bit_width::<u32>();
1✔
2149
        let n_prime = compute_n_prime_newton(modulus, w);
1✔
2150
        let r2 = compute_r2_mod_n(compute_r_mod_n(modulus, w), modulus, w);
1✔
2151
        let to_mont = |x: u32| {
8✔
2152
            let (lo, hi) = x.wide_mul(&r2);
8✔
2153
            wide_redc(lo, hi, modulus, n_prime)
8✔
2154
        };
8✔
2155
        let pairs: &[(u32, u32)] = &[(123, 456), (789, 1011), (2222, 3000), (1, 3328)];
1✔
2156

2157
        let (mut acc_lo, mut acc_hi) = (0u32, 0u32);
1✔
2158
        for &(a, b) in pairs {
4✔
2159
            let (l, h) = wide_montgomery_mul_acc(acc_lo, acc_hi, to_mont(a), to_mont(b));
4✔
2160
            acc_lo = l;
4✔
2161
            acc_hi = h;
4✔
2162
        }
4✔
2163
        let result_mont = wide_redc(acc_lo, acc_hi, modulus, n_prime);
1✔
2164
        let result = wide_redc(result_mont, 0u32, modulus, n_prime);
1✔
2165

2166
        let expected = pairs.iter().fold(0u64, |acc, &(a, b)| {
4✔
2167
            acc + (a as u64 * b as u64) % modulus as u64
4✔
2168
        }) % modulus as u64;
4✔
2169
        assert_eq!(result as u64, expected);
1✔
2170
    }
1✔
2171

2172
    /// wide_redc_ct matches at FixedUInt sizes.
2173
    ///
2174
    /// Under fixed-bigint's personality typestate, `wide_redc_ct`'s
2175
    /// `ConditionallySelectable`/`ConstantTimeLess` bounds resolve only for
2176
    /// Ct-typed FixedUInts; the Nct-typed precompute values are bridged via
2177
    /// the free `.into()` upgrade, and the CT result is brought back via
2178
    /// `forget_ct()` for cross-personality equality.
2179
    #[test]
2180
    fn test_wide_redc_ct_matches_nct_fixed() {
1✔
2181
        type U128 = FixedUInt<u32, 4>;
2182
        type U128Ct = FixedUInt<u32, 4, Ct>;
2183

2184
        let modulus = !U128::from(0u64) - U128::from(58u64);
1✔
2185
        let w = type_bit_width::<U128>();
1✔
2186
        let n_prime = compute_n_prime_newton(modulus, w);
1✔
2187
        let modulus_ct: U128Ct = modulus.into();
1✔
2188
        let n_prime_ct: U128Ct = n_prime.into();
1✔
2189

2190
        // A mix: small values, values near modulus, and full-width-ish values
2191
        let test_vals = [
1✔
2192
            U128::from(0u64),
1✔
2193
            U128::from(1u64),
1✔
2194
            U128::from(0xDEAD_BEEF_u64),
1✔
2195
            modulus - U128::from(1u64),
1✔
2196
            !U128::from(0u64), // 2^128 - 1 (well above modulus)
1✔
2197
        ];
1✔
2198
        for &t_lo in &test_vals {
5✔
2199
            for &t_hi in &test_vals {
25✔
2200
                let nct = wide_redc(t_lo, t_hi, modulus, n_prime);
25✔
2201
                let t_lo_ct: U128Ct = t_lo.into();
25✔
2202
                let t_hi_ct: U128Ct = t_hi.into();
25✔
2203
                let ct = wide_redc_ct(t_lo_ct, t_hi_ct, modulus_ct, n_prime_ct);
25✔
2204
                assert_eq!(
25✔
2205
                    nct,
2206
                    ct.forget_ct(),
25✔
2207
                    "wide_redc_ct mismatch at t_lo={t_lo:?} t_hi={t_hi:?}"
2208
                );
2209
            }
2210
        }
2211
    }
1✔
2212

2213
    /// basic_montgomery_mod_exp_pr_ct must produce the same output as
2214
    /// the NCT version when the caller pre-reduces base.
2215
    #[test]
2216
    fn test_basic_montgomery_mod_exp_pr_ct_matches_nct_u32_with_external_reduce() {
1✔
2217
        let modulus = 13u32;
1✔
2218
        for base in 0u32..modulus {
13✔
2219
            for exp in 0u32..32 {
416✔
2220
                let nct = basic_montgomery_mod_exp(base, exp, modulus).unwrap();
416✔
2221
                // External pre-reduction: caller's responsibility on
2222
                // the CT path. Inputs are already in [0, modulus) for
2223
                // this test, so the `% modulus` is a no-op observable.
2224
                let ct = basic_montgomery_mod_exp_pr_ct(base % modulus, exp, modulus).unwrap();
416✔
2225
                assert_eq!(nct, ct, "mod_exp_pr_ct mismatch at base={base} exp={exp}");
416✔
2226
            }
2227
        }
2228
    }
1✔
2229

2230
    /// basic_montgomery_mod_exp_pr_ct must produce the same output as the NCT
2231
    /// _pr version for pre-reduced inputs.
2232
    #[test]
2233
    fn test_basic_montgomery_mod_exp_pr_ct_matches_nct_u32() {
1✔
2234
        let modulus = 13u32;
1✔
2235
        for base in 0u32..modulus {
13✔
2236
            for exp in 0u32..32 {
416✔
2237
                let nct = basic_montgomery_mod_exp_pr(base, exp, modulus).unwrap();
416✔
2238
                let ct = basic_montgomery_mod_exp_pr_ct(base, exp, modulus).unwrap();
416✔
2239
                assert_eq!(nct, ct, "mod_exp_pr_ct mismatch at base={base} exp={exp}");
416✔
2240
            }
2241
        }
2242
    }
1✔
2243

2244
    /// CT exp matches NCT exp at FixedUInt sizes.
2245
    ///
2246
    /// See [`test_wide_redc_ct_matches_nct_fixed`] for the personality-bridge
2247
    /// rationale — Ct-typed inputs are required for `_ct` functions.
2248
    #[test]
2249
    fn test_basic_montgomery_mod_exp_pr_ct_matches_nct_fixed() {
1✔
2250
        type U128 = FixedUInt<u32, 4>;
2251
        type U128Ct = FixedUInt<u32, 4, Ct>;
2252

2253
        let modulus = !U128::from(0u64) - U128::from(58u64); // 2^128 - 59 (odd prime)
1✔
2254
        let modulus_ct: U128Ct = modulus.into();
1✔
2255
        let bases = [U128::from(2u64), U128::from(0xDEAD_BEEF_u64)];
1✔
2256
        let exps = [
1✔
2257
            U128::from(0u64),
1✔
2258
            U128::from(1u64),
1✔
2259
            U128::from(7u64),
1✔
2260
            U128::from(0xCAFE_BABE_u64),
1✔
2261
        ];
1✔
2262
        for &base in &bases {
2✔
2263
            for &exp in &exps {
8✔
2264
                let nct = basic_montgomery_mod_exp_pr(base, exp, modulus).unwrap();
8✔
2265
                let base_ct: U128Ct = base.into();
8✔
2266
                let exp_ct: U128Ct = exp.into();
8✔
2267
                let ct = basic_montgomery_mod_exp_pr_ct(base_ct, exp_ct, modulus_ct).unwrap();
8✔
2268
                assert_eq!(
8✔
2269
                    nct,
2270
                    ct.forget_ct(),
8✔
2271
                    "mod_exp_pr_ct mismatch at base={base:?} exp={exp:?}"
2272
                );
2273
            }
2274
        }
2275
    }
1✔
2276

2277
    /// Equivalence of the NCT and CT high-half accumulators. Exhaustive over
2278
    /// `result` (u8), both carry flags. Confirms `accumulate_high_half_carry_ct`
2279
    /// produces the same `(result, extra_bit)` pair as `accumulate_high_half_carry`
2280
    /// for every input — the swap inside `wide_redc_ct` / `strict_wide_redc_ct`
2281
    /// is purely a side-channel hardening, not a semantic change.
2282
    #[test]
2283
    fn test_accumulate_high_half_carry_ct_matches_nct_u8() {
1✔
2284
        for result in 0u8..=255 {
256✔
2285
            for &carry1 in &[false, true] {
512✔
2286
                for &carry2 in &[false, true] {
1,024✔
2287
                    let nct = accumulate_high_half_carry(result, carry1, carry2);
1,024✔
2288
                    let ct = accumulate_high_half_carry_ct(result, carry1, carry2);
1,024✔
2289
                    assert_eq!(
1,024✔
2290
                        nct, ct,
2291
                        "mismatch at result={result} carry1={carry1} carry2={carry2}"
2292
                    );
2293
                }
2294
            }
2295
        }
2296
    }
1✔
2297
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc