From 527ffce058b93df7e48595ca1ac4f9f3a1240ff9 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 14:09:49 -0800 Subject: [PATCH 01/16] Bump crypto-bigint to 0.6.0-pre.5 --- .github/workflows/crypto-primes.yml | 4 +- CHANGELOG.md | 11 +++-- Cargo.toml | 6 +-- benches/bench.rs | 18 ++++---- src/hazmat/gcd.rs | 4 +- src/hazmat/jacobi.rs | 2 +- src/hazmat/lucas.rs | 63 ++++++++++++++++++---------- src/hazmat/miller_rabin.rs | 65 +++++++++++++---------------- src/hazmat/precomputed.rs | 6 ++- src/hazmat/sieve.rs | 34 ++++++++------- src/presets.rs | 36 +++++++++------- src/traits.rs | 14 ++----- 12 files changed, 144 insertions(+), 119 deletions(-) diff --git a/.github/workflows/crypto-primes.yml b/.github/workflows/crypto-primes.yml index 00c5cbf..8d1a667 100644 --- a/.github/workflows/crypto-primes.yml +++ b/.github/workflows/crypto-primes.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: rust: - - 1.65.0 # MSRV + - 1.73.0 # MSRV - stable target: - wasm32-unknown-unknown @@ -134,6 +134,6 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.65.0 + toolchain: 1.73.0 profile: minimal - run: cargo build --all-features --benches diff --git a/CHANGELOG.md b/CHANGELOG.md index 8debf14..6c2de85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.5.1] - Unreleased +## [0.6.0-pre.0] - Unreleased -### Fixed +### Changed -- Bumped `crypto-bigint` to 0.5.4. ([#35]) +- Bumped `crypto-bigint` to 0.6.0-pre.5. ([#38]) +- Bumped MSRV to 1.73. (#[38]) +- `MillerRabin::new()` takes an `Odd`-wrapped integer by value. `random_odd_uint()` returns an `Odd`-wrapped integer. (#[38]) +- All bit length-type parameters take `u32` instead of `usize`. (#[38]) -[#35]: https://github.com/entropyxyz/crypto-primes/pull/35 +[#35]: https://github.com/entropyxyz/crypto-primes/pull/38 ## [0.5.0] - 2023-08-20 diff --git a/Cargo.toml b/Cargo.toml index 96fcd7b..54b05f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,16 @@ [package] name = "crypto-primes" -version = "0.5.0" +version = "0.6.0-pre.0" edition = "2021" license = "Apache-2.0 OR MIT" description = "Random prime number generation and primality checking library" repository = "https://github.com/entropyxyz/crypto-primes" readme = "README.md" categories = ["cryptography", "no-std"] -rust-version = "1.65" +rust-version = "1.73" [dependencies] -crypto-bigint = { version = "0.5.4", default-features = false, features = ["rand_core"] } +crypto-bigint = { version = "0.6.0-pre.5", default-features = false, features = ["rand_core"] } rand_core = { version = "0.6.4", default-features = false } openssl = { version = "0.10.39", optional = true, features = ["vendored"] } rug = { version = "1.18", default-features = false, features = ["integer"], optional = true } diff --git a/benches/bench.rs b/benches/bench.rs index 7c469ed..8992b5e 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; -use crypto_bigint::{nlimbs, Uint, U1024}; +use crypto_bigint::{nlimbs, Odd, Uint, U1024}; use rand_chacha::ChaCha8Rng; use rand_core::{CryptoRngCore, OsRng, SeedableRng}; @@ -23,13 +23,13 @@ fn make_rng() -> ChaCha8Rng { } fn make_sieve(rng: &mut impl CryptoRngCore) -> Sieve { - let start: Uint = random_odd_uint(rng, Uint::::BITS); + let start = random_odd_uint::(rng, Uint::::BITS); Sieve::new(&start, Uint::::BITS, false) } -fn make_presieved_num(rng: &mut impl CryptoRngCore) -> Uint { +fn make_presieved_num(rng: &mut impl CryptoRngCore) -> Odd> { let mut sieve = make_sieve(rng); - sieve.next().unwrap() + Odd::new(sieve.next().unwrap()).unwrap() } fn bench_sieve(c: &mut Criterion) { @@ -85,14 +85,14 @@ fn bench_miller_rabin(c: &mut Criterion) { group.bench_function("(U128) creation", |b| { b.iter_batched( || random_odd_uint::<{ nlimbs!(128) }>(&mut OsRng, 128), - |start| MillerRabin::new(&start), + MillerRabin::new, BatchSize::SmallInput, ) }); group.bench_function("(U128) random base test (pre-sieved)", |b| { b.iter_batched( - || MillerRabin::new(&make_presieved_num::<{ nlimbs!(128) }>(&mut OsRng)), + || MillerRabin::new(make_presieved_num::<{ nlimbs!(128) }>(&mut OsRng)), |mr| mr.test_random_base(&mut OsRng), BatchSize::SmallInput, ) @@ -101,14 +101,14 @@ fn bench_miller_rabin(c: &mut Criterion) { group.bench_function("(U1024) creation", |b| { b.iter_batched( || random_odd_uint::<{ nlimbs!(1024) }>(&mut OsRng, 1024), - |start| MillerRabin::new(&start), + MillerRabin::new, BatchSize::SmallInput, ) }); group.bench_function("(U1024) random base test (pre-sieved)", |b| { b.iter_batched( - || MillerRabin::new(&make_presieved_num::<{ nlimbs!(1024) }>(&mut OsRng)), + || MillerRabin::new(make_presieved_num::<{ nlimbs!(1024) }>(&mut OsRng)), |mr| mr.test_random_base(&mut OsRng), BatchSize::SmallInput, ) @@ -257,7 +257,7 @@ fn bench_gmp(c: &mut Criterion) { let mut group = c.benchmark_group("GMP"); fn random(rng: &mut impl CryptoRngCore) -> Integer { - let num: Uint = random_odd_uint(rng, Uint::::BITS); + let num = random_odd_uint::(rng, Uint::::BITS); Integer::from_digits(num.as_words(), Order::Lsf) } diff --git a/src/hazmat/gcd.rs b/src/hazmat/gcd.rs index 0b3d6bf..cb74393 100644 --- a/src/hazmat/gcd.rs +++ b/src/hazmat/gcd.rs @@ -16,7 +16,7 @@ pub(crate) fn gcd(n: &Uint, m: u32) -> u32 { } // Normalize input: the resulting (a, b) are both small, a >= b, and b != 0. - let (mut a, mut b): (u32, u32) = if n.bits() > (u32::BITS as usize) { + let (mut a, mut b): (u32, u32) = if n.bits() > u32::BITS { // `m` is non-zero, so we can unwrap. let (_quo, n) = n.div_rem_limb(NonZero::new(Limb::from(m)).unwrap()); // `n` is a remainder of a division by `u32`, so it can be safely cast to `u32`. @@ -47,7 +47,7 @@ pub(crate) fn gcd(n: &Uint, m: u32) -> u32 { #[cfg(test)] mod tests { - use crypto_bigint::{Encoding, U128}; + use crypto_bigint::U128; use num_bigint::BigUint; use num_integer::Integer; use proptest::prelude::*; diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index 8f2bc04..b8bc3c3 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -151,7 +151,7 @@ mod tests { use alloc::format; - use crypto_bigint::{Encoding, U128}; + use crypto_bigint::U128; use num_bigint::{BigInt, Sign}; use num_modular::ModularSymbols; use proptest::prelude::*; diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 69f0039..de9081b 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -1,7 +1,7 @@ //! Lucas primality test. use crypto_bigint::{ - modular::runtime_mod::{DynResidue, DynResidueParams}, - CheckedAdd, Integer, Limb, Uint, Word, + modular::{MontyForm, MontyParams}, + CheckedAdd, Integer, Limb, Odd, Uint, Word, }; use super::{ @@ -172,17 +172,22 @@ impl LucasBase for BruteForceBase { } /// For the given odd `n`, finds `s` and odd `d` such that `n + 1 == 2^s * d`. -fn decompose(n: &Uint) -> (usize, Uint) { - debug_assert!(bool::from(n.is_odd())); - +fn decompose(n: &Odd>) -> (u32, Odd>) { // Need to be careful here since `n + 1` can overflow. // Instead of adding 1 and counting trailing 0s, we count trailing ones on the original `n`. let s = n.trailing_ones(); - // This won't overflow since the original `n` was odd, so we right-shifted at least once. - let d = Option::from((n >> s).checked_add(&Uint::::ONE)).expect("Integer overflow"); + let d = if s < n.bits_precision() { + // This won't overflow since the original `n` was odd, so we right-shifted at least once. + n.as_ref() + .wrapping_shr(s) + .checked_add(&Uint::ONE) + .expect("Integer overflow") + } else { + Uint::ONE + }; - (s, d) + (s, Odd::new(d).expect("ensured to be odd")) } /// The checks to perform in the Lucas test. @@ -279,10 +284,15 @@ pub fn lucas_test( // R. Crandall, C. Pomerance, "Prime numbers: a computational perspective", // 2nd ed., Springer (2005) (ISBN: 0-387-25282-7, 978-0387-25282-7) - if candidate.is_even().into() { - return Primality::Composite; + if candidate == &Uint::::from(2u32) { + return Primality::Prime; } + let odd_candidate = match Odd::new(*candidate).into() { + Some(x) => x, + None => return Primality::Composite, + }; + // Find the base for the Lucas sequence. let (p, q) = match base.generate(candidate) { Ok((p, q)) => (p, q), @@ -311,14 +321,14 @@ pub fn lucas_test( // Find `d` and `s`, such that `d` is odd and `d * 2^s = n - (D/n)`. // Since `(D/n) == -1` by construction, we're looking for `d * 2^s = n + 1`. - let (s, d) = decompose(candidate); + let (s, d) = decompose(&odd_candidate); // Some constants in Montgomery form - let params = DynResidueParams::::new(candidate); + let params = MontyParams::::new(odd_candidate); - let zero = DynResidue::::zero(params); - let one = DynResidue::::one(params); + let zero = MontyForm::::zero(params); + let one = MontyForm::::one(params); let two = one + one; let minus_two = -two; @@ -327,7 +337,7 @@ pub fn lucas_test( let q = if q_is_one { one } else { - let abs_q = DynResidue::::new(&Uint::::from(q.abs_diff(0)), params); + let abs_q = MontyForm::::new(&Uint::::from(q.abs_diff(0)), params); if q < 0 { -abs_q } else { @@ -340,7 +350,7 @@ pub fn lucas_test( let p = if p_is_one { one } else { - DynResidue::::new(&Uint::::from(p), params) + MontyForm::::new(&Uint::::from(p), params) }; // Compute d-th element of Lucas sequence (U_d(P, Q), V_d(P, Q)), where: @@ -359,11 +369,11 @@ pub fn lucas_test( // Starting with k = 0 let mut vk = two; // keeps V_k - let mut uk = DynResidue::::zero(params); // keeps U_k + let mut uk = MontyForm::::zero(params); // keeps U_k let mut qk = one; // keeps Q^k // D in Montgomery representation - note that it can be negative. - let abs_d = DynResidue::::new(&Uint::::from(discriminant.abs_diff(0)), params); + let abs_d = MontyForm::::new(&Uint::::from(discriminant.abs_diff(0)), params); let d_m = if discriminant < 0 { -abs_d } else { abs_d }; for i in (0..d.bits_vartime()).rev() { @@ -472,7 +482,7 @@ mod tests { use alloc::format; - use crypto_bigint::{Uint, U128, U64}; + use crypto_bigint::{Odd, Uint, U128, U64}; #[cfg(feature = "tests-exhaustive")] use num_prime::nt_funcs::is_prime64; @@ -547,9 +557,18 @@ mod tests { #[test] fn decomposition() { - assert_eq!(decompose(&U128::MAX), (128, U128::ONE)); - assert_eq!(decompose(&U128::ONE), (1, U128::ONE)); - assert_eq!(decompose(&U128::from(7766015u32)), (15, U128::from(237u32))); + assert_eq!( + decompose(&Odd::new(U128::MAX).unwrap()), + (128, Odd::new(U128::ONE).unwrap()) + ); + assert_eq!( + decompose(&Odd::new(U128::ONE).unwrap()), + (1, Odd::new(U128::ONE).unwrap()) + ); + assert_eq!( + decompose(&Odd::new(U128::from(7766015u32)).unwrap()), + (15, Odd::new(U128::from(237u32)).unwrap()) + ); } fn is_slpsp(num: u32) -> bool { diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 29c401a..c773053 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -3,8 +3,8 @@ use rand_core::CryptoRngCore; use crypto_bigint::{ - modular::runtime_mod::{DynResidue, DynResidueParams}, - CheckedAdd, Integer, NonZero, RandomMod, Uint, + modular::{MontyForm, MontyParams}, + CheckedAdd, NonZero, Odd, RandomMod, Uint, }; use super::Primality; @@ -20,11 +20,11 @@ use super::Primality; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct MillerRabin { candidate: Uint, - bit_length: usize, - montgomery_params: DynResidueParams, - one: DynResidue, - minus_one: DynResidue, - s: usize, + bit_length: u32, + montgomery_params: MontyParams, + one: MontyForm, + minus_one: MontyForm, + s: u32, d: Uint, } @@ -32,19 +32,20 @@ impl MillerRabin { /// Initializes a Miller-Rabin test for `candidate`. /// /// Panics if `candidate` is even. - pub fn new(candidate: &Uint) -> Self { - if candidate.is_even().into() { - panic!("`candidate` must be odd."); - } - - let params = DynResidueParams::::new(candidate); - let one = DynResidue::::one(params); + pub fn new(candidate: Odd>) -> Self { + let params = MontyParams::::new(candidate); + let one = MontyForm::::one(params); let minus_one = -one; // Find `s` and odd `d` such that `candidate - 1 == 2^s * d`. - let candidate_minus_one = candidate.wrapping_sub(&Uint::::ONE); - let s = candidate_minus_one.trailing_zeros(); - let d = candidate_minus_one >> s; + let (s, d) = if candidate.as_ref() == &Uint::ONE { + (0, Uint::ONE) + } else { + let candidate_minus_one = candidate.wrapping_sub(&Uint::ONE); + let s = candidate_minus_one.trailing_zeros(); + let d = candidate_minus_one.shr(s); + (s, d) + }; Self { candidate: *candidate, @@ -62,7 +63,7 @@ impl MillerRabin { // TODO: it may be faster to first check that gcd(base, candidate) == 1, // otherwise we can return `Composite` right away. - let base = DynResidue::::new(base, self.montgomery_params); + let base = MontyForm::::new(base, self.montgomery_params); // Implementation detail: bounded exp gets faster every time we decrease the bound // by the window length it uses, which is currently 4 bits. @@ -119,7 +120,7 @@ mod tests { use alloc::format; - use crypto_bigint::{Uint, U1024, U128, U1536, U64}; + use crypto_bigint::{Odd, Uint, U1024, U128, U1536, U64}; use rand_chacha::ChaCha8Rng; use rand_core::{CryptoRngCore, OsRng, SeedableRng}; @@ -131,23 +132,17 @@ mod tests { #[test] fn miller_rabin_derived_traits() { - let mr = MillerRabin::new(&U64::ONE); + let mr = MillerRabin::new(Odd::new(U64::ONE).unwrap()); assert!(format!("{mr:?}").starts_with("MillerRabin")); assert_eq!(mr.clone(), mr); } - #[test] - #[should_panic(expected = "`candidate` must be odd.")] - fn parity_check() { - let _mr = MillerRabin::new(&U64::from(10u32)); - } - #[test] #[should_panic( expected = "No suitable random base possible when `candidate == 3`; use the base 2 test." )] fn random_base_range_check() { - let mr = MillerRabin::new(&U64::from(3u32)); + let mr = MillerRabin::new(Odd::new(U64::from(3u32)).unwrap()); mr.test_random_base(&mut OsRng); } @@ -179,7 +174,7 @@ mod tests { // with about 1/4 probability. So we're expecting less than // 35 out of 100 false positives, seems to work. - let mr = MillerRabin::new(&U64::from(*num)); + let mr = MillerRabin::new(Odd::new(U64::from(*num)).unwrap()); assert_eq!( mr.test_base_two().is_probably_prime(), actual_expected_result @@ -195,9 +190,9 @@ mod tests { #[test] fn trivial() { let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); - let start: U1024 = random_odd_uint(&mut rng, 1024); + let start: Odd = random_odd_uint(&mut rng, 1024); for num in Sieve::new(&start, 1024, false).take(10) { - let mr = MillerRabin::new(&num); + let mr = MillerRabin::new(Odd::new(num).unwrap()); // Trivial tests, must always be true. assert!(mr.test(&1u32.into()).is_probably_prime()); @@ -212,7 +207,7 @@ mod tests { // Mersenne prime 2^127-1 let num = U128::from_be_hex("7fffffffffffffffffffffffffffffff"); - let mr = MillerRabin::new(&num); + let mr = MillerRabin::new(Odd::new(num).unwrap()); assert!(mr.test_base_two().is_probably_prime()); for _ in 0..10 { assert!(mr.test_random_base(&mut rng).is_probably_prime()); @@ -224,7 +219,7 @@ mod tests { let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); for num in pseudoprimes::STRONG_FIBONACCI.iter() { - let mr = MillerRabin::new(num); + let mr = MillerRabin::new(Odd::new(*num).unwrap()); assert!(!mr.test_base_two().is_probably_prime()); for _ in 0..1000 { assert!(!mr.test_random_base(&mut rng).is_probably_prime()); @@ -252,7 +247,7 @@ mod tests { #[test] fn large_carmichael_number() { - let mr = MillerRabin::new(&pseudoprimes::LARGE_CARMICHAEL_NUMBER); + let mr = MillerRabin::new(Odd::new(pseudoprimes::LARGE_CARMICHAEL_NUMBER).unwrap()); // It is known to pass MR tests for all prime bases <307 assert!(mr.test_base_two().is_probably_prime()); @@ -265,7 +260,7 @@ mod tests { fn test_large_primes(nums: &[Uint]) { let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); for num in nums { - let mr = MillerRabin::new(num); + let mr = MillerRabin::new(Odd::new(*num).unwrap()); assert!(mr.test_base_two().is_probably_prime()); for _ in 0..10 { assert!(mr.test_random_base(&mut rng).is_probably_prime()); @@ -292,7 +287,7 @@ mod tests { let spsp = is_spsp(num); - let mr = MillerRabin::new(&U64::from(num)); + let mr = MillerRabin::new(Odd::new(U64::from(num)).unwrap()); let res = mr.test_base_two().is_probably_prime(); let expected = spsp || res_ref; assert_eq!( diff --git a/src/hazmat/precomputed.rs b/src/hazmat/precomputed.rs index bdd363e..0f52f7f 100644 --- a/src/hazmat/precomputed.rs +++ b/src/hazmat/precomputed.rs @@ -151,7 +151,11 @@ const fn create_reciprocals() -> [Reciprocal; SMALL_PRIMES_SIZE] { let mut arr = [Reciprocal::default(); SMALL_PRIMES_SIZE]; let mut i = 0; while i < SMALL_PRIMES_SIZE { - arr[i] = Reciprocal::ct_new(Limb(SMALL_PRIMES[i] as Word)).0; + arr[i] = Reciprocal::new( + Limb(SMALL_PRIMES[i] as Word) + .to_nz() + .expect("ensured to be non-zero"), + ); i += 1; } arr diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 7b29625..7a0fa80 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -3,7 +3,7 @@ use alloc::{vec, vec::Vec}; -use crypto_bigint::{CheckedAdd, Random, Uint}; +use crypto_bigint::{CheckedAdd, Odd, Random, Uint}; use rand_core::CryptoRngCore; use crate::hazmat::precomputed::{SmallPrime, RECIPROCALS, SMALL_PRIMES}; @@ -12,7 +12,10 @@ use crate::hazmat::precomputed::{SmallPrime, RECIPROCALS, SMALL_PRIMES}; /// (that is, with both `0` and `bit_length-1` bits set). /// /// Panics if `bit_length` is 0 or is greater than the bit size of the target `Uint`. -pub fn random_odd_uint(rng: &mut impl CryptoRngCore, bit_length: usize) -> Uint { +pub fn random_odd_uint( + rng: &mut impl CryptoRngCore, + bit_length: u32, +) -> Odd> { if bit_length == 0 { panic!("Bit length must be non-zero"); } @@ -36,7 +39,7 @@ pub fn random_odd_uint(rng: &mut impl CryptoRngCore, bit_length: // Make sure it's the correct bit size random |= Uint::::ONE << (bit_length - 1); - random + Odd::new(random).expect("ensured to be odd") } // The type we use to calculate incremental residues. @@ -59,7 +62,7 @@ pub struct Sieve { incr_limit: Residue, safe_primes: bool, residues: Vec, - max_bit_length: usize, + max_bit_length: u32, produces_nothing: bool, starts_from_exception: bool, last_round: bool, @@ -78,7 +81,7 @@ impl Sieve { /// Panics if `max_bit_length` is zero or greater than the size of the target `Uint`. /// /// If `safe_primes` is `true`, both the returned `n` and `n/2` are sieved. - pub fn new(start: &Uint, max_bit_length: usize, safe_primes: bool) -> Self { + pub fn new(start: &Uint, max_bit_length: u32, safe_primes: bool) -> Self { if max_bit_length == 0 { panic!("The requested bit length cannot be zero"); } @@ -162,14 +165,17 @@ impl Sieve { // Re-calculate residues. for (i, rec) in RECIPROCALS.iter().enumerate().take(self.residues.len()) { - let (_quo, rem) = self.base.ct_div_rem_limb_with_reciprocal(rec); + let (_quo, rem) = self.base.div_rem_limb_with_reciprocal(rec); self.residues[i] = rem.0 as SmallPrime; } // Find the increment limit. - let max_value = (Uint::::ONE << self.max_bit_length).wrapping_sub(&Uint::::ONE); + let max_value = Uint::::ONE + .overflowing_shl(self.max_bit_length) + .unwrap_or(Uint::ZERO) + .wrapping_sub(&Uint::::ONE); let incr_limit = max_value.wrapping_sub(&self.base); - self.incr_limit = if incr_limit > INCR_LIMIT.into() { + self.incr_limit = if incr_limit > Uint::::from(INCR_LIMIT) { INCR_LIMIT } else { // We are close to `2^max_bit_length - 1`. @@ -267,7 +273,7 @@ mod tests { use alloc::format; use alloc::vec::Vec; - use crypto_bigint::U64; + use crypto_bigint::{Odd, U64}; use num_prime::nt_funcs::factorize64; use rand_chacha::ChaCha8Rng; use rand_core::{OsRng, SeedableRng}; @@ -280,7 +286,7 @@ mod tests { let max_prime = SMALL_PRIMES[SMALL_PRIMES.len() - 1]; let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); - let start: U64 = random_odd_uint(&mut rng, 32); + let start: Odd = random_odd_uint(&mut rng, 32); for num in Sieve::new(&start, 32, false).take(100) { let num_u64: u64 = num.into(); assert!(num_u64.leading_zeros() == 32); @@ -292,7 +298,7 @@ mod tests { } } - fn check_sieve(start: u32, bit_length: usize, safe_prime: bool, reference: &[u32]) { + fn check_sieve(start: u32, bit_length: u32, safe_prime: bool, reference: &[u32]) { let test = Sieve::new(&U64::from(start), bit_length, safe_prime).collect::>(); assert_eq!(test.len(), reference.len()); for (x, y) in test.iter().zip(reference.iter()) { @@ -360,7 +366,7 @@ mod tests { #[test] fn random_below_max_length() { for _ in 0..10 { - let r: U64 = random_odd_uint(&mut OsRng, 50); + let r: Odd = random_odd_uint(&mut OsRng, 50); assert_eq!(r.bits(), 50); } } @@ -368,13 +374,13 @@ mod tests { #[test] #[should_panic(expected = "Bit length must be non-zero")] fn random_odd_uint_0bits() { - let _p: U64 = random_odd_uint(&mut OsRng, 0); + let _p: Odd = random_odd_uint(&mut OsRng, 0); } #[test] #[should_panic(expected = "The requested bit length (65) is larger than the chosen Uint size")] fn random_odd_uint_too_many_bits() { - let _p: U64 = random_odd_uint(&mut OsRng, 65); + let _p: Odd = random_odd_uint(&mut OsRng, 65); } #[test] diff --git a/src/presets.rs b/src/presets.rs index 50eada1..f624695 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -1,4 +1,4 @@ -use crypto_bigint::{Integer, Uint}; +use crypto_bigint::{Integer, Odd, Uint}; use rand_core::CryptoRngCore; #[cfg(feature = "default-rng")] @@ -13,7 +13,7 @@ use crate::hazmat::{ /// /// See [`is_prime_with_rng`] for details about the performed checks. #[cfg(feature = "default-rng")] -pub fn generate_prime(bit_length: Option) -> Uint { +pub fn generate_prime(bit_length: Option) -> Uint { generate_prime_with_rng(&mut OsRng, bit_length) } @@ -23,7 +23,7 @@ pub fn generate_prime(bit_length: Option) -> Uint { /// /// See [`is_prime_with_rng`] for details about the performed checks. #[cfg(feature = "default-rng")] -pub fn generate_safe_prime(bit_length: Option) -> Uint { +pub fn generate_safe_prime(bit_length: Option) -> Uint { generate_safe_prime_with_rng(&mut OsRng, bit_length) } @@ -53,14 +53,14 @@ pub fn is_safe_prime(num: &Uint) -> bool { /// See [`is_prime_with_rng`] for details about the performed checks. pub fn generate_prime_with_rng( rng: &mut impl CryptoRngCore, - bit_length: Option, + bit_length: Option, ) -> Uint { let bit_length = bit_length.unwrap_or(Uint::::BITS); if bit_length < 2 { panic!("`bit_length` must be 2 or greater."); } loop { - let start: Uint = random_odd_uint(rng, bit_length); + let start = random_odd_uint::(rng, bit_length); let sieve = Sieve::new(&start, bit_length, false); for num in sieve { if is_prime_with_rng(rng, &num) { @@ -79,14 +79,14 @@ pub fn generate_prime_with_rng( /// See [`is_prime_with_rng`] for details about the performed checks. pub fn generate_safe_prime_with_rng( rng: &mut impl CryptoRngCore, - bit_length: Option, + bit_length: Option, ) -> Uint { let bit_length = bit_length.unwrap_or(Uint::::BITS); if bit_length < 3 { panic!("`bit_length` must be 3 or greater."); } loop { - let start: Uint = random_odd_uint(rng, bit_length); + let start = random_odd_uint::(rng, bit_length); let sieve = Sieve::new(&start, bit_length, true); for num in sieve { if is_safe_prime_with_rng(rng, &num) { @@ -152,7 +152,7 @@ pub fn is_safe_prime_with_rng(rng: &mut impl CryptoRngCore, num: /// Checks for primality assuming that `num` is odd. fn _is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &Uint) -> bool { debug_assert!(bool::from(num.is_odd())); - let mr = MillerRabin::new(num); + let mr = MillerRabin::new(Odd::new(*num).unwrap()); if !mr.test_base_two().is_probably_prime() { return false; @@ -232,7 +232,11 @@ mod tests { assert!(is_safe_prime(&next)); } - next = (next << 1).checked_add(&Uint::::ONE).unwrap(); + next = next + .overflowing_shl_vartime(1) + .unwrap() + .checked_add(&Uint::::ONE) + .unwrap(); } // The chain ended. @@ -321,7 +325,7 @@ mod tests { #[test] fn corner_cases_generate_prime() { - for bits in 2usize..5 { + for bits in 2..5 { for _ in 0..100 { let p: U64 = generate_prime(Some(bits)); let p_word = p.as_words()[0]; @@ -332,7 +336,7 @@ mod tests { #[test] fn corner_cases_generate_safe_prime() { - for bits in 3usize..5 { + for bits in 3..5 { for _ in 0..100 { let p: U64 = generate_safe_prime(Some(bits)); let p_word = p.as_words()[0]; @@ -347,7 +351,7 @@ mod tests { mod tests_openssl { use alloc::format; - use crypto_bigint::U128; + use crypto_bigint::{Odd, U128}; use openssl::bn::{BigNum, BigNumContext}; use rand_core::OsRng; @@ -390,7 +394,7 @@ mod tests_openssl { // Generate random numbers, check if our test agrees with OpenSSL for _ in 0..100 { - let p: U128 = random_odd_uint(&mut OsRng, 128); + let p: Odd = random_odd_uint(&mut OsRng, 128); let actual = is_prime(&p); let p_bn = to_openssl(&p); let expected = openssl_is_prime(&p_bn, &mut ctx); @@ -405,7 +409,7 @@ mod tests_openssl { #[cfg(test)] #[cfg(feature = "tests-gmp")] mod tests_gmp { - use crypto_bigint::U128; + use crypto_bigint::{Odd, U128}; use rand_core::OsRng; use rug::{ integer::{IsPrime, Order}, @@ -438,7 +442,7 @@ mod tests_gmp { // Generate primes with GMP, check them for _ in 0..100 { - let start: U128 = random_odd_uint(&mut OsRng, 128); + let start: Odd = random_odd_uint(&mut OsRng, 128); let start_bn = to_gmp(&start); let p_bn = start_bn.next_prime(); let p = from_gmp(&p_bn); @@ -447,7 +451,7 @@ mod tests_gmp { // Generate random numbers, check if our test agrees with GMP for _ in 0..100 { - let p: U128 = random_odd_uint(&mut OsRng, 128); + let p: Odd = random_odd_uint(&mut OsRng, 128); let actual = is_prime(&p); let p_bn = to_gmp(&p); let expected = gmp_is_prime(&p_bn); diff --git a/src/traits.rs b/src/traits.rs index c36c11d..bb72242 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -15,7 +15,7 @@ pub trait RandomPrimeWithRng { /// Panics if `bit_length` is less than 2, or greater than the bit size of the target `Uint`. /// /// See [`is_prime_with_rng`] for details about the performed checks. - fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: Option) -> Self; + fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: Option) -> Self; /// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime) /// of size `bit_length` using the provided RNG. @@ -24,10 +24,7 @@ pub trait RandomPrimeWithRng { /// Panics if `bit_length` is less than 3, or greater than the bit size of the target `Uint`. /// /// See [`is_prime_with_rng`] for details about the performed checks. - fn generate_safe_prime_with_rng( - rng: &mut impl CryptoRngCore, - bit_length: Option, - ) -> Self; + fn generate_safe_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: Option) -> Self; /// Checks probabilistically if the given number is prime using the provided RNG. /// @@ -41,13 +38,10 @@ pub trait RandomPrimeWithRng { } impl RandomPrimeWithRng for Uint { - fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: Option) -> Self { + fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: Option) -> Self { generate_prime_with_rng(rng, bit_length) } - fn generate_safe_prime_with_rng( - rng: &mut impl CryptoRngCore, - bit_length: Option, - ) -> Self { + fn generate_safe_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: Option) -> Self { generate_safe_prime_with_rng(rng, bit_length) } fn is_prime_with_rng(&self, rng: &mut impl CryptoRngCore) -> bool { From 957aa7acffd4005dddd93913f2217e151211ae27 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 14:10:55 -0800 Subject: [PATCH 02/16] Use `Odd` in `LucasBase::generate()` --- CHANGELOG.md | 2 +- src/hazmat/lucas.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c2de85..6203336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bumped `crypto-bigint` to 0.6.0-pre.5. ([#38]) - Bumped MSRV to 1.73. (#[38]) -- `MillerRabin::new()` takes an `Odd`-wrapped integer by value. `random_odd_uint()` returns an `Odd`-wrapped integer. (#[38]) +- `MillerRabin::new()` takes an `Odd`-wrapped integer by value. `random_odd_uint()` returns an `Odd`-wrapped integer. `LucasBase::generate()` takes an `Odd`-wrapped integer. (#[38]) - All bit length-type parameters take `u32` instead of `usize`. (#[38]) diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index de9081b..1725289 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -30,7 +30,7 @@ pub trait LucasBase { /// Given an odd integer, returns `Ok((P, Q))` on success, /// or `Err(Primality)` if the primality for the given integer was discovered /// during the search for a base. - fn generate(&self, n: &Uint) -> Result<(u32, i32), Primality>; + fn generate(&self, n: &Odd>) -> Result<(u32, i32), Primality>; } /// "Method A" for selecting the base given in Baillie & Wagstaff[^Baillie1980], @@ -48,7 +48,7 @@ pub trait LucasBase { pub struct SelfridgeBase; impl LucasBase for SelfridgeBase { - fn generate(&self, n: &Uint) -> Result<(u32, i32), Primality> { + fn generate(&self, n: &Odd>) -> Result<(u32, i32), Primality> { let mut d = 5_i32; let n_is_small = n.bits_vartime() < (Limb::BITS - 1); // Can unwrap here since it won't overflow after `&` @@ -108,7 +108,7 @@ impl LucasBase for SelfridgeBase { pub struct AStarBase; impl LucasBase for AStarBase { - fn generate(&self, n: &Uint) -> Result<(u32, i32), Primality> { + fn generate(&self, n: &Odd>) -> Result<(u32, i32), Primality> { SelfridgeBase .generate(n) .map(|(p, q)| if q == -1 { (5, 5) } else { (p, q) }) @@ -127,7 +127,7 @@ impl LucasBase for AStarBase { pub struct BruteForceBase; impl LucasBase for BruteForceBase { - fn generate(&self, n: &Uint) -> Result<(u32, i32), Primality> { + fn generate(&self, n: &Odd>) -> Result<(u32, i32), Primality> { let mut p = 3_u32; let mut attempts = 0; @@ -155,7 +155,7 @@ impl LucasBase for BruteForceBase { // Since the loop proceeds in increasing P and starts with P - 2 == 1, // the shared prime factor must be P + 2. // If P + 2 == n, then n is prime; otherwise P + 2 is a proper factor of n. - let primality = if n == &Uint::::from(p + 2) { + let primality = if n.as_ref() == &Uint::::from(p + 2) { Primality::Prime } else { Primality::Composite @@ -294,7 +294,7 @@ pub fn lucas_test( }; // Find the base for the Lucas sequence. - let (p, q) = match base.generate(candidate) { + let (p, q) = match base.generate(&odd_candidate) { Ok((p, q)) => (p, q), Err(primality) => return primality, }; @@ -509,7 +509,7 @@ mod tests { fn base_for_square() { // We can't find a base with Jacobi symbol = -1 for a square, // check that it is handled properly. - let num = U64::from(131u32).square(); + let num = Odd::new(U64::from(131u32).square()).unwrap(); assert_eq!(SelfridgeBase.generate(&num), Err(Primality::Composite)); assert_eq!(AStarBase.generate(&num), Err(Primality::Composite)); assert_eq!(BruteForceBase.generate(&num), Err(Primality::Composite)); @@ -519,7 +519,7 @@ mod tests { fn base_early_quit() { // 5 is flagged as prime at the base generation stage assert_eq!( - BruteForceBase.generate(&U64::from(5u32)), + BruteForceBase.generate(&Odd::new(U64::from(5u32)).unwrap()), Err(Primality::Prime) ) } @@ -544,7 +544,7 @@ mod tests { struct TestBase; impl LucasBase for TestBase { - fn generate(&self, _n: &Uint) -> Result<(u32, i32), Primality> { + fn generate(&self, _n: &Odd>) -> Result<(u32, i32), Primality> { Ok((5, 5)) } } From 4b28e0f04c10d921de4e4d46f741bf663d958afa Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 11:31:52 -0800 Subject: [PATCH 03/16] Use `Odd` in `_is_prime_with_rng()` --- src/presets.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/presets.rs b/src/presets.rs index f624695..db6c379 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -129,7 +129,8 @@ pub fn is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &Uin return false; } - _is_prime_with_rng(rng, num) + let odd_num = Odd::new(*num).expect("ensured to be odd"); + _is_prime_with_rng(rng, &odd_num) } /// Checks probabilistically if the given number is a safe prime using the provided RNG. @@ -146,19 +147,22 @@ pub fn is_safe_prime_with_rng(rng: &mut impl CryptoRngCore, num: return false; } - _is_prime_with_rng(rng, num) && _is_prime_with_rng(rng, &(num >> 1)) + // These are ensured to be odd by the check above. + let odd_num = Odd::new(*num).expect("ensured to be odd"); + let odd_half_num = Odd::new(num.wrapping_shr_vartime(1)).expect("ensured to be odd"); + + _is_prime_with_rng(rng, &odd_num) && _is_prime_with_rng(rng, &odd_half_num) } /// Checks for primality assuming that `num` is odd. -fn _is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &Uint) -> bool { - debug_assert!(bool::from(num.is_odd())); - let mr = MillerRabin::new(Odd::new(*num).unwrap()); +fn _is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &Odd>) -> bool { + let mr = MillerRabin::new(*num); if !mr.test_base_two().is_probably_prime() { return false; } - match lucas_test(num, AStarBase, LucasCheck::Strong) { + match lucas_test(num.as_ref(), AStarBase, LucasCheck::Strong) { Primality::Composite => return false, Primality::Prime => return true, _ => {} From a180a4f1901bb6dfec8c526000ccb987b92f4e48 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 11:46:19 -0800 Subject: [PATCH 04/16] Replace unwraps with expects in the main code --- src/hazmat/gcd.rs | 9 ++++++--- src/hazmat/jacobi.rs | 3 ++- src/hazmat/lucas.rs | 10 ++++++---- src/hazmat/miller_rabin.rs | 3 ++- src/hazmat/sieve.rs | 4 +++- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/hazmat/gcd.rs b/src/hazmat/gcd.rs index cb74393..2eca028 100644 --- a/src/hazmat/gcd.rs +++ b/src/hazmat/gcd.rs @@ -18,14 +18,17 @@ pub(crate) fn gcd(n: &Uint, m: u32) -> u32 { // Normalize input: the resulting (a, b) are both small, a >= b, and b != 0. let (mut a, mut b): (u32, u32) = if n.bits() > u32::BITS { // `m` is non-zero, so we can unwrap. - let (_quo, n) = n.div_rem_limb(NonZero::new(Limb::from(m)).unwrap()); + let (_quo, n) = + n.div_rem_limb(NonZero::new(Limb::from(m)).expect("divisor ensured to be non-zero")); // `n` is a remainder of a division by `u32`, so it can be safely cast to `u32`. - let b: u32 = n.0.try_into().unwrap(); + let b: u32 = n.0.try_into().expect("ensured to fit into `u32`"); (m, b) } else { // In this branch `n` is 32 bits or shorter, // so we can safely take the first limb and cast it to u32. - let n: u32 = n.as_words()[0].try_into().unwrap(); + let n: u32 = n.as_words()[0] + .try_into() + .expect("ensured to fit into `u32`"); if n > m { (n, m) } else { diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index b8bc3c3..f6126e4 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -113,7 +113,8 @@ pub(crate) fn jacobi_symbol(a: i32, p_long: &Uint) -> JacobiS let (result, a_long, p) = swap(result, a, *p_long); // Can unwrap here, since `p` is swapped with `a`, // and `a` would be odd after `reduce_numerator()`. - let (_, a) = a_long.div_rem_limb(NonZero::new(Limb::from(p)).unwrap()); + let (_, a) = + a_long.div_rem_limb(NonZero::new(Limb::from(p)).expect("ensured to be non-zero")); (result, a.0, p) }; diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 1725289..31ae543 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -1,7 +1,7 @@ //! Lucas primality test. use crypto_bigint::{ modular::{MontyForm, MontyParams}, - CheckedAdd, Integer, Limb, Odd, Uint, Word, + CheckedAdd, Integer, Odd, Uint, Word, }; use super::{ @@ -50,9 +50,11 @@ pub struct SelfridgeBase; impl LucasBase for SelfridgeBase { fn generate(&self, n: &Odd>) -> Result<(u32, i32), Primality> { let mut d = 5_i32; - let n_is_small = n.bits_vartime() < (Limb::BITS - 1); + let n_is_small = n.bits_vartime() < (u32::BITS - 1); // Can unwrap here since it won't overflow after `&` - let small_n: u32 = (n.as_words()[0] & Word::from(u32::MAX)).try_into().unwrap(); + let small_n: u32 = (n.as_words()[0] & Word::from(u32::MAX)) + .try_into() + .expect("ensured to fit into `u32`"); let mut attempts = 0; loop { if attempts >= MAX_ATTEMPTS { @@ -144,7 +146,7 @@ impl LucasBase for BruteForceBase { } // Can unwrap here since `p` is always small (see the condition above). - let j = jacobi_symbol((p * p - 4).try_into().unwrap(), n); + let j = jacobi_symbol((p * p - 4).try_into().expect("fits into `i32`"), n); if j == JacobiSymbol::MinusOne { break; diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index c773053..be5c39e 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -104,7 +104,8 @@ impl MillerRabin { } let range = self.candidate.wrapping_sub(&Uint::::from(4u32)); - let range_nonzero = NonZero::new(range).unwrap(); + // Can unwrap here since `candidate` is odd, and `candidate >= 4` (as checked above) + let range_nonzero = NonZero::new(range).expect("ensured to be non-zero"); // This should not overflow as long as `random_mod()` behaves according to the contract // (that is, returns a number within the given range). let random = Option::from( diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 7a0fa80..ee0a995 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -183,7 +183,9 @@ impl Sieve { self.last_round = true; // Can unwrap here since we just checked above that `incr_limit <= INCR_LIMIT`, // and `INCR_LIMIT` fits into `Residue`. - let incr_limit_small: Residue = incr_limit.as_words()[0].try_into().unwrap(); + let incr_limit_small: Residue = incr_limit.as_words()[0] + .try_into() + .expect("ensured to fit within `Residue`"); incr_limit_small }; From 63cdd66fcf383b2d6686bc9d0f45afa69f43ba32 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 12:24:05 -0800 Subject: [PATCH 05/16] Use `Word` instead of `i32`/`u32` in GCD and Jacobi, to avoid conversions --- src/hazmat/gcd.rs | 24 +++++------ src/hazmat/jacobi.rs | 72 +++++++++++++++++--------------- src/hazmat/lucas.rs | 97 +++++++++++++++++++++++++++----------------- 3 files changed, 109 insertions(+), 84 deletions(-) diff --git a/src/hazmat/gcd.rs b/src/hazmat/gcd.rs index 2eca028..d131b10 100644 --- a/src/hazmat/gcd.rs +++ b/src/hazmat/gcd.rs @@ -1,9 +1,9 @@ -use crypto_bigint::{Limb, NonZero, Uint}; +use crypto_bigint::{Limb, NonZero, Uint, Word}; /// Calculates the greatest common divisor of `n` and `m`. /// By definition, `gcd(0, m) == m`. /// `n` must be non-zero. -pub(crate) fn gcd(n: &Uint, m: u32) -> u32 { +pub(crate) fn gcd(n: &Uint, m: Word) -> Word { // This is an internal function, and it will never be called with `m = 0`. // Allowing `m = 0` would require us to have the return type of `Uint` // (since `gcd(n, 0) = n`). @@ -16,19 +16,15 @@ pub(crate) fn gcd(n: &Uint, m: u32) -> u32 { } // Normalize input: the resulting (a, b) are both small, a >= b, and b != 0. - let (mut a, mut b): (u32, u32) = if n.bits() > u32::BITS { + let (mut a, mut b): (Word, Word) = if n.bits() > Word::BITS { // `m` is non-zero, so we can unwrap. let (_quo, n) = n.div_rem_limb(NonZero::new(Limb::from(m)).expect("divisor ensured to be non-zero")); - // `n` is a remainder of a division by `u32`, so it can be safely cast to `u32`. - let b: u32 = n.0.try_into().expect("ensured to fit into `u32`"); - (m, b) + (m, n.0) } else { - // In this branch `n` is 32 bits or shorter, - // so we can safely take the first limb and cast it to u32. - let n: u32 = n.as_words()[0] - .try_into() - .expect("ensured to fit into `u32`"); + // In this branch `n` is `Word::BITS` bits or shorter, + // so we can safely take the first limb. + let n = n.as_words()[0]; if n > m { (n, m) } else { @@ -50,7 +46,7 @@ pub(crate) fn gcd(n: &Uint, m: u32) -> u32 { #[cfg(test)] mod tests { - use crypto_bigint::U128; + use crypto_bigint::{Word, U128}; use num_bigint::BigUint; use num_integer::Integer; use proptest::prelude::*; @@ -73,14 +69,14 @@ mod tests { proptest! { #[test] - fn fuzzy(m in any::(), n in uint()) { + fn fuzzy(m in any::(), n in uint()) { if m == 0 { return Ok(()); } let m_bi = BigUint::from(m); let n_bi = BigUint::from_bytes_be(n.to_be_bytes().as_ref()); - let gcd_ref: u32 = n_bi.gcd(&m_bi).try_into().unwrap(); + let gcd_ref: Word = n_bi.gcd(&m_bi).try_into().unwrap(); let gcd_test = gcd(&n, m); assert_eq!(gcd_test, gcd_ref); diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index f6126e4..959beb3 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -73,7 +73,11 @@ fn swap(j: JacobiSymbol, a: T, p: V) -> (JacobiSymbol, } /// Returns the Jacobi symbol `(a/p)` given an odd `p`. Panics on even `p`. -pub(crate) fn jacobi_symbol(a: i32, p_long: &Uint) -> JacobiSymbol { +pub(crate) fn jacobi_symbol( + abs_a: Word, + a_is_negative: bool, + p_long: &Uint, +) -> JacobiSymbol { if p_long.is_even().into() { panic!("`p_long` must be an odd integer, but got {}", p_long); } @@ -84,21 +88,18 @@ pub(crate) fn jacobi_symbol(a: i32, p_long: &Uint) -> JacobiS // (-a/n) = (-1/n) * (a/n) // = (-1)^((n-1)/2) * (a/n) // = (-1 if n = 3 mod 4 else 1) * (a/n) - let (result, a_pos) = { - let result = if a < 0 && p_long.mod4() == 3 { - -result - } else { - result - }; - (result, a.abs_diff(0)) + let result = if a_is_negative && p_long.mod4() == 3 { + -result + } else { + result }; // A degenerate case. - if a_pos == 1 || p_long == &Uint::::ONE { + if abs_a == 1 || p_long == &Uint::::ONE { return result; } - let a_limb = Limb::from(a_pos); + let a_limb = Limb::from(abs_a); // Normalize input: at the end we want `a < p`, `p` odd, and both fitting into a `Word`. let (result, a, p): (JacobiSymbol, Word, Word) = if p_long.bits_vartime() <= Limb::BITS { @@ -152,7 +153,7 @@ mod tests { use alloc::format; - use crypto_bigint::U128; + use crypto_bigint::{Word, U128}; use num_bigint::{BigInt, Sign}; use num_modular::ModularSymbols; use proptest::prelude::*; @@ -179,12 +180,15 @@ mod tests { expected = "`p_long` must be an odd integer, but got 00000000000000000000000000000004" )] fn jacobi_symbol_p_is_even() { - let _j = jacobi_symbol(1, &U128::from(4u32)); + let _j = jacobi_symbol(1, false, &U128::from(4u32)); } // Reference from `num-modular` - supports long `p`, but only positive `a`. - fn jacobi_symbol_ref(a: i32, p: &U128) -> JacobiSymbol { - let a_bi = BigInt::from(a); + fn jacobi_symbol_ref(a: Word, a_is_negative: bool, p: &U128) -> JacobiSymbol { + let mut a_bi = BigInt::from(a); + if a_is_negative { + a_bi = -a_bi; + } let p_bi = BigInt::from_bytes_be(Sign::Plus, p.to_be_bytes().as_ref()); let j = a_bi.jacobi(&p_bi); if j == 1 { @@ -199,12 +203,14 @@ mod tests { #[test] fn small_values() { // Test small values, using a reference implementation. - for a in -31i32..31 { - for p in (1u32..31).step_by(2) { - let p_long = U128::from(p); - let j_ref = jacobi_symbol_ref(a, &p_long); - let j = jacobi_symbol(a, &p_long); - assert_eq!(j, j_ref); + for a in 0..31 { + for a_is_negative in [true, false] { + for p in (1u32..31).step_by(2) { + let p_long = U128::from(p); + let j_ref = jacobi_symbol_ref(a, a_is_negative, &p_long); + let j = jacobi_symbol(a, a_is_negative, &p_long); + assert_eq!(j, j_ref); + } } } } @@ -212,21 +218,23 @@ mod tests { #[test] fn big_values() { // a = x, p = x * y, where x and y are big primes. Should give 0. - let a = 2147483647i32; // 2^31 - 1, a prime + let a = 2147483647; // 2^31 - 1, a prime let p = U128::from_be_hex("000000007ffffffeffffffe28000003b"); // (2^31 - 1) * (2^64 - 59) - assert_eq!(jacobi_symbol(a, &p), JacobiSymbol::Zero); - assert_eq!(jacobi_symbol_ref(a, &p), JacobiSymbol::Zero); + assert_eq!(jacobi_symbol(a, false, &p), JacobiSymbol::Zero); + assert_eq!(jacobi_symbol_ref(a, false, &p), JacobiSymbol::Zero); // a = x^2 mod p, should give 1. - let a = 659456i32; // Obtained from x = 2^70 + let a = 659456; // Obtained from x = 2^70 let p = U128::from_be_hex("ffffffffffffffffffffffffffffff5f"); // 2^128 - 161 - not a prime - assert_eq!(jacobi_symbol(a, &p), JacobiSymbol::One); - assert_eq!(jacobi_symbol_ref(a, &p), JacobiSymbol::One); + assert_eq!(jacobi_symbol(a, false, &p), JacobiSymbol::One); + assert_eq!(jacobi_symbol_ref(a, false, &p), JacobiSymbol::One); - let a = i32::MIN; // -2^31, check that no overflow occurs + // -2^31 + let a = 2147483648; + let a_is_negative = true; let p = U128::from_be_hex("000000007ffffffeffffffe28000003b"); // (2^31 - 1) * (2^64 - 59) - assert_eq!(jacobi_symbol(a, &p), JacobiSymbol::One); - assert_eq!(jacobi_symbol_ref(a, &p), JacobiSymbol::One); + assert_eq!(jacobi_symbol(a, a_is_negative, &p), JacobiSymbol::One); + assert_eq!(jacobi_symbol_ref(a, a_is_negative, &p), JacobiSymbol::One); } prop_compose! { @@ -237,9 +245,9 @@ mod tests { proptest! { #[test] - fn fuzzy(a in any::(), p in odd_uint()) { - let j_ref = jacobi_symbol_ref(a, &p); - let j = jacobi_symbol(a, &p); + fn fuzzy(abs_a in any::(), a_is_negative in any::(), p in odd_uint()) { + let j_ref = jacobi_symbol_ref(abs_a, a_is_negative, &p); + let j = jacobi_symbol(abs_a, a_is_negative, &p); assert_eq!(j, j_ref); } } diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 31ae543..97b1d71 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -13,7 +13,7 @@ use super::{ /// The maximum number of attempts to find `D` such that `(D/n) == -1`. // This is widely believed to be impossible. // So if we exceed it, we will panic reporting the value of `n`. -const MAX_ATTEMPTS: u32 = 10000; +const MAX_ATTEMPTS: usize = 10000; /// The number of attempts to find `D` such that `(D/n) == -1` /// before checking that `n` is a square (in which case such `D` does not exist). @@ -23,14 +23,14 @@ const MAX_ATTEMPTS: u32 = 10000; // in just a few attempts on average (an estimate for the Selfridge method // can be found in [^Baillie1980], section 7; for the brute force method // it seems to be about the same). -const ATTEMPTS_BEFORE_SQRT: u32 = 30; +const ATTEMPTS_BEFORE_SQRT: usize = 30; /// A method for selecting the base `(P, Q)` for the Lucas primality test. pub trait LucasBase { - /// Given an odd integer, returns `Ok((P, Q))` on success, + /// Given an odd integer, returns `Ok((P, abs(Q), is_negative(Q)))` on success, /// or `Err(Primality)` if the primality for the given integer was discovered /// during the search for a base. - fn generate(&self, n: &Odd>) -> Result<(u32, i32), Primality>; + fn generate(&self, n: &Odd>) -> Result<(Word, Word, bool), Primality>; } /// "Method A" for selecting the base given in Baillie & Wagstaff[^Baillie1980], @@ -48,13 +48,11 @@ pub trait LucasBase { pub struct SelfridgeBase; impl LucasBase for SelfridgeBase { - fn generate(&self, n: &Odd>) -> Result<(u32, i32), Primality> { - let mut d = 5_i32; - let n_is_small = n.bits_vartime() < (u32::BITS - 1); - // Can unwrap here since it won't overflow after `&` - let small_n: u32 = (n.as_words()[0] & Word::from(u32::MAX)) - .try_into() - .expect("ensured to fit into `u32`"); + fn generate(&self, n: &Odd>) -> Result<(Word, Word, bool), Primality> { + let mut abs_d = 5; + let mut d_is_negative = false; + let n_is_small = n.bits_vartime() < (Word::BITS - 1); + let small_n = n.as_words()[0]; let mut attempts = 0; loop { if attempts >= MAX_ATTEMPTS { @@ -68,7 +66,7 @@ impl LucasBase for SelfridgeBase { } } - let j = jacobi_symbol(d, n); + let j = jacobi_symbol(abs_d, d_is_negative, n); if j == JacobiSymbol::MinusOne { break; @@ -80,20 +78,25 @@ impl LucasBase for SelfridgeBase { // this small modification of Selfridge's method A // enables 5 and 11 to be classified as Lucas probable primes. // Otherwise GCD(D, n) > 1, and therefore n is not prime. - if !(n_is_small && small_n == d.abs_diff(0)) { + if !(n_is_small && small_n == abs_d) { return Err(Primality::Composite); } } attempts += 1; - d = -d; - d += d.signum() * 2; + d_is_negative = !d_is_negative; + abs_d += 2; } - // No remainder by construction of `d`. - let q = (1 - d) / 4; + // Calculate `q = (1 - d) / 4`. + // No remainder from division by 4, by construction of `d`. + let (abs_q, q_is_negative) = if d_is_negative { + ((abs_d + 1) / 4, false) + } else { + ((abs_d - 1) / 4, true) + }; - Ok((1, q)) + Ok((1, abs_q, q_is_negative)) } } @@ -110,10 +113,14 @@ impl LucasBase for SelfridgeBase { pub struct AStarBase; impl LucasBase for AStarBase { - fn generate(&self, n: &Odd>) -> Result<(u32, i32), Primality> { - SelfridgeBase - .generate(n) - .map(|(p, q)| if q == -1 { (5, 5) } else { (p, q) }) + fn generate(&self, n: &Odd>) -> Result<(Word, Word, bool), Primality> { + SelfridgeBase.generate(n).map(|(p, abs_q, q_is_negative)| { + if abs_q == 1 && q_is_negative { + (5, 5, false) + } else { + (p, abs_q, q_is_negative) + } + }) } } @@ -129,8 +136,8 @@ impl LucasBase for AStarBase { pub struct BruteForceBase; impl LucasBase for BruteForceBase { - fn generate(&self, n: &Odd>) -> Result<(u32, i32), Primality> { - let mut p = 3_u32; + fn generate(&self, n: &Odd>) -> Result<(Word, Word, bool), Primality> { + let mut p = 3; let mut attempts = 0; loop { @@ -146,7 +153,7 @@ impl LucasBase for BruteForceBase { } // Can unwrap here since `p` is always small (see the condition above). - let j = jacobi_symbol((p * p - 4).try_into().expect("fits into `i32`"), n); + let j = jacobi_symbol(p * p - 4, false, n); if j == JacobiSymbol::MinusOne { break; @@ -169,7 +176,7 @@ impl LucasBase for BruteForceBase { p += 1; } - Ok((p, 1)) + Ok((p, 1, false)) } } @@ -296,15 +303,27 @@ pub fn lucas_test( }; // Find the base for the Lucas sequence. - let (p, q) = match base.generate(&odd_candidate) { - Ok((p, q)) => (p, q), + let (p, abs_q, q_is_negative) = match base.generate(&odd_candidate) { + Ok(pq) => pq, Err(primality) => return primality, }; - let discriminant = (p * p) as i32 - 4 * q; + + // Discriminant `d = p^2 - 4q` + let (abs_d, d_is_negative) = if q_is_negative { + (p * p + 4 * abs_q, false) + } else { + let t1 = p * p; + let t2 = 4 * abs_q; + if t2 > t1 { + (t2 - t1, true) + } else { + (t1 - t2, false) + } + }; // If either is true, it allows us to optimize certain parts of the calculations. let p_is_one = p == 1; - let q_is_one = q == 1; + let q_is_one = abs_q == 1 && !q_is_negative; // See the references for the specific checks in the docstrings for [`LucasCheck`]. @@ -316,7 +335,6 @@ pub fn lucas_test( // But in order to avoid an implicit assumption that a sieve has been run, // we check that gcd(n, Q) = 1 anyway - again, since `Q` is small, // it does not noticeably affect the performance. - let abs_q = q.abs_diff(0); if abs_q != 1 && gcd(candidate, abs_q) != 1 && candidate > &Uint::::from(abs_q) { return Primality::Composite; } @@ -339,8 +357,8 @@ pub fn lucas_test( let q = if q_is_one { one } else { - let abs_q = MontyForm::::new(&Uint::::from(q.abs_diff(0)), params); - if q < 0 { + let abs_q = MontyForm::::new(&Uint::::from(abs_q), params); + if q_is_negative { -abs_q } else { abs_q @@ -375,8 +393,8 @@ pub fn lucas_test( let mut qk = one; // keeps Q^k // D in Montgomery representation - note that it can be negative. - let abs_d = MontyForm::::new(&Uint::::from(discriminant.abs_diff(0)), params); - let d_m = if discriminant < 0 { -abs_d } else { abs_d }; + let abs_d = MontyForm::::new(&Uint::::from(abs_d), params); + let d_m = if d_is_negative { -abs_d } else { abs_d }; for i in (0..d.bits_vartime()).rev() { // k' = k * 2 @@ -484,7 +502,7 @@ mod tests { use alloc::format; - use crypto_bigint::{Odd, Uint, U128, U64}; + use crypto_bigint::{Odd, Uint, Word, U128, U64}; #[cfg(feature = "tests-exhaustive")] use num_prime::nt_funcs::is_prime64; @@ -546,8 +564,11 @@ mod tests { struct TestBase; impl LucasBase for TestBase { - fn generate(&self, _n: &Odd>) -> Result<(u32, i32), Primality> { - Ok((5, 5)) + fn generate( + &self, + _n: &Odd>, + ) -> Result<(Word, Word, bool), Primality> { + Ok((5, 5, false)) } } From dda3802bf9a4616bf3d610fe67b9671725602e0c Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 12:28:10 -0800 Subject: [PATCH 06/16] Use `Odd` in `jacobi_symbol` --- src/hazmat/jacobi.rs | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index 959beb3..e57b49f 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -1,6 +1,6 @@ //! Jacobi symbol calculation. -use crypto_bigint::{Integer, Limb, NonZero, Uint, Word}; +use crypto_bigint::{Limb, NonZero, Odd, Uint, Word}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum JacobiSymbol { @@ -72,16 +72,12 @@ fn swap(j: JacobiSymbol, a: T, p: V) -> (JacobiSymbol, (j, p, a) } -/// Returns the Jacobi symbol `(a/p)` given an odd `p`. Panics on even `p`. +/// Returns the Jacobi symbol `(a/p)` given an odd `p`. pub(crate) fn jacobi_symbol( abs_a: Word, a_is_negative: bool, - p_long: &Uint, + p_long: &Odd>, ) -> JacobiSymbol { - if p_long.is_even().into() { - panic!("`p_long` must be an odd integer, but got {}", p_long); - } - let result = JacobiSymbol::One; // Keep track of all the sign flips here. // Deal with a negative `a` first: @@ -95,7 +91,7 @@ pub(crate) fn jacobi_symbol( }; // A degenerate case. - if abs_a == 1 || p_long == &Uint::::ONE { + if abs_a == 1 || p_long.as_ref() == &Uint::::ONE { return result; } @@ -107,11 +103,11 @@ pub(crate) fn jacobi_symbol( let p = p_long.as_limbs()[0].0; (result, a % p, p) } else { - let (result, a) = reduce_numerator(result, a_limb.0, p_long); + let (result, a) = reduce_numerator(result, a_limb.0, p_long.as_ref()); if a == 1 { return result; } - let (result, a_long, p) = swap(result, a, *p_long); + let (result, a_long, p) = swap(result, a, p_long.get()); // Can unwrap here, since `p` is swapped with `a`, // and `a` would be odd after `reduce_numerator()`. let (_, a) = @@ -153,7 +149,7 @@ mod tests { use alloc::format; - use crypto_bigint::{Word, U128}; + use crypto_bigint::{Odd, Word, U128}; use num_bigint::{BigInt, Sign}; use num_modular::ModularSymbols; use proptest::prelude::*; @@ -175,14 +171,6 @@ mod tests { assert_eq!(-JacobiSymbol::Zero, JacobiSymbol::Zero); } - #[test] - #[should_panic( - expected = "`p_long` must be an odd integer, but got 00000000000000000000000000000004" - )] - fn jacobi_symbol_p_is_even() { - let _j = jacobi_symbol(1, false, &U128::from(4u32)); - } - // Reference from `num-modular` - supports long `p`, but only positive `a`. fn jacobi_symbol_ref(a: Word, a_is_negative: bool, p: &U128) -> JacobiSymbol { let mut a_bi = BigInt::from(a); @@ -206,7 +194,7 @@ mod tests { for a in 0..31 { for a_is_negative in [true, false] { for p in (1u32..31).step_by(2) { - let p_long = U128::from(p); + let p_long = Odd::new(U128::from(p)).unwrap(); let j_ref = jacobi_symbol_ref(a, a_is_negative, &p_long); let j = jacobi_symbol(a, a_is_negative, &p_long); assert_eq!(j, j_ref); @@ -219,27 +207,27 @@ mod tests { fn big_values() { // a = x, p = x * y, where x and y are big primes. Should give 0. let a = 2147483647; // 2^31 - 1, a prime - let p = U128::from_be_hex("000000007ffffffeffffffe28000003b"); // (2^31 - 1) * (2^64 - 59) + let p = Odd::new(U128::from_be_hex("000000007ffffffeffffffe28000003b")).unwrap(); // (2^31 - 1) * (2^64 - 59) assert_eq!(jacobi_symbol(a, false, &p), JacobiSymbol::Zero); assert_eq!(jacobi_symbol_ref(a, false, &p), JacobiSymbol::Zero); // a = x^2 mod p, should give 1. let a = 659456; // Obtained from x = 2^70 - let p = U128::from_be_hex("ffffffffffffffffffffffffffffff5f"); // 2^128 - 161 - not a prime + let p = Odd::new(U128::from_be_hex("ffffffffffffffffffffffffffffff5f")).unwrap(); // 2^128 - 161 - not a prime assert_eq!(jacobi_symbol(a, false, &p), JacobiSymbol::One); assert_eq!(jacobi_symbol_ref(a, false, &p), JacobiSymbol::One); // -2^31 let a = 2147483648; let a_is_negative = true; - let p = U128::from_be_hex("000000007ffffffeffffffe28000003b"); // (2^31 - 1) * (2^64 - 59) + let p = Odd::new(U128::from_be_hex("000000007ffffffeffffffe28000003b")).unwrap(); // (2^31 - 1) * (2^64 - 59) assert_eq!(jacobi_symbol(a, a_is_negative, &p), JacobiSymbol::One); assert_eq!(jacobi_symbol_ref(a, a_is_negative, &p), JacobiSymbol::One); } prop_compose! { - fn odd_uint()(bytes in any::<[u8; 16]>()) -> U128 { - U128::from_le_slice(&bytes) | U128::ONE + fn odd_uint()(bytes in any::<[u8; 16]>()) -> Odd { + Odd::new(U128::from_le_slice(&bytes) | U128::ONE).unwrap() } } From 6b5c91459f7d382011dbef4a803abe3448b03b91 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 12:30:03 -0800 Subject: [PATCH 07/16] Add explicit `_vartime` suffixes for GCD and Jacobi --- src/hazmat/gcd.rs | 17 ++++++++++------- src/hazmat/jacobi.rs | 17 ++++++++++------- src/hazmat/lucas.rs | 10 +++++----- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/hazmat/gcd.rs b/src/hazmat/gcd.rs index d131b10..808a93e 100644 --- a/src/hazmat/gcd.rs +++ b/src/hazmat/gcd.rs @@ -3,7 +3,7 @@ use crypto_bigint::{Limb, NonZero, Uint, Word}; /// Calculates the greatest common divisor of `n` and `m`. /// By definition, `gcd(0, m) == m`. /// `n` must be non-zero. -pub(crate) fn gcd(n: &Uint, m: Word) -> Word { +pub(crate) fn gcd_vartime(n: &Uint, m: Word) -> Word { // This is an internal function, and it will never be called with `m = 0`. // Allowing `m = 0` would require us to have the return type of `Uint` // (since `gcd(n, 0) = n`). @@ -51,14 +51,17 @@ mod tests { use num_integer::Integer; use proptest::prelude::*; - use super::gcd; + use super::gcd_vartime; #[test] fn corner_cases() { - assert_eq!(gcd(&U128::from(0u64), 5), 5); - assert_eq!(gcd(&U128::from(1u64), 11 * 13 * 19), 1); - assert_eq!(gcd(&U128::from(7u64 * 11 * 13), 1), 1); - assert_eq!(gcd(&U128::from(7u64 * 11 * 13), 11 * 13 * 19), 11 * 13); + assert_eq!(gcd_vartime(&U128::from(0u64), 5), 5); + assert_eq!(gcd_vartime(&U128::from(1u64), 11 * 13 * 19), 1); + assert_eq!(gcd_vartime(&U128::from(7u64 * 11 * 13), 1), 1); + assert_eq!( + gcd_vartime(&U128::from(7u64 * 11 * 13), 11 * 13 * 19), + 11 * 13 + ); } prop_compose! { @@ -78,7 +81,7 @@ mod tests { let n_bi = BigUint::from_bytes_be(n.to_be_bytes().as_ref()); let gcd_ref: Word = n_bi.gcd(&m_bi).try_into().unwrap(); - let gcd_test = gcd(&n, m); + let gcd_test = gcd_vartime(&n, m); assert_eq!(gcd_test, gcd_ref); } } diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index e57b49f..70b6d29 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -73,7 +73,7 @@ fn swap(j: JacobiSymbol, a: T, p: V) -> (JacobiSymbol, } /// Returns the Jacobi symbol `(a/p)` given an odd `p`. -pub(crate) fn jacobi_symbol( +pub(crate) fn jacobi_symbol_vartime( abs_a: Word, a_is_negative: bool, p_long: &Odd>, @@ -154,7 +154,7 @@ mod tests { use num_modular::ModularSymbols; use proptest::prelude::*; - use super::{jacobi_symbol, JacobiSymbol}; + use super::{jacobi_symbol_vartime, JacobiSymbol}; #[test] fn jacobi_symbol_derived_traits() { @@ -196,7 +196,7 @@ mod tests { for p in (1u32..31).step_by(2) { let p_long = Odd::new(U128::from(p)).unwrap(); let j_ref = jacobi_symbol_ref(a, a_is_negative, &p_long); - let j = jacobi_symbol(a, a_is_negative, &p_long); + let j = jacobi_symbol_vartime(a, a_is_negative, &p_long); assert_eq!(j, j_ref); } } @@ -208,20 +208,23 @@ mod tests { // a = x, p = x * y, where x and y are big primes. Should give 0. let a = 2147483647; // 2^31 - 1, a prime let p = Odd::new(U128::from_be_hex("000000007ffffffeffffffe28000003b")).unwrap(); // (2^31 - 1) * (2^64 - 59) - assert_eq!(jacobi_symbol(a, false, &p), JacobiSymbol::Zero); + assert_eq!(jacobi_symbol_vartime(a, false, &p), JacobiSymbol::Zero); assert_eq!(jacobi_symbol_ref(a, false, &p), JacobiSymbol::Zero); // a = x^2 mod p, should give 1. let a = 659456; // Obtained from x = 2^70 let p = Odd::new(U128::from_be_hex("ffffffffffffffffffffffffffffff5f")).unwrap(); // 2^128 - 161 - not a prime - assert_eq!(jacobi_symbol(a, false, &p), JacobiSymbol::One); + assert_eq!(jacobi_symbol_vartime(a, false, &p), JacobiSymbol::One); assert_eq!(jacobi_symbol_ref(a, false, &p), JacobiSymbol::One); // -2^31 let a = 2147483648; let a_is_negative = true; let p = Odd::new(U128::from_be_hex("000000007ffffffeffffffe28000003b")).unwrap(); // (2^31 - 1) * (2^64 - 59) - assert_eq!(jacobi_symbol(a, a_is_negative, &p), JacobiSymbol::One); + assert_eq!( + jacobi_symbol_vartime(a, a_is_negative, &p), + JacobiSymbol::One + ); assert_eq!(jacobi_symbol_ref(a, a_is_negative, &p), JacobiSymbol::One); } @@ -235,7 +238,7 @@ mod tests { #[test] fn fuzzy(abs_a in any::(), a_is_negative in any::(), p in odd_uint()) { let j_ref = jacobi_symbol_ref(abs_a, a_is_negative, &p); - let j = jacobi_symbol(abs_a, a_is_negative, &p); + let j = jacobi_symbol_vartime(abs_a, a_is_negative, &p); assert_eq!(j, j_ref); } } diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 97b1d71..3512053 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -5,8 +5,8 @@ use crypto_bigint::{ }; use super::{ - gcd::gcd, - jacobi::{jacobi_symbol, JacobiSymbol}, + gcd::gcd_vartime, + jacobi::{jacobi_symbol_vartime, JacobiSymbol}, Primality, }; @@ -66,7 +66,7 @@ impl LucasBase for SelfridgeBase { } } - let j = jacobi_symbol(abs_d, d_is_negative, n); + let j = jacobi_symbol_vartime(abs_d, d_is_negative, n); if j == JacobiSymbol::MinusOne { break; @@ -153,7 +153,7 @@ impl LucasBase for BruteForceBase { } // Can unwrap here since `p` is always small (see the condition above). - let j = jacobi_symbol(p * p - 4, false, n); + let j = jacobi_symbol_vartime(p * p - 4, false, n); if j == JacobiSymbol::MinusOne { break; @@ -335,7 +335,7 @@ pub fn lucas_test( // But in order to avoid an implicit assumption that a sieve has been run, // we check that gcd(n, Q) = 1 anyway - again, since `Q` is small, // it does not noticeably affect the performance. - if abs_q != 1 && gcd(candidate, abs_q) != 1 && candidate > &Uint::::from(abs_q) { + if abs_q != 1 && gcd_vartime(candidate, abs_q) != 1 && candidate > &Uint::::from(abs_q) { return Primality::Composite; } From a8c732707c14e1ea3807af75b699ebb6c197ff0d Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 12:34:43 -0800 Subject: [PATCH 08/16] Use vartime shifts --- src/hazmat/miller_rabin.rs | 4 ++-- src/hazmat/sieve.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index be5c39e..32c8668 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -42,8 +42,8 @@ impl MillerRabin { (0, Uint::ONE) } else { let candidate_minus_one = candidate.wrapping_sub(&Uint::ONE); - let s = candidate_minus_one.trailing_zeros(); - let d = candidate_minus_one.shr(s); + let s = candidate_minus_one.trailing_zeros_vartime(); + let d = candidate_minus_one.wrapping_shr_vartime(s); (s, d) }; diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index ee0a995..46808f3 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -37,7 +37,7 @@ pub fn random_odd_uint( random |= Uint::::ONE; // Make sure it's the correct bit size - random |= Uint::::ONE << (bit_length - 1); + random |= Uint::::ONE.wrapping_shl_vartime(bit_length - 1); Odd::new(random).expect("ensured to be odd") } @@ -96,7 +96,7 @@ impl Sieve { // If we are targeting safe primes, iterate over the corresponding // possible Germain primes (`n/2`), reducing the task to that with `safe_primes = false`. let (max_bit_length, base) = if safe_primes { - (max_bit_length - 1, start >> 1) + (max_bit_length - 1, start.wrapping_shr_vartime(1)) } else { (max_bit_length, *start) }; @@ -225,10 +225,10 @@ impl Sieve { // The overflow should never happen here since `incr` // is never greater than `incr_limit`, and the latter is chosen such that // it does not overflow when added to `base` (see `update_residues()`). - let mut num = + let mut num: Uint = Option::from(self.base.checked_add(&self.incr.into())).expect("Integer overflow"); if self.safe_primes { - num = (num << 1) | Uint::::ONE; + num = num.wrapping_shl_vartime(1) | Uint::::ONE; } Some(num) }; From 45565ef379f6f43fc7011190fbcc6aef5ddf2b21 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 12:44:04 -0800 Subject: [PATCH 09/16] Adjust the limit in `SelfridgeBase::generate()` --- src/hazmat/lucas.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 3512053..b55e527 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -51,7 +51,7 @@ impl LucasBase for SelfridgeBase { fn generate(&self, n: &Odd>) -> Result<(Word, Word, bool), Primality> { let mut abs_d = 5; let mut d_is_negative = false; - let n_is_small = n.bits_vartime() < (Word::BITS - 1); + let n_is_small = n.bits_vartime() < Word::BITS; // if true, `n` fits into one `Word` let small_n = n.as_words()[0]; let mut attempts = 0; loop { From 5a25fa69d02c03a743f14303b9b225948a3ce5dc Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 12:45:44 -0800 Subject: [PATCH 10/16] `subtle::CtOption` supports `expect()` since v2.5 And `crypto-bigint` has v2.5 as its minimum bound. --- src/hazmat/miller_rabin.rs | 7 +++---- src/hazmat/sieve.rs | 12 ++++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 32c8668..14dd86a 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -108,10 +108,9 @@ impl MillerRabin { let range_nonzero = NonZero::new(range).expect("ensured to be non-zero"); // This should not overflow as long as `random_mod()` behaves according to the contract // (that is, returns a number within the given range). - let random = Option::from( - Uint::::random_mod(rng, &range_nonzero).checked_add(&Uint::::from(3u32)), - ) - .expect("Integer overflow"); + let random = Uint::::random_mod(rng, &range_nonzero) + .checked_add(&Uint::::from(3u32)) + .expect("Integer overflow"); self.test(&random) } } diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 46808f3..bedf8b3 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -158,8 +158,10 @@ impl Sieve { // Should not overflow since `incr` is never greater than `incr_limit`, // and the latter is chosen such that it doesn't overflow when added to `base` // (see the rest of this method). - self.base = - Option::from(self.base.checked_add(&self.incr.into())).expect("Integer overflow"); + self.base = self + .base + .checked_add(&self.incr.into()) + .expect("Integer overflow"); self.incr = 0; @@ -225,8 +227,10 @@ impl Sieve { // The overflow should never happen here since `incr` // is never greater than `incr_limit`, and the latter is chosen such that // it does not overflow when added to `base` (see `update_residues()`). - let mut num: Uint = - Option::from(self.base.checked_add(&self.incr.into())).expect("Integer overflow"); + let mut num: Uint = self + .base + .checked_add(&self.incr.into()) + .expect("Integer overflow"); if self.safe_primes { num = num.wrapping_shl_vartime(1) | Uint::::ONE; } From 8b02d0455afe0889520361dedfcefa25ef4d1f09 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 12:51:36 -0800 Subject: [PATCH 11/16] Use `rem` instead of `div_rem` --- src/hazmat/gcd.rs | 5 ++--- src/hazmat/jacobi.rs | 3 +-- src/hazmat/sieve.rs | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/hazmat/gcd.rs b/src/hazmat/gcd.rs index 808a93e..5a69624 100644 --- a/src/hazmat/gcd.rs +++ b/src/hazmat/gcd.rs @@ -18,9 +18,8 @@ pub(crate) fn gcd_vartime(n: &Uint, m: Word) -> Word { // Normalize input: the resulting (a, b) are both small, a >= b, and b != 0. let (mut a, mut b): (Word, Word) = if n.bits() > Word::BITS { // `m` is non-zero, so we can unwrap. - let (_quo, n) = - n.div_rem_limb(NonZero::new(Limb::from(m)).expect("divisor ensured to be non-zero")); - (m, n.0) + let r = n.rem_limb(NonZero::new(Limb::from(m)).expect("divisor ensured to be non-zero")); + (m, r.0) } else { // In this branch `n` is `Word::BITS` bits or shorter, // so we can safely take the first limb. diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index 70b6d29..7ec79f2 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -110,8 +110,7 @@ pub(crate) fn jacobi_symbol_vartime( let (result, a_long, p) = swap(result, a, p_long.get()); // Can unwrap here, since `p` is swapped with `a`, // and `a` would be odd after `reduce_numerator()`. - let (_, a) = - a_long.div_rem_limb(NonZero::new(Limb::from(p)).expect("ensured to be non-zero")); + let a = a_long.rem_limb(NonZero::new(Limb::from(p)).expect("ensured to be non-zero")); (result, a.0, p) }; diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index bedf8b3..2aa88cc 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -167,7 +167,7 @@ impl Sieve { // Re-calculate residues. for (i, rec) in RECIPROCALS.iter().enumerate().take(self.residues.len()) { - let (_quo, rem) = self.base.div_rem_limb_with_reciprocal(rec); + let rem = self.base.rem_limb_with_reciprocal(rec); self.residues[i] = rem.0 as SmallPrime; } From 50d8ea8ae9bff6915ed802548fa180d73417164e Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 12:57:33 -0800 Subject: [PATCH 12/16] Add some checked operations, just in case --- src/hazmat/lucas.rs | 7 +++++-- src/hazmat/miller_rabin.rs | 5 ++++- src/hazmat/sieve.rs | 5 ++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index b55e527..b912eba 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -187,9 +187,12 @@ fn decompose(n: &Odd>) -> (u32, Odd>) { let s = n.trailing_ones(); let d = if s < n.bits_precision() { - // This won't overflow since the original `n` was odd, so we right-shifted at least once. + // The shift won't overflow because of the check above. + // The addition won't overflow since the original `n` was odd, + // so we right-shifted at least once. n.as_ref() - .wrapping_shr(s) + .overflowing_shr(s) + .expect("shift within range") .checked_add(&Uint::ONE) .expect("Integer overflow") } else { diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 14dd86a..f4de251 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -43,7 +43,10 @@ impl MillerRabin { } else { let candidate_minus_one = candidate.wrapping_sub(&Uint::ONE); let s = candidate_minus_one.trailing_zeros_vartime(); - let d = candidate_minus_one.wrapping_shr_vartime(s); + // Will not overflow because `candidate` is odd and greater than 1. + let d = candidate_minus_one + .overflowing_shr_vartime(s) + .expect("shift within range"); (s, d) }; diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 2aa88cc..3bf9c25 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -37,7 +37,10 @@ pub fn random_odd_uint( random |= Uint::::ONE; // Make sure it's the correct bit size - random |= Uint::::ONE.wrapping_shl_vartime(bit_length - 1); + // Will not overflow since `bit_length` is ensured to be within the size of the integer. + random |= Uint::::ONE + .overflowing_shl_vartime(bit_length - 1) + .expect("shift within range"); Odd::new(random).expect("ensured to be odd") } From 52b99c02d5e58df90524c9d25b08693c21739284 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 13:11:23 -0800 Subject: [PATCH 13/16] Add a specific test for corner cases of `lucas_test()` --- src/hazmat/lucas.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index b912eba..a759de2 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -812,6 +812,22 @@ mod tests { } } + #[test] + fn corner_cases() { + // Test 1 and 2 specifically + + // By convention, 1 is composite. That's what `num-prime` returns. + let res = lucas_test(&U64::ONE, BruteForceBase, LucasCheck::AlmostExtraStrong); + assert_eq!(res, Primality::Composite); + + let res = lucas_test( + &U64::from(2u32), + BruteForceBase, + LucasCheck::AlmostExtraStrong, + ); + assert_eq!(res, Primality::Prime); + } + #[cfg(feature = "tests-exhaustive")] #[test] fn exhaustive() { From caff27311d42d76fa425832cac355fab490537d7 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 13:33:04 -0800 Subject: [PATCH 14/16] Add a coverage test for precomputed reciprocals --- src/hazmat/precomputed.rs | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/hazmat/precomputed.rs b/src/hazmat/precomputed.rs index 0f52f7f..de8c0e4 100644 --- a/src/hazmat/precomputed.rs +++ b/src/hazmat/precomputed.rs @@ -3,10 +3,8 @@ use crypto_bigint::{Limb, Reciprocal, Word}; /// The type that fits any small prime from the table. pub(crate) type SmallPrime = u16; -const SMALL_PRIMES_SIZE: usize = 2047; - /// The list of 2nd to 2048th primes (The 1st one, 2, is not included). -pub(crate) const SMALL_PRIMES: [SmallPrime; SMALL_PRIMES_SIZE] = [ +pub(crate) const SMALL_PRIMES: [SmallPrime; 2047] = [ 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, @@ -147,10 +145,10 @@ pub(crate) const SMALL_PRIMES: [SmallPrime; SMALL_PRIMES_SIZE] = [ 17747, 17749, 17761, 17783, 17789, 17791, 17807, 17827, 17837, 17839, 17851, 17863, ]; -const fn create_reciprocals() -> [Reciprocal; SMALL_PRIMES_SIZE] { - let mut arr = [Reciprocal::default(); SMALL_PRIMES_SIZE]; +const fn create_reciprocals() -> [Reciprocal; SMALL_PRIMES.len()] { + let mut arr = [Reciprocal::default(); SMALL_PRIMES.len()]; let mut i = 0; - while i < SMALL_PRIMES_SIZE { + while i < SMALL_PRIMES.len() { arr[i] = Reciprocal::new( Limb(SMALL_PRIMES[i] as Word) .to_nz() @@ -161,4 +159,28 @@ const fn create_reciprocals() -> [Reciprocal; SMALL_PRIMES_SIZE] { arr } -pub(crate) const RECIPROCALS: [Reciprocal; SMALL_PRIMES_SIZE] = create_reciprocals(); +pub(crate) const RECIPROCALS: [Reciprocal; SMALL_PRIMES.len()] = create_reciprocals(); + +#[cfg(test)] +mod tests { + use crypto_bigint::{NonZero, Random, U256}; + use rand_core::OsRng; + + use super::{create_reciprocals, SMALL_PRIMES}; + + #[test] + fn correctness() { + let reciprocals = create_reciprocals(); + + assert_eq!(reciprocals.len(), SMALL_PRIMES.len()); + + for (reciprocal, prime) in reciprocals.iter().zip(SMALL_PRIMES.iter()) { + for _ in 0..10 { + let x = U256::random(&mut OsRng); + let r_ref = (x % NonZero::new(U256::from(*prime)).unwrap()).as_limbs()[0]; + let r_test = x.rem_limb_with_reciprocal(reciprocal); + assert_eq!(r_ref, r_test); + } + } + } +} From de8d0fa685da764f0c65900114c08353d285cf26 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 14:42:20 -0800 Subject: [PATCH 15/16] Rephrase `expect` messages according to the stdlib guidelines --- src/hazmat/gcd.rs | 2 +- src/hazmat/jacobi.rs | 3 ++- src/hazmat/lucas.rs | 6 +++--- src/hazmat/miller_rabin.rs | 7 ++++--- src/hazmat/precomputed.rs | 2 +- src/hazmat/sieve.rs | 10 +++++----- src/presets.rs | 15 ++++++++------- 7 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/hazmat/gcd.rs b/src/hazmat/gcd.rs index 5a69624..c51e4a2 100644 --- a/src/hazmat/gcd.rs +++ b/src/hazmat/gcd.rs @@ -18,7 +18,7 @@ pub(crate) fn gcd_vartime(n: &Uint, m: Word) -> Word { // Normalize input: the resulting (a, b) are both small, a >= b, and b != 0. let (mut a, mut b): (Word, Word) = if n.bits() > Word::BITS { // `m` is non-zero, so we can unwrap. - let r = n.rem_limb(NonZero::new(Limb::from(m)).expect("divisor ensured to be non-zero")); + let r = n.rem_limb(NonZero::new(Limb::from(m)).expect("divisor should be non-zero here")); (m, r.0) } else { // In this branch `n` is `Word::BITS` bits or shorter, diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index 7ec79f2..f45434f 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -110,7 +110,8 @@ pub(crate) fn jacobi_symbol_vartime( let (result, a_long, p) = swap(result, a, p_long.get()); // Can unwrap here, since `p` is swapped with `a`, // and `a` would be odd after `reduce_numerator()`. - let a = a_long.rem_limb(NonZero::new(Limb::from(p)).expect("ensured to be non-zero")); + let a = + a_long.rem_limb(NonZero::new(Limb::from(p)).expect("divisor should be non-zero here")); (result, a.0, p) }; diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index a759de2..5fdd962 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -192,14 +192,14 @@ fn decompose(n: &Odd>) -> (u32, Odd>) { // so we right-shifted at least once. n.as_ref() .overflowing_shr(s) - .expect("shift within range") + .expect("shift should be within range by construction") .checked_add(&Uint::ONE) - .expect("Integer overflow") + .expect("addition should not overflow by construction") } else { Uint::ONE }; - (s, Odd::new(d).expect("ensured to be odd")) + (s, Odd::new(d).expect("`d` should be odd by construction")) } /// The checks to perform in the Lucas test. diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index f4de251..faf1156 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -46,7 +46,7 @@ impl MillerRabin { // Will not overflow because `candidate` is odd and greater than 1. let d = candidate_minus_one .overflowing_shr_vartime(s) - .expect("shift within range"); + .expect("shift should be within range by construction"); (s, d) }; @@ -108,12 +108,13 @@ impl MillerRabin { let range = self.candidate.wrapping_sub(&Uint::::from(4u32)); // Can unwrap here since `candidate` is odd, and `candidate >= 4` (as checked above) - let range_nonzero = NonZero::new(range).expect("ensured to be non-zero"); + let range_nonzero = + NonZero::new(range).expect("the range should be non-zero by construction"); // This should not overflow as long as `random_mod()` behaves according to the contract // (that is, returns a number within the given range). let random = Uint::::random_mod(rng, &range_nonzero) .checked_add(&Uint::::from(3u32)) - .expect("Integer overflow"); + .expect("addition should not overflow by construction"); self.test(&random) } } diff --git a/src/hazmat/precomputed.rs b/src/hazmat/precomputed.rs index de8c0e4..ae395e5 100644 --- a/src/hazmat/precomputed.rs +++ b/src/hazmat/precomputed.rs @@ -152,7 +152,7 @@ const fn create_reciprocals() -> [Reciprocal; SMALL_PRIMES.len()] { arr[i] = Reciprocal::new( Limb(SMALL_PRIMES[i] as Word) .to_nz() - .expect("ensured to be non-zero"), + .expect("divisor should be non-zero"), ); i += 1; } diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 3bf9c25..8b10edd 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -40,9 +40,9 @@ pub fn random_odd_uint( // Will not overflow since `bit_length` is ensured to be within the size of the integer. random |= Uint::::ONE .overflowing_shl_vartime(bit_length - 1) - .expect("shift within range"); + .expect("shift should be within range by construction"); - Odd::new(random).expect("ensured to be odd") + Odd::new(random).expect("the number should be odd by construction") } // The type we use to calculate incremental residues. @@ -164,7 +164,7 @@ impl Sieve { self.base = self .base .checked_add(&self.incr.into()) - .expect("Integer overflow"); + .expect("addition should not overflow by construction"); self.incr = 0; @@ -190,7 +190,7 @@ impl Sieve { // and `INCR_LIMIT` fits into `Residue`. let incr_limit_small: Residue = incr_limit.as_words()[0] .try_into() - .expect("ensured to fit within `Residue`"); + .expect("the increment limit should fit within `Residue`"); incr_limit_small }; @@ -233,7 +233,7 @@ impl Sieve { let mut num: Uint = self .base .checked_add(&self.incr.into()) - .expect("Integer overflow"); + .expect("addition should not overflow by construction"); if self.safe_primes { num = num.wrapping_shl_vartime(1) | Uint::::ONE; } diff --git a/src/presets.rs b/src/presets.rs index db6c379..f1fc003 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -1,4 +1,4 @@ -use crypto_bigint::{Integer, Odd, Uint}; +use crypto_bigint::{Odd, Uint}; use rand_core::CryptoRngCore; #[cfg(feature = "default-rng")] @@ -125,11 +125,12 @@ pub fn is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &Uin if num == &Uint::::from(2u32) { return true; } - if num.is_even().into() { - return false; - } - let odd_num = Odd::new(*num).expect("ensured to be odd"); + let odd_num = match Odd::new(*num).into() { + Some(x) => x, + None => return false, + }; + _is_prime_with_rng(rng, &odd_num) } @@ -148,8 +149,8 @@ pub fn is_safe_prime_with_rng(rng: &mut impl CryptoRngCore, num: } // These are ensured to be odd by the check above. - let odd_num = Odd::new(*num).expect("ensured to be odd"); - let odd_half_num = Odd::new(num.wrapping_shr_vartime(1)).expect("ensured to be odd"); + let odd_num = Odd::new(*num).expect("`num` should be odd here"); + let odd_half_num = Odd::new(num.wrapping_shr_vartime(1)).expect("`num/2` should be odd here"); _is_prime_with_rng(rng, &odd_num) && _is_prime_with_rng(rng, &odd_half_num) } From 685a6ee7b19b826163ff7861d82098cbd86c739c Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 24 Dec 2023 15:01:46 -0800 Subject: [PATCH 16/16] Make `lucas_test()` take an `Odd`-wrapped integer --- CHANGELOG.md | 2 +- benches/bench.rs | 5 +- src/hazmat/lucas.rs | 149 ++++++++++++++++++++++++-------------------- src/presets.rs | 2 +- 4 files changed, 86 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6203336..c34723d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bumped `crypto-bigint` to 0.6.0-pre.5. ([#38]) - Bumped MSRV to 1.73. (#[38]) -- `MillerRabin::new()` takes an `Odd`-wrapped integer by value. `random_odd_uint()` returns an `Odd`-wrapped integer. `LucasBase::generate()` takes an `Odd`-wrapped integer. (#[38]) +- `MillerRabin::new()` takes an `Odd`-wrapped integer by value. `random_odd_uint()` returns an `Odd`-wrapped integer. `LucasBase::generate()` takes an `Odd`-wrapped integer. `lucas_test` takes an `Odd`-wrapped integer. (#[38]) - All bit length-type parameters take `u32` instead of `usize`. (#[38]) diff --git a/benches/bench.rs b/benches/bench.rs index 8992b5e..48eb504 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -171,12 +171,13 @@ fn bench_lucas(c: &mut Criterion) { // - V_{d * 2^t} checked for t == 0..s-1, but no V = 0 found // - s = 5, so the previous step has multiple checks // - Q != 1 (since we're using Selfridge base) - let slow_path = U1024::from_be_hex(concat![ + let slow_path = Odd::new(U1024::from_be_hex(concat![ "D1CB9F1B6F3414A4B40A7E51C53C6AE4689DFCDC49FF875E7066A229D704EA8E", "6B674231D8C5974001673C3CE7FF9D377C8564E5182165A23434BC7B7E6C0419", "FD25C9921B0E9C90AF2570DB0772E1A9C82ACABBC8FC0F0864CE8A12124FA29B", "7F870924041DFA13EE5F5541C1BF96CA679EFAE2C96F5F4E9DF6007185198F5F" - ]); + ])) + .unwrap(); group.bench_function("(U1024) Selfridge base, strong check, slow path", |b| { b.iter(|| { diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 5fdd962..d96676a 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -286,7 +286,7 @@ pub enum LucasCheck { /// See [`LucasCheck`] for possible checks, and the implementors of [`LucasBase`] /// for the corresponding bases. pub fn lucas_test( - candidate: &Uint, + candidate: &Odd>, base: impl LucasBase, check: LucasCheck, ) -> Primality { @@ -296,17 +296,8 @@ pub fn lucas_test( // R. Crandall, C. Pomerance, "Prime numbers: a computational perspective", // 2nd ed., Springer (2005) (ISBN: 0-387-25282-7, 978-0387-25282-7) - if candidate == &Uint::::from(2u32) { - return Primality::Prime; - } - - let odd_candidate = match Odd::new(*candidate).into() { - Some(x) => x, - None => return Primality::Composite, - }; - // Find the base for the Lucas sequence. - let (p, abs_q, q_is_negative) = match base.generate(&odd_candidate) { + let (p, abs_q, q_is_negative) = match base.generate(candidate) { Ok(pq) => pq, Err(primality) => return primality, }; @@ -338,17 +329,20 @@ pub fn lucas_test( // But in order to avoid an implicit assumption that a sieve has been run, // we check that gcd(n, Q) = 1 anyway - again, since `Q` is small, // it does not noticeably affect the performance. - if abs_q != 1 && gcd_vartime(candidate, abs_q) != 1 && candidate > &Uint::::from(abs_q) { + if abs_q != 1 + && gcd_vartime(candidate, abs_q) != 1 + && candidate.as_ref() > &Uint::::from(abs_q) + { return Primality::Composite; } // Find `d` and `s`, such that `d` is odd and `d * 2^s = n - (D/n)`. // Since `(D/n) == -1` by construction, we're looking for `d * 2^s = n + 1`. - let (s, d) = decompose(&odd_candidate); + let (s, d) = decompose(candidate); // Some constants in Montgomery form - let params = MontyParams::::new(odd_candidate); + let params = MontyParams::::new(*candidate); let zero = MontyForm::::zero(params); let one = MontyForm::::one(params); @@ -547,15 +541,6 @@ mod tests { ) } - #[test] - fn lucas_early_quit() { - // If the number is even, no need to run the test. - assert_eq!( - lucas_test(&U64::from(6u32), SelfridgeBase, LucasCheck::Strong), - Primality::Composite - ); - } - #[test] fn gcd_check() { // Test that `gcd(2QD, n) == 1` is checked after the base is found. @@ -576,7 +561,11 @@ mod tests { } assert_eq!( - lucas_test(&U64::from(15u32), TestBase, LucasCheck::Strong), + lucas_test( + &Odd::new(U64::from(15u32)).unwrap(), + TestBase, + LucasCheck::Strong + ), Primality::Composite ); } @@ -626,13 +615,21 @@ mod tests { // Test both single-limb and multi-limb, just in case. assert_eq!( - lucas_test(&Uint::<1>::from(*num), SelfridgeBase, LucasCheck::Strong) - .is_probably_prime(), + lucas_test( + &Odd::new(Uint::<1>::from(*num)).unwrap(), + SelfridgeBase, + LucasCheck::Strong + ) + .is_probably_prime(), actual_expected_result ); assert_eq!( - lucas_test(&Uint::<2>::from(*num), SelfridgeBase, LucasCheck::Strong) - .is_probably_prime(), + lucas_test( + &Odd::new(Uint::<2>::from(*num)).unwrap(), + SelfridgeBase, + LucasCheck::Strong + ) + .is_probably_prime(), actual_expected_result ); } @@ -649,13 +646,21 @@ mod tests { // Test both single-limb and multi-limb, just in case. assert_eq!( - lucas_test(&Uint::<1>::from(*num), AStarBase, LucasCheck::LucasV) - .is_probably_prime(), + lucas_test( + &Odd::new(Uint::<1>::from(*num)).unwrap(), + AStarBase, + LucasCheck::LucasV + ) + .is_probably_prime(), actual_expected_result ); assert_eq!( - lucas_test(&Uint::<2>::from(*num), AStarBase, LucasCheck::LucasV) - .is_probably_prime(), + lucas_test( + &Odd::new(Uint::<2>::from(*num)).unwrap(), + AStarBase, + LucasCheck::LucasV + ) + .is_probably_prime(), actual_expected_result ); } @@ -681,12 +686,22 @@ mod tests { // Test both single-limb and multi-limb, just in case. assert_eq!( - lucas_test(&Uint::<1>::from(*num), BruteForceBase, check).is_probably_prime(), + lucas_test( + &Odd::new(Uint::<1>::from(*num)).unwrap(), + BruteForceBase, + check + ) + .is_probably_prime(), actual_expected_result, "Brute force base, n = {num}, almost_extra = {almost_extra}", ); assert_eq!( - lucas_test(&Uint::<2>::from(*num), BruteForceBase, check).is_probably_prime(), + lucas_test( + &Odd::new(Uint::<2>::from(*num)).unwrap(), + BruteForceBase, + check + ) + .is_probably_prime(), actual_expected_result ); } @@ -698,8 +713,16 @@ mod tests { // Good thing we don't need to test for intersection // with `EXTRA_STRONG_LUCAS` or `STRONG_LUCAS` - there's none. for num in pseudoprimes::STRONG_FIBONACCI.iter() { - assert!(!lucas_test(num, SelfridgeBase, LucasCheck::Strong).is_probably_prime()); - assert!(!lucas_test(num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime()); + assert!( + !lucas_test(&Odd::new(*num).unwrap(), SelfridgeBase, LucasCheck::Strong) + .is_probably_prime() + ); + assert!(!lucas_test( + &Odd::new(*num).unwrap(), + BruteForceBase, + LucasCheck::ExtraStrong + ) + .is_probably_prime()); } } @@ -770,7 +793,7 @@ mod tests { #[test] fn large_carmichael_number() { - let p = pseudoprimes::LARGE_CARMICHAEL_NUMBER; + let p = Odd::new(pseudoprimes::LARGE_CARMICHAEL_NUMBER).unwrap(); assert!(!lucas_test(&p, SelfridgeBase, LucasCheck::Strong).is_probably_prime()); assert!(!lucas_test(&p, AStarBase, LucasCheck::LucasV).is_probably_prime()); assert!(!lucas_test(&p, BruteForceBase, LucasCheck::AlmostExtraStrong).is_probably_prime()); @@ -779,12 +802,13 @@ mod tests { fn test_large_primes(nums: &[Uint]) { for num in nums { - assert!(lucas_test(num, SelfridgeBase, LucasCheck::Strong).is_probably_prime()); - assert!(lucas_test(num, AStarBase, LucasCheck::LucasV).is_probably_prime()); + let num = Odd::new(*num).unwrap(); + assert!(lucas_test(&num, SelfridgeBase, LucasCheck::Strong).is_probably_prime()); + assert!(lucas_test(&num, AStarBase, LucasCheck::LucasV).is_probably_prime()); assert!( - lucas_test(num, BruteForceBase, LucasCheck::AlmostExtraStrong).is_probably_prime() + lucas_test(&num, BruteForceBase, LucasCheck::AlmostExtraStrong).is_probably_prime() ); - assert!(lucas_test(num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime()); + assert!(lucas_test(&num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime()); } } @@ -800,32 +824,29 @@ mod tests { #[test] fn test_lucas_v_pseudoprimes() { for num in pseudoprimes::LARGE_LUCAS_V { + let num = Odd::new(*num).unwrap(); // These are false positives for Lucas-V test - assert!(lucas_test(num, AStarBase, LucasCheck::LucasV).is_probably_prime()); + assert!(lucas_test(&num, AStarBase, LucasCheck::LucasV).is_probably_prime()); // These tests should work correctly - assert!(!lucas_test(num, SelfridgeBase, LucasCheck::Strong).is_probably_prime()); + assert!(!lucas_test(&num, SelfridgeBase, LucasCheck::Strong).is_probably_prime()); assert!( - !lucas_test(num, BruteForceBase, LucasCheck::AlmostExtraStrong).is_probably_prime() + !lucas_test(&num, BruteForceBase, LucasCheck::AlmostExtraStrong) + .is_probably_prime() ); - assert!(!lucas_test(num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime()); + assert!(!lucas_test(&num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime()); } } #[test] fn corner_cases() { - // Test 1 and 2 specifically - // By convention, 1 is composite. That's what `num-prime` returns. - let res = lucas_test(&U64::ONE, BruteForceBase, LucasCheck::AlmostExtraStrong); - assert_eq!(res, Primality::Composite); - let res = lucas_test( - &U64::from(2u32), + &Odd::new(U64::ONE).unwrap(), BruteForceBase, LucasCheck::AlmostExtraStrong, ); - assert_eq!(res, Primality::Prime); + assert_eq!(res, Primality::Composite); } #[cfg(feature = "tests-exhaustive")] @@ -841,40 +862,32 @@ mod tests { let slpsp = is_slpsp(num); let vpsp = is_vpsp(num); - let res = lucas_test( - &Uint::<1>::from(num), - BruteForceBase, - LucasCheck::AlmostExtraStrong, - ) - .is_probably_prime(); + let odd_num = Odd::new(Uint::<1>::from(num)).unwrap(); + + let res = lucas_test(&odd_num, BruteForceBase, LucasCheck::AlmostExtraStrong) + .is_probably_prime(); let expected = aeslpsp || res_ref; assert_eq!( res, expected, "Brute force base, almost extra strong: n={num}, expected={expected}, actual={res}", ); - let res = lucas_test( - &Uint::<1>::from(num), - BruteForceBase, - LucasCheck::ExtraStrong, - ) - .is_probably_prime(); + let res = + lucas_test(&odd_num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime(); let expected = eslpsp || res_ref; assert_eq!( res, expected, "Brute force base: n={num}, expected={expected}, actual={res}", ); - let res = lucas_test(&Uint::<1>::from(num), SelfridgeBase, LucasCheck::Strong) - .is_probably_prime(); + let res = lucas_test(&odd_num, SelfridgeBase, LucasCheck::Strong).is_probably_prime(); let expected = slpsp || res_ref; assert_eq!( res, expected, "Selfridge base: n={num}, expected={expected}, actual={res}", ); - let res = lucas_test(&Uint::<1>::from(num), AStarBase, LucasCheck::LucasV) - .is_probably_prime(); + let res = lucas_test(&odd_num, AStarBase, LucasCheck::LucasV).is_probably_prime(); let expected = vpsp || res_ref; assert_eq!( diff --git a/src/presets.rs b/src/presets.rs index f1fc003..5b880ab 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -163,7 +163,7 @@ fn _is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &Odd return false, Primality::Prime => return true, _ => {}