• 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

99.49
/modmath/src/exp.rs
1
#[cfg(feature = "nightly")]
2
use super::mul::const_mod_mul;
3
use super::mul::{basic_mod_mul_pr, constrained_mod_mul_pr, strict_mod_mul_pr};
4

5
#[cfg(feature = "nightly")]
6
use const_num_traits::{One, OverflowingAdd, OverflowingSub, Zero};
7

8
#[cfg(feature = "nightly")]
9
c0nst::c0nst! {
10
    /// # Const Modular Exponentiation
11
    /// Const-evaluable version of modular exponentiation.
12
    pub c0nst fn const_mod_exp<T>(mut base: T, exponent: T, modulus: T) -> T
13
    where
14
        T: [c0nst] core::cmp::PartialOrd
15
            + [c0nst] core::cmp::PartialEq
16
            + Copy
17
            + [c0nst] Zero
18
            + [c0nst] One
19
            + [c0nst] core::ops::BitAnd<Output = T>
20
            + [c0nst] OverflowingAdd
21
            + [c0nst] core::ops::Add<Output = T>
22
            + [c0nst] OverflowingSub
23
            + [c0nst] core::ops::Sub<Output = T>
24
            + [c0nst] core::ops::Shr<usize, Output = T>
25
            + [c0nst] core::ops::ShrAssign<usize>
26
            + [c0nst] core::ops::Rem<Output = T>,
27
    {
28
        let mut result = T::one() % modulus;
29
        let mut exp = exponent;
30

31
        base = base % modulus;
32

33
        while exp > T::zero() {
34
            if exp & T::one() == T::one() {
35
                result = const_mod_mul(result, base, modulus);
36
            }
37
            exp >>= 1;
38

39
            if exp > T::zero() {
40
                base = const_mod_mul(base, base, modulus);
41
            }
42
        }
43
        result
44
    }
45
}
46

47
/// # Modular Exponentiation (Basic)
48
/// Simple version that operates on values and copies them. Requires
49
/// `WrappingAdd` and `WrappingSub` traits to be implemented.
50
pub fn basic_mod_exp<T>(base: T, exponent: T, modulus: T) -> T
450✔
51
where
450✔
52
    T: PartialOrd
450✔
53
        + const_num_traits::One
450✔
54
        + const_num_traits::Zero
450✔
55
        + crate::parity::Parity
450✔
56
        + core::ops::Rem<Output = T>
450✔
57
        + core::ops::Shr<usize, Output = T>
450✔
58
        + const_num_traits::ops::wrapping::WrappingAdd
450✔
59
        + const_num_traits::ops::wrapping::WrappingSub
450✔
60
        + core::ops::Add<Output = T>
450✔
61
        + core::ops::Sub<Output = T>
450✔
62
        + core::ops::ShrAssign<usize>
450✔
63
        + Copy
450✔
64
        + crate::NonCt,
450✔
65
{
66
    if modulus == T::one() {
450✔
67
        return T::zero();
3✔
68
    }
447✔
69
    basic_mod_exp_pr(base % modulus, exponent, modulus)
447✔
70
}
450✔
71

72
/// # Modular Exponentiation (Basic, proven-non-zero modulus). **Infallible.**
73
///
74
/// The `modulus == 1` early-return is preserved (any `x mod 1 == 0`) —
75
/// `T::NonZero` proves `modulus != 0`, not `modulus > 1`.
76
pub fn basic_mod_exp_nz<T>(base: T, exponent: T, modulus: T::NonZero) -> T
21✔
77
where
21✔
78
    T: PartialOrd
21✔
79
        + const_num_traits::One
21✔
80
        + const_num_traits::Zero
21✔
81
        + crate::parity::Parity
21✔
82
        + const_num_traits::HasNonZero
21✔
83
        + const_num_traits::DivNonZero<Output = T>
21✔
84
        + core::ops::Shr<usize, Output = T>
21✔
85
        + const_num_traits::ops::wrapping::WrappingAdd
21✔
86
        + const_num_traits::ops::wrapping::WrappingSub
21✔
87
        + core::ops::Add<Output = T>
21✔
88
        + core::ops::Sub<Output = T>
21✔
89
        + core::ops::ShrAssign<usize>
21✔
90
        + Copy
21✔
91
        + crate::NonCt,
21✔
92
{
93
    let m_raw = T::nonzero_get(modulus);
21✔
94
    if m_raw == T::one() {
21✔
95
        return T::zero();
1✔
96
    }
20✔
97
    basic_mod_exp_pr(base.rem_nonzero(modulus), exponent, m_raw)
20✔
98
}
21✔
99

100
/// # Modular Exponentiation (Basic, pre-reduced)
101
/// Precondition: if `modulus > 1`, then `base < modulus`. No `Rem` bound.
102
/// Returns `0` when `modulus == 1`.
103
///
104
/// Note: this only removes the division side-channel from the signature.
105
/// The square-and-multiply loop still branches on `exp.is_odd()`; for a
106
/// constant-time ladder, use the Montgomery wide-REDC path.
107
pub fn basic_mod_exp_pr<T>(mut base: T, exponent: T, modulus: T) -> T
500✔
108
where
500✔
109
    T: PartialOrd
500✔
110
        + const_num_traits::One
500✔
111
        + const_num_traits::Zero
500✔
112
        + crate::parity::Parity
500✔
113
        + core::ops::Shr<usize, Output = T>
500✔
114
        + const_num_traits::ops::wrapping::WrappingAdd
500✔
115
        + const_num_traits::ops::wrapping::WrappingSub
500✔
116
        + core::ops::Add<Output = T>
500✔
117
        + core::ops::Sub<Output = T>
500✔
118
        + core::ops::ShrAssign<usize>
500✔
119
        + Copy
500✔
120
        + crate::NonCt,
500✔
121
{
122
    // x mod 1 == 0 for every x, including 1. The square-and-multiply loop
123
    // below starts with `result = T::one()` and would return 1 in that case.
124
    if modulus == T::one() {
500✔
125
        return T::zero();
30✔
126
    }
470✔
127
    let mut result = T::one();
470✔
128
    let mut exp = exponent;
470✔
129

130
    while exp > T::zero() {
2,142✔
131
        if exp.is_odd() {
1,672✔
132
            result = basic_mod_mul_pr(result, base, modulus);
910✔
133
        }
910✔
134
        exp >>= 1;
1,672✔
135

136
        if exp > T::zero() {
1,672✔
137
            base = basic_mod_mul_pr(base, base, modulus);
1,242✔
138
        }
1,242✔
139
    }
140
    result
470✔
141
}
500✔
142

143
/// # Modular Exponentiation (Constrained)
144
/// Version that works with references, requires `WrappingAdd` and
145
/// `WrappingSub` traits to be implemented.
146
pub fn constrained_mod_exp<T>(mut base: T, exponent: &T, modulus: &T) -> T
52✔
147
where
52✔
148
    T: Copy
52✔
149
        + PartialOrd
52✔
150
        + const_num_traits::One
52✔
151
        + const_num_traits::Zero
52✔
152
        + crate::parity::Parity
52✔
153
        + const_num_traits::ops::wrapping::WrappingAdd
52✔
154
        + const_num_traits::ops::wrapping::WrappingSub
52✔
155
        + core::ops::Add<Output = T>
52✔
156
        + core::ops::Sub<Output = T>
52✔
157
        + core::ops::ShrAssign<usize>
52✔
158
        + core::ops::Shr<usize, Output = T>
52✔
159
        + crate::NonCt,
52✔
160
    for<'a> T: core::ops::RemAssign<&'a T>
52✔
161
        + core::ops::DivAssign<&'a T>
52✔
162
        + core::ops::Rem<&'a T, Output = T>,
52✔
163
    for<'a> &'a T: core::ops::Rem<&'a T, Output = T>,
52✔
164
{
165
    if modulus == &T::one() {
52✔
166
        return T::zero();
3✔
167
    }
49✔
168
    base.rem_assign(modulus);
49✔
169
    constrained_mod_exp_pr(base, exponent, modulus)
49✔
170
}
52✔
171

172
/// # Modular Exponentiation (Constrained, proven-non-zero modulus). **Infallible.**
173
pub fn constrained_mod_exp_nz<T>(base: T, exponent: &T, modulus: T::NonZero) -> T
20✔
174
where
20✔
175
    T: Copy
20✔
176
        + PartialOrd
20✔
177
        + const_num_traits::One
20✔
178
        + const_num_traits::Zero
20✔
179
        + crate::parity::Parity
20✔
180
        + const_num_traits::HasNonZero
20✔
181
        + const_num_traits::DivNonZero<Output = T>
20✔
182
        + const_num_traits::ops::wrapping::WrappingAdd
20✔
183
        + const_num_traits::ops::wrapping::WrappingSub
20✔
184
        + core::ops::Add<Output = T>
20✔
185
        + core::ops::Sub<Output = T>
20✔
186
        + core::ops::ShrAssign<usize>
20✔
187
        + core::ops::Shr<usize, Output = T>
20✔
188
        + crate::NonCt,
20✔
189
{
190
    let m_raw = T::nonzero_get(modulus);
20✔
191
    if m_raw == T::one() {
20✔
NEW
192
        return T::zero();
×
193
    }
20✔
194
    constrained_mod_exp_pr(base.rem_nonzero(modulus), exponent, &m_raw)
20✔
195
}
20✔
196

197
/// # Modular Exponentiation (Constrained, pre-reduced)
198
/// Precondition: if `*modulus > 1`, then `base < *modulus`. No `Rem` family bound.
199
/// Returns `0` when `*modulus == 1`.
200
pub fn constrained_mod_exp_pr<T>(mut base: T, exponent: &T, modulus: &T) -> T
101✔
201
where
101✔
202
    T: Copy
101✔
203
        + PartialOrd
101✔
204
        + const_num_traits::One
101✔
205
        + const_num_traits::Zero
101✔
206
        + crate::parity::Parity
101✔
207
        + const_num_traits::ops::wrapping::WrappingAdd
101✔
208
        + const_num_traits::ops::wrapping::WrappingSub
101✔
209
        + core::ops::Add<Output = T>
101✔
210
        + core::ops::Sub<Output = T>
101✔
211
        + core::ops::ShrAssign<usize>
101✔
212
        + core::ops::Shr<usize, Output = T>
101✔
213
        + crate::NonCt,
101✔
214
{
215
    // See `basic_mod_exp_pr` for the modulus==1 rationale.
216
    if modulus == &T::one() {
101✔
217
        return T::zero();
30✔
218
    }
71✔
219
    let mut result = T::one();
71✔
220
    let mut exp = T::zero().wrapping_add(*exponent);
71✔
221
    while exp > T::zero() {
401✔
222
        if exp.is_odd() {
330✔
223
            result = constrained_mod_mul_pr(result, &base, modulus);
120✔
224
        }
210✔
225
        exp >>= 1;
330✔
226
        if exp > T::zero() {
330✔
227
            // Squaring step: `mul_pr` consumes `a` (first arg) and borrows
273✔
228
            // `b` (second arg); when both come from the same `base`, we
273✔
229
            // still need one clone so the borrow doesn't alias the move.
273✔
230
            let tmp_base = T::zero().wrapping_add(base);
273✔
231
            base = constrained_mod_mul_pr(base, &tmp_base, modulus);
273✔
232
        }
273✔
233
    }
234
    result
71✔
235
}
101✔
236

237
/// # Modular Exponentiation (Strict)
238
/// Most constrained version that works with references. Requires
239
/// `OverflowingAdd` and `OverflowingSub` traits to be implemented, and
240
/// all multiplication contraints as well.
241
pub fn strict_mod_exp<T>(mut base: T, exponent: &T, modulus: &T) -> T
185✔
242
where
185✔
243
    T: Copy
185✔
244
        + PartialOrd
185✔
245
        + const_num_traits::One
185✔
246
        + const_num_traits::Zero
185✔
247
        + crate::parity::Parity
185✔
248
        + const_num_traits::ops::overflowing::OverflowingAdd
185✔
249
        + const_num_traits::ops::overflowing::OverflowingSub
185✔
250
        + core::ops::Add<Output = T>
185✔
251
        + core::ops::Sub<Output = T>
185✔
252
        + core::ops::Shr<usize, Output = T>
185✔
253
        + crate::NonCt,
185✔
254
    for<'a> T: core::ops::RemAssign<&'a T>
185✔
255
        + core::ops::DivAssign<&'a T>
185✔
256
        + core::ops::ShrAssign<usize>
185✔
257
        + core::ops::Rem<&'a T, Output = T>,
185✔
258
    for<'a> &'a T: core::ops::Rem<&'a T, Output = T>,
185✔
259
{
260
    if modulus == &T::one() {
185✔
261
        return T::zero();
3✔
262
    }
182✔
263
    base.rem_assign(modulus);
182✔
264
    strict_mod_exp_pr(base, exponent, modulus)
182✔
265
}
185✔
266

267
/// # Modular Exponentiation (Strict, proven-non-zero modulus). **Infallible.**
268
pub fn strict_mod_exp_nz<T>(base: T, exponent: &T, modulus: T::NonZero) -> T
20✔
269
where
20✔
270
    T: Copy
20✔
271
        + PartialOrd
20✔
272
        + const_num_traits::One
20✔
273
        + const_num_traits::Zero
20✔
274
        + crate::parity::Parity
20✔
275
        + const_num_traits::HasNonZero
20✔
276
        + const_num_traits::DivNonZero<Output = T>
20✔
277
        + const_num_traits::ops::overflowing::OverflowingAdd
20✔
278
        + const_num_traits::ops::overflowing::OverflowingSub
20✔
279
        + core::ops::Add<Output = T>
20✔
280
        + core::ops::Sub<Output = T>
20✔
281
        + core::ops::Shr<usize, Output = T>
20✔
282
        + crate::NonCt,
20✔
283
    for<'a> T: core::ops::ShrAssign<usize>,
20✔
284
{
285
    let m_raw = T::nonzero_get(modulus);
20✔
286
    if m_raw == T::one() {
20✔
NEW
287
        return T::zero();
×
288
    }
20✔
289
    strict_mod_exp_pr(base.rem_nonzero(modulus), exponent, &m_raw)
20✔
290
}
20✔
291

292
/// # Modular Exponentiation (Strict, pre-reduced)
293
/// Precondition: if `*modulus > 1`, then `base < *modulus`. No `Rem` family bound.
294
/// Returns `0` when `*modulus == 1`.
295
pub fn strict_mod_exp_pr<T>(mut base: T, exponent: &T, modulus: &T) -> T
234✔
296
where
234✔
297
    T: Copy
234✔
298
        + PartialOrd
234✔
299
        + const_num_traits::One
234✔
300
        + const_num_traits::Zero
234✔
301
        + crate::parity::Parity
234✔
302
        + const_num_traits::ops::overflowing::OverflowingAdd
234✔
303
        + const_num_traits::ops::overflowing::OverflowingSub
234✔
304
        + core::ops::Add<Output = T>
234✔
305
        + core::ops::Sub<Output = T>
234✔
306
        + core::ops::Shr<usize, Output = T>
234✔
307
        + crate::NonCt,
234✔
308
    for<'a> T: core::ops::ShrAssign<usize>,
234✔
309
{
310
    // See `basic_mod_exp_pr` for the modulus==1 rationale.
311
    if modulus == &T::one() {
234✔
312
        return T::zero();
30✔
313
    }
204✔
314
    let mut result = T::one();
204✔
315
    let mut exp = T::zero().overflowing_add(*exponent).0;
204✔
316

317
    while exp > T::zero() {
896✔
318
        if exp.is_odd() {
692✔
319
            result = strict_mod_mul_pr(result, &base, modulus);
329✔
320
        }
363✔
321
        exp >>= 1;
692✔
322

323
        if exp > T::zero() {
692✔
324
            // Squaring step: `mul_pr` consumes `a` (first arg) and borrows
515✔
325
            // `b` (second arg); when both come from the same `base`, we
515✔
326
            // still need one clone so the borrow doesn't alias the move.
515✔
327
            let tmp_base = T::zero().overflowing_add(base).0;
515✔
328
            base = strict_mod_mul_pr(base, &tmp_base, modulus);
515✔
329
        }
515✔
330
    }
331
    result
204✔
332
}
234✔
333

334
#[cfg(test)]
335
macro_rules! select_mod_exp {
336
    ($mod_exp:path, $t:ty, by_ref) => {
337
        fn mod_exp(a: $t, b: &$t, m: &$t) -> $t {
50✔
338
            $mod_exp(a, b, m)
50✔
339
        }
50✔
340
    };
341
    ($mod_exp:path, $t:ty, by_val) => {
342
        fn mod_exp(a: $t, b: &$t, m: &$t) -> $t {
25✔
343
            $mod_exp(a, *b, *m)
25✔
344
        }
25✔
345
    };
346
}
347

348
#[cfg(test)]
349
macro_rules! generate_mod_exp_tests_block_64 {
350
    ($mod_add:path, $t:ty, $by_ref:tt) => {
351
        select_mod_exp!($mod_add, $t, $by_ref);
352

353
        #[test]
354
        fn test_basic_small_values() {
3✔
355
            assert_eq!(mod_exp(2_u64, &3_u64, &5_u64), 3_u64); // 2^3 % 5 = 8 % 5 = 3
3✔
356
            assert_eq!(mod_exp(5_u64, &0_u64, &7_u64), 1_u64); // 5^0 % 7 = 1
3✔
357
        }
3✔
358

359
        #[test]
360
        fn test_basic_base_or_exponent_1() {
3✔
361
            assert_eq!(mod_exp(1_u64, &10_u64, &7_u64), 1_u64); // 1^10 % 7 = 1
3✔
362
            assert_eq!(mod_exp(7_u64, &1_u64, &13_u64), 7_u64); // 7^1 % 13 = 7
3✔
363
        }
3✔
364

365
        #[test]
366
        fn test_identity_modulus_of_1() {
3✔
367
            assert_eq!(mod_exp(10_u64, &10_u64, &1_u64), 0_u64); // Any number % 1 = 0
3✔
368
        }
3✔
369

370
        #[test]
371
        fn test_identity_exponent_of_0() {
3✔
372
            assert_eq!(mod_exp(5_u64, &0_u64, &9_u64), 1_u64); // 5^0 % 9 = 1
3✔
373
        }
3✔
374

375
        #[test]
376
        fn test_identity_exponent_of_0_modulus_of_1() {
3✔
377
            assert_eq!(mod_exp(5_u64, &0_u64, &1_u64), 0_u64); // 5^0 % 1 = 0
3✔
378
            assert_eq!(mod_exp(0_u64, &0_u64, &1_u64), 0_u64); // 0^0 % 1 = 0
3✔
379
        }
3✔
380

381
        #[test]
382
        fn test_identity_zero_to_the_zero() {
3✔
383
            // Handle 0^0 case based on how it's defined in your mod_exp implementation.
384
            assert_eq!(mod_exp(0_u64, &0_u64, &7_u64), 1_u64); // This assumes 0^0 = 1
3✔
385
        }
3✔
386

387
        #[test]
388
        fn test_edge_max_u64_values() {
3✔
389
            assert_eq!(mod_exp(u64::MAX, &2_u64, &u64::MAX), 0_u64); // (2^63 - 1)^2 % (2^63 - 1) = 0
3✔
390
            assert_eq!(
3✔
391
                mod_exp(u64::MAX, &2_u64, &1_000_000_007_u64),
3✔
392
                114_944_269_u64
393
            );
394
            // Big exponent mod test
395
        }
3✔
396

397
        #[test]
398
        fn test_edge_base_of_zero() {
3✔
399
            assert_eq!(mod_exp(0_u64, &10_u64, &7_u64), 0_u64); // 0^10 % 7 = 0
3✔
400
        }
3✔
401

402
        #[test]
403
        fn test_prime_modulus() {
3✔
404
            assert_eq!(mod_exp(7_u64, &13_u64, &19_u64), 7_u64); // 7^13 % 19 = 7
3✔
405
            assert_eq!(mod_exp(3_u64, &13_u64, &17_u64), 12_u64); // 3^13 % 17 = 12
3✔
406
        }
3✔
407

408
        #[test]
409
        fn test_large_exponent() {
3✔
410
            // This test assumes efficient modular exponentiation like exponentiation by squaring.
411
            assert_eq!(mod_exp(7_u64, &(1 << 20), &13_u64), 9_u64); // 7^2^20 % 13 = 9
3✔
412
        }
3✔
413

414
        #[test]
415
        fn test_overflow_handling() {
3✔
416
            assert_eq!(mod_exp(2_u64.pow(32), &2_u64.pow(32), &97_u64), 35_u64); // Big exponent/modulus
3✔
417
            assert_eq!(
3✔
418
                mod_exp(2_u64.pow(63), &2_u64.pow(63), &1_000_000_007_u64),
3✔
419
                719_537_220_u64
420
            );
421
        }
3✔
422

423
        #[test]
424
        fn test_coprime_values() {
3✔
425
            assert_eq!(
3✔
426
                mod_exp(123_456_789_u64, &987_654_321_u64, &1_000_000_007_u64),
3✔
427
                652_541_198_u64
428
            );
429
        }
3✔
430
    };
431
}
432

433
#[cfg(test)]
434
macro_rules! generate_mod_exp_tests_block_8 {
435
    ($mod_add:path, $t:ty, $by_ref:tt) => {
436
        select_mod_exp!($mod_add, $t, $by_ref);
437

438
        #[test]
439
        fn test_edge_max_u8_values() {
3✔
440
            // Equivalent of mod_exp(u64::MAX, 2_u64, u64::MAX) with u8
441
            assert_eq!(mod_exp(u8::MAX, &2_u8, &u8::MAX), 0_u8); // (255^2) % 255 = 0
3✔
442
            assert_eq!(mod_exp(u8::MAX, &2_u8, &97_u8), 35_u8); // (255^2) % 97 = 35
3✔
443
        }
3✔
444

445
        #[test]
446
        fn test_big_exponent_mod_u8() {
3✔
447
            assert_eq!(mod_exp(u8::MAX, &2_u8, &97_u8), 35_u8); // (255^2) % 97 = 35
3✔
448
        }
3✔
449

450
        #[test]
451
        fn test_overflow_handling_u8() {
3✔
452
            // Equivalent of mod_exp(2^32, 2^32, 97) with u8
453
            assert_eq!(mod_exp(2_u8.pow(4), &2_u8.pow(4), &97_u8), 61_u8); // (16^16) % 97 = 61
3✔
454
        }
3✔
455

456
        #[test]
457
        fn test_prime_modulus_u8() {
3✔
458
            // Equivalent of mod_exp(7_u64, 13_u64, 19_u64) with u8
459
            assert_eq!(mod_exp(7_u8, &13_u8, &19_u8), 7_u8); // 7^13 % 19 = 7
3✔
460
        }
3✔
461
    };
462
}
463
#[cfg(test)]
464
macro_rules! generate_mod_exp_tests_block_16 {
465
    ($mod_add:path, $t:ty, $by_ref:tt) => {
466
        select_mod_exp!($mod_add, $t, $by_ref);
467

468
        #[test]
469
        fn test_edge_max_u16_values() {
3✔
470
            // Equivalent of mod_exp(u64::MAX, 2_u64, u64::MAX) with u16
471
            assert_eq!(mod_exp(u16::MAX, &2_u16, &u16::MAX), 0_u16); // (65535^2) % 65535 = 0
3✔
472
        }
3✔
473
    };
474
}
475

476
#[cfg(test)]
477
macro_rules! generate_mod_exp_tests_block_32 {
478
    ($mod_add:path, $t:ty, $by_ref:tt) => {
479
        select_mod_exp!($mod_add, $t, $by_ref);
480

481
        #[test]
482
        fn test_edge_max_u32_values() {
3✔
483
            // Equivalent of mod_exp(u64::MAX, 2_u64, u64::MAX) with u32
484
            assert_eq!(mod_exp(u32::MAX, &2_u32, &u32::MAX), 0_u32); // (4294967295^2) % 4294967295 = 0
3✔
485
        }
3✔
486
    };
487
}
488

489
#[cfg(test)]
490
mod strict_mod_exp_tests {
491
    use super::strict_mod_exp;
492
    mod u64_tests {
493
        generate_mod_exp_tests_block_64!(super::strict_mod_exp, u64, by_ref);
494
    }
495
    mod u8_tests {
496
        generate_mod_exp_tests_block_8!(super::strict_mod_exp, u8, by_ref);
497
    }
498
    mod u16_tests {
499
        generate_mod_exp_tests_block_16!(super::strict_mod_exp, u16, by_ref);
500
    }
501
    mod u32_tests {
502
        generate_mod_exp_tests_block_32!(super::strict_mod_exp, u32, by_ref);
503
    }
504
}
505

506
#[cfg(test)]
507
mod constrained_mod_exp_tests {
508
    use super::constrained_mod_exp;
509

510
    mod u64_tests {
511
        generate_mod_exp_tests_block_64!(super::constrained_mod_exp, u64, by_ref);
512
    }
513
    mod u8_tests {
514
        generate_mod_exp_tests_block_8!(super::constrained_mod_exp, u8, by_ref);
515
    }
516
    mod u16_tests {
517
        generate_mod_exp_tests_block_16!(super::constrained_mod_exp, u16, by_ref);
518
    }
519
    mod u32_tests {
520
        generate_mod_exp_tests_block_32!(super::constrained_mod_exp, u32, by_ref);
521
    }
522
}
523

524
#[cfg(test)]
525
mod basic_mod_exp_tests {
526
    use super::basic_mod_exp;
527

528
    mod u64_tests {
529
        generate_mod_exp_tests_block_64!(super::basic_mod_exp, u64, by_val);
530
    }
531
    mod u8_tests {
532
        generate_mod_exp_tests_block_8!(super::basic_mod_exp, u8, by_val);
533
    }
534
    mod u16_tests {
535
        generate_mod_exp_tests_block_16!(super::basic_mod_exp, u16, by_val);
536
    }
537
    mod u32_tests {
538
        generate_mod_exp_tests_block_32!(super::basic_mod_exp, u32, by_val);
539
    }
540
}
541

542
#[cfg(test)]
543
#[cfg(feature = "nightly")]
544
const _: () = {
545
    let result = const_mod_exp(2u64, 3u64, 5u64);
546
    assert!(result == 3u64);
547
};
548

549
#[cfg(test)]
550
#[cfg(feature = "nightly")]
551
mod const_mod_exp_tests {
552
    use super::const_mod_exp;
553

554
    mod u64_tests {
555
        generate_mod_exp_tests_block_64!(super::const_mod_exp, u64, by_val);
556
    }
557
    mod u8_tests {
558
        generate_mod_exp_tests_block_8!(super::const_mod_exp, u8, by_val);
559
    }
560
    mod u16_tests {
561
        generate_mod_exp_tests_block_16!(super::const_mod_exp, u16, by_val);
562
    }
563
    mod u32_tests {
564
        generate_mod_exp_tests_block_32!(super::const_mod_exp, u32, by_val);
565
    }
566
}
567

568
#[cfg(test)]
569
macro_rules! exp_test_module {
570
    (
571
        $stem:ident,           // Base name (e.g., "bnum")
572
        $type_path:path,       // Full path to the type
573
        $(type $type_def:ty = $type_expr:ty;)? // Optional type definition
574
        strict: $strict:expr,
575
        constrained: $constrained:expr,
576
        basic: $basic:expr,
577
    ) => {
578
        paste::paste! {
579
            mod [<$stem _tests>] {     // This creates e.g., mod bnum_tests
580
                #[allow(unused_imports)]
581
                use $type_path;
582
                $( type $type_def = $type_expr; )?
583

584
                #[test]
585
                #[allow(unused_variables)]
586
                fn test_mod_exp_basic() {
1✔
587
                    let a = U256::from(5u8);
1✔
588
                    let b = U256::from(3u8);
1✔
589
                    let m = U256::from(13u8);
1✔
590

591
                    // pow(5,3,13)
592
                    let a_val = 5u8;
1✔
593
                    let a = U256::from(a_val);
1✔
594
                    let b = U256::from(3u8);
1✔
595
                    let m = U256::from(13u8);
1✔
596
                    let result = U256::from(8u8);
1✔
597

598
                    crate::maybe_test!($strict, assert_eq!(super::strict_mod_exp(a, &b, &m), result));
1✔
599
                    let a = U256::from(a_val);
1✔
600
                    crate::maybe_test!($constrained, assert_eq!(super::constrained_mod_exp(a, &b, &m), result));
1✔
601
                    let a = U256::from(a_val);
1✔
602
                    crate::maybe_test!($basic, assert_eq!(super::basic_mod_exp(a, b, m), result));
1✔
603

604
                    // pow(123,45,1000)
605
                    let a_val = 123u8;
1✔
606
                    let a = U256::from(a_val);
1✔
607
                    let b = U256::from(45u8);
1✔
608
                    let m = U256::from(1000u16);
1✔
609
                    let result = U256::from(43u16);
1✔
610

611
                    crate::maybe_test!($strict, assert_eq!(super::strict_mod_exp(a, &b, &m), result));
1✔
612
                    let a = U256::from(a_val);
1✔
613
                    crate::maybe_test!($constrained, assert_eq!(super::constrained_mod_exp(a, &b, &m), result));
1✔
614
                    let a = U256::from(a_val);
1✔
615
                    crate::maybe_test!($basic, assert_eq!(super::basic_mod_exp(a, b, m), result));
1✔
616
                }
1✔
617
            }
618
        }
619
    };
620
}
621

622
#[cfg(test)]
623
mod bnum_exp_tests {
624
    use super::basic_mod_exp;
625
    use super::constrained_mod_exp;
626
    use super::strict_mod_exp;
627

628
    //     exp_test_module!(
629
    //         bnum,
630
    //         bnum::types::U256,
631
    //         strict: off, // OverflowingAdd + OverflowingSub is not implemented
632
    //         constrained: on,
633
    //         basic: on,
634
    //     );
635

636
    //     exp_test_module!(
637
    //         bnum_patched,
638
    //         bnum_patched::types::U256,
639
    //         strict: on,
640
    //         constrained: on,
641
    //         basic: on,
642
    //     );
643

644
    //     exp_test_module!(
645
    //         crypto_bigint,
646
    //         crypto_bigint::U256,
647
    //         strict: off, // OverflowingAdd missing
648
    //         constrained: off, // RemAssign
649
    //         basic: off, // RemAssign is not implemented
650
    //     );
651

652
    //     exp_test_module!(
653
    //         crypto_bigint_patched,
654
    //         crypto_bigint_patched::U256,
655
    //         strict: on,
656
    //         constrained: on,
657
    //         basic: on,
658
    //     );
659

660
    //     exp_test_module!(
661
    //         num_bigint,
662
    //         num_bigint::BigUint,
663
    //         type U256 = num_bigint::BigUint;
664
    //         strict: off, // OverflowingAdd missing
665
    //         constrained: off, // WrappingAdd missing
666
    //         basic: off, // Copy cannot be implemented, heap allocation
667
    //     );
668

669
    //     exp_test_module!(
670
    //         num_bigint_patched,
671
    //         num_bigint_patched::BigUint,
672
    //         type U256 = num_bigint_patched::BigUint;
673
    //         strict: on,
674
    //         constrained: on,
675
    //         basic: off, // Copy cannot be implemented, heap allocation
676
    //     );
677

678
    //     exp_test_module!(
679
    //         ibig,
680
    //         ibig::UBig,
681
    //         type U256 = ibig::UBig;
682
    //         strict: off, // OverflowingAdd missing
683
    //         constrained: off, // WrappingAdd missing
684
    //         basic: off, // Copy cannot be implemented, heap allocation
685
    //     );
686

687
    //     exp_test_module!(
688
    //         ibig_patched,
689
    //         ibig_patched::UBig,
690
    //         type U256 = ibig_patched::UBig;
691
    //         strict: on,
692
    //         constrained: on,
693
    //         basic: off, // Copy cannot be implemented, heap allocation
694
    //     );
695

696
    // Default (Nct-personality) FixedUInt provides Rem; the wrapper
697
    // functions are usable here. The Ct personality intentionally omits
698
    // Rem (variable-time on operand magnitudes).
699
    exp_test_module!(
700
        fixed_bigint,
701
        fixed_bigint::FixedUInt,
702
        type U256 = fixed_bigint::FixedUInt<u8, 4>;
703
        strict: on,
704
        constrained: on,
705
        basic: on,
706
    );
707
}
708

709
#[cfg(test)]
710
mod fixed_bigint_pr_tests {
711
    use super::{basic_mod_exp_pr, constrained_mod_exp_pr, strict_mod_exp_pr};
712
    type U256 = fixed_bigint::FixedUInt<u8, 4>;
713

714
    #[test]
715
    fn test_mod_exp_basic_pr() {
1✔
716
        // 5^3 mod 13 = 125 mod 13 = 8
717
        let base = U256::from(5u8);
1✔
718
        let exp = U256::from(3u8);
1✔
719
        let m = U256::from(13u8);
1✔
720
        let expected = U256::from(8u8);
1✔
721

722
        assert_eq!(strict_mod_exp_pr(base, &exp, &m), expected);
1✔
723
        let base = U256::from(5u8);
1✔
724
        assert_eq!(constrained_mod_exp_pr(base, &exp, &m), expected);
1✔
725
        let base = U256::from(5u8);
1✔
726
        assert_eq!(basic_mod_exp_pr(base, exp, m), expected);
1✔
727
    }
1✔
728

729
    #[test]
730
    fn test_mod_exp_zero_exponent_pr() {
1✔
731
        // x^0 mod m = 1 (for m > 1)
732
        let base = U256::from(7u8);
1✔
733
        let exp = U256::from(0u8);
1✔
734
        let m = U256::from(13u8);
1✔
735
        let expected = U256::from(1u8);
1✔
736

737
        assert_eq!(strict_mod_exp_pr(base, &exp, &m), expected);
1✔
738
        let base = U256::from(7u8);
1✔
739
        assert_eq!(constrained_mod_exp_pr(base, &exp, &m), expected);
1✔
740
        let base = U256::from(7u8);
1✔
741
        assert_eq!(basic_mod_exp_pr(base, exp, m), expected);
1✔
742
    }
1✔
743

744
    /// Regression: every value mod 1 is 0, including 1. The square-and-multiply
745
    /// loop starts with `result = T::one()` and used to return 1 here.
746
    #[test]
747
    fn test_mod_exp_modulus_one_pr() {
1✔
748
        let m = U256::from(1u8);
1✔
749
        let zero = U256::from(0u8);
1✔
750
        // Exercise a representative grid of (base, exponent) pairs.
751
        let bases = [0u8, 1, 2, 7, 42, 255];
1✔
752
        let exps = [0u8, 1, 2, 5, 100];
1✔
753
        for &b in &bases {
6✔
754
            for &e in &exps {
30✔
755
                let base = U256::from(b);
30✔
756
                let exp = U256::from(e);
30✔
757
                assert_eq!(strict_mod_exp_pr(base, &exp, &m), zero);
30✔
758
                let base = U256::from(b);
30✔
759
                assert_eq!(constrained_mod_exp_pr(base, &exp, &m), zero);
30✔
760
                let base = U256::from(b);
30✔
761
                assert_eq!(basic_mod_exp_pr(base, exp, m), zero);
30✔
762
            }
763
        }
764
    }
1✔
765
}
766

767
#[cfg(test)]
768
mod nz_tests {
769
    use super::*;
770
    use const_num_traits::HasNonZero;
771

772
    #[test]
773
    fn basic_mod_exp_nz_matches_basic_mod_exp() {
1✔
774
        let m: u32 = 97;
1✔
775
        let m_nz = m.into_nonzero().unwrap();
1✔
776
        for base in [0u32, 1, 2, 96, 200] {
5✔
777
            for exp in [0u32, 1, 5, 96] {
20✔
778
                assert_eq!(
20✔
779
                    basic_mod_exp_nz(base, exp, m_nz),
20✔
780
                    basic_mod_exp(base, exp, m)
20✔
781
                );
782
            }
783
        }
784
    }
1✔
785

786
    #[test]
787
    fn basic_mod_exp_nz_modulus_one_returns_zero() {
1✔
788
        // `T::NonZero` rules out modulus == 0 but not modulus == 1; the
789
        // early-return preserves the `x mod 1 == 0` contract.
790
        let m_nz = 1u32.into_nonzero().unwrap();
1✔
791
        assert_eq!(basic_mod_exp_nz(123u32, 7u32, m_nz), 0);
1✔
792
    }
1✔
793

794
    #[test]
795
    fn constrained_mod_exp_nz_matches_constrained_mod_exp() {
1✔
796
        let m: u32 = 97;
1✔
797
        let m_nz = m.into_nonzero().unwrap();
1✔
798
        for base in [0u32, 1, 2, 96, 200] {
5✔
799
            for exp in [0u32, 1, 5, 96] {
20✔
800
                assert_eq!(
20✔
801
                    constrained_mod_exp_nz(base, &exp, m_nz),
20✔
802
                    constrained_mod_exp(base, &exp, &m)
20✔
803
                );
804
            }
805
        }
806
    }
1✔
807

808
    #[test]
809
    fn strict_mod_exp_nz_matches_strict_mod_exp() {
1✔
810
        let m: u32 = 97;
1✔
811
        let m_nz = m.into_nonzero().unwrap();
1✔
812
        for base in [0u32, 1, 2, 96, 200] {
5✔
813
            for exp in [0u32, 1, 5, 96] {
20✔
814
                assert_eq!(
20✔
815
                    strict_mod_exp_nz(base, &exp, m_nz),
20✔
816
                    strict_mod_exp(base, &exp, &m)
20✔
817
                );
818
            }
819
        }
820
    }
1✔
821
}
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