diff --git a/CHANGELOG.md b/CHANGELOG.md index 2692ff7..a8ca3fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,15 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Bumped `crypto-bigint` to 0.6.0-pre.6. ([#38]) -- Bumped MSRV to 1.73. (#[38]) -- `MillerRabin::new()` takes an `Odd`-wrapped integer. `random_odd_uint()` is renamed to `random_odd_integer()`, takes a `NonZeroU32` for `bit_length`, and 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]) -- All the API is based on the `Integer` trait instead of `Uint` specifically. (#[38]) +- Bumped `crypto-bigint` to 0.6.0-pre.7. ([#40]) +- Bumped MSRV to 1.73. ([#36]) +- `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. ([#36]) +- `random_odd_uint()` is renamed to `random_odd_integer()`, takes a `NonZeroU32` for `bit_length`. ([#38]) +- All bit length-type parameters take `u32` instead of `usize`. ([#36]) +- All the API is based on the `Integer` trait instead of `Uint` specifically. ([#38]) +- High-level generation/checking functions take an additional `bits_precision` argument. ([#40]) [#36]: https://github.com/entropyxyz/crypto-primes/pull/36 [#38]: https://github.com/entropyxyz/crypto-primes/pull/38 +[#40]: https://github.com/entropyxyz/crypto-primes/pull/40 ## [0.5.0] - 2023-08-20 diff --git a/Cargo.toml b/Cargo.toml index f0741d4..945787d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,14 @@ categories = ["cryptography", "no-std"] rust-version = "1.73" [dependencies] -crypto-bigint = { version = "0.6.0-pre.6", default-features = false, features = ["rand_core"] } +crypto-bigint = { version = "0.6.0-pre.7", 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 } [dev-dependencies] +# need `crypto-bigint` with `alloc` to test `BoxedUint` +crypto-bigint = { version = "0.6.0-pre.7", default-features = false, features = ["alloc"] } rand_chacha = "0.3" criterion = { version = "0.4", features = ["html_reports"] } num-modular = { version = "0.5", features = ["num-bigint"] } diff --git a/benches/bench.rs b/benches/bench.rs index 3923f46..d4f22ed 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,7 +1,7 @@ use core::num::NonZeroU32; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; -use crypto_bigint::{nlimbs, Integer, Odd, RandomBits, Uint, U1024, U128, U256}; +use crypto_bigint::{nlimbs, BoxedUint, Integer, Odd, RandomBits, Uint, U1024, U128, U256}; use rand_chacha::ChaCha8Rng; use rand_core::{CryptoRngCore, OsRng, SeedableRng}; @@ -27,12 +27,13 @@ fn make_rng() -> ChaCha8Rng { fn random_odd_uint( rng: &mut impl CryptoRngCore, bit_length: u32, + bits_precision: u32, ) -> Odd { - random_odd_integer::(rng, NonZeroU32::new(bit_length).unwrap()) + random_odd_integer::(rng, NonZeroU32::new(bit_length).unwrap(), bits_precision) } fn make_sieve(rng: &mut impl CryptoRngCore) -> Sieve> { - let start = random_odd_uint::>(rng, Uint::::BITS); + let start = random_odd_uint::>(rng, Uint::::BITS, Uint::::BITS); Sieve::new(&start, NonZeroU32::new(Uint::::BITS).unwrap(), false) } @@ -45,12 +46,12 @@ fn bench_sieve(c: &mut Criterion) { let mut group = c.benchmark_group("Sieve"); group.bench_function("(U128) random start", |b| { - b.iter(|| random_odd_uint::(&mut OsRng, 128)) + b.iter(|| random_odd_uint::(&mut OsRng, 128, 128)) }); group.bench_function("(U128) creation", |b| { b.iter_batched( - || random_odd_uint::(&mut OsRng, 128), + || random_odd_uint::(&mut OsRng, 128, 128), |start| Sieve::new(start.as_ref(), NonZeroU32::new(128).unwrap(), false), BatchSize::SmallInput, ) @@ -66,12 +67,12 @@ fn bench_sieve(c: &mut Criterion) { }); group.bench_function("(U1024) random start", |b| { - b.iter(|| random_odd_uint::(&mut OsRng, 1024)) + b.iter(|| random_odd_uint::(&mut OsRng, 1024, 1024)) }); group.bench_function("(U1024) creation", |b| { b.iter_batched( - || random_odd_uint::(&mut OsRng, 1024), + || random_odd_uint::(&mut OsRng, 1024, 1024), |start| Sieve::new(start.as_ref(), NonZeroU32::new(1024).unwrap(), false), BatchSize::SmallInput, ) @@ -93,7 +94,7 @@ fn bench_miller_rabin(c: &mut Criterion) { group.bench_function("(U128) creation", |b| { b.iter_batched( - || random_odd_uint::(&mut OsRng, 128), + || random_odd_uint::(&mut OsRng, 128, 128), |n| MillerRabin::new(&n), BatchSize::SmallInput, ) @@ -109,7 +110,7 @@ fn bench_miller_rabin(c: &mut Criterion) { group.bench_function("(U1024) creation", |b| { b.iter_batched( - || random_odd_uint::(&mut OsRng, 1024), + || random_odd_uint::(&mut OsRng, 1024, 1024), |n| MillerRabin::new(&n), BatchSize::SmallInput, ) @@ -202,7 +203,7 @@ fn bench_presets(c: &mut Criterion) { group.bench_function("(U128) Prime test", |b| { b.iter_batched( - || random_odd_uint::(&mut OsRng, 128), + || random_odd_uint::(&mut OsRng, 128, 128), |num| is_prime_with_rng(&mut OsRng, num.as_ref()), BatchSize::SmallInput, ) @@ -210,7 +211,7 @@ fn bench_presets(c: &mut Criterion) { group.bench_function("(U128) Safe prime test", |b| { b.iter_batched( - || random_odd_uint::(&mut OsRng, 128), + || random_odd_uint::(&mut OsRng, 128, 128), |num| is_safe_prime_with_rng(&mut OsRng, num.as_ref()), BatchSize::SmallInput, ) @@ -218,23 +219,34 @@ fn bench_presets(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U128) Random prime", |b| { - b.iter(|| generate_prime_with_rng::(&mut rng, 128)) + b.iter(|| generate_prime_with_rng::(&mut rng, 128, 128)) }); let mut rng = make_rng(); group.bench_function("(U1024) Random prime", |b| { - b.iter(|| generate_prime_with_rng::(&mut rng, 1024)) + b.iter(|| generate_prime_with_rng::(&mut rng, 1024, 1024)) }); let mut rng = make_rng(); group.bench_function("(U128) Random safe prime", |b| { - b.iter(|| generate_safe_prime_with_rng::(&mut rng, 128)) + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 128, 128)) }); group.sample_size(20); let mut rng = make_rng(); group.bench_function("(U1024) Random safe prime", |b| { - b.iter(|| generate_safe_prime_with_rng::(&mut rng, 1024)) + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 1024, 1024)) + }); + + let mut rng = make_rng(); + group.bench_function("(Boxed128) Random safe prime", |b| { + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 128, 128)) + }); + + group.sample_size(20); + let mut rng = make_rng(); + group.bench_function("(Boxed1024) Random safe prime", |b| { + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 1024, 1024)) }); group.finish(); @@ -244,19 +256,19 @@ fn bench_presets(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U128) Random safe prime", |b| { - b.iter(|| generate_safe_prime_with_rng::(&mut rng, 128)) + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 128, 128)) }); // The performance should scale with the prime size, not with the Uint size. // So we should strive for this test's result to be as close as possible // to that of the previous one and as far away as possible from the next one. group.bench_function("(U256) Random 128 bit safe prime", |b| { - b.iter(|| generate_safe_prime_with_rng::(&mut rng, 128)) + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 128, 256)) }); // The upper bound for the previous test. group.bench_function("(U256) Random 256 bit safe prime", |b| { - b.iter(|| generate_safe_prime_with_rng::(&mut rng, 256)) + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 256, 256)) }); group.finish(); @@ -267,7 +279,7 @@ fn bench_gmp(c: &mut Criterion) { let mut group = c.benchmark_group("GMP"); fn random(rng: &mut impl CryptoRngCore) -> GmpInteger { - let num = random_odd_uint::>(rng, Uint::::BITS).get(); + let num = random_odd_uint::>(rng, Uint::::BITS, Uint::::BITS).get(); GmpInteger::from_digits(num.as_words(), Order::Lsf) } diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index c435872..ed29a31 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -84,7 +84,7 @@ pub(crate) fn jacobi_symbol_vartime( }; // A degenerate case. - if abs_a == 1 || p_long.as_ref() == &T::one() { + if abs_a == 1 || p_long.as_ref() == &T::one_like(p_long) { return result; } diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 42ddbde..583bd2b 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -1,5 +1,5 @@ //! Lucas primality test. -use crypto_bigint::{Integer, Monty, Odd, Square, Word}; +use crypto_bigint::{Integer, Limb, Monty, Odd, Square, Word}; use super::{ gcd::gcd_vartime, @@ -161,7 +161,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.as_ref() == &T::from(p + 2) { + let primality = if n.as_ref() == &T::from_limb_like(Limb::from(p + 2), n.as_ref()) { Primality::Prime } else { Primality::Composite @@ -182,6 +182,7 @@ 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 one = T::one_like(n); let s = n.trailing_ones_vartime(); let d = if s < n.bits_precision() { // The shift won't overflow because of the check above. @@ -190,10 +191,10 @@ fn decompose(n: &Odd) -> (u32, Odd) { n.as_ref() .overflowing_shr_vartime(s) .expect("shift should be within range by construction") - .checked_add(&T::one()) + .checked_add(&one) .expect("addition should not overflow by construction") } else { - T::one() + one }; (s, Odd::new(d).expect("`d` should be odd by construction")) @@ -293,6 +294,9 @@ 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) + // A word-to-big integer conversion helper + let to_integer = |x: Word| T::from_limb_like(Limb::from(x), candidate.as_ref()); + // Find the base for the Lucas sequence. let (p, abs_q, q_is_negative) = match base.generate(candidate) { Ok(pq) => pq, @@ -328,7 +332,7 @@ pub fn lucas_test( // it does not noticeably affect the performance. if abs_q != 1 && gcd_vartime(candidate.as_ref(), abs_q) != 1 - && candidate.as_ref() > &T::from(abs_q) + && candidate.as_ref() > &to_integer(abs_q) { return Primality::Composite; } @@ -351,7 +355,7 @@ pub fn lucas_test( let q = if q_is_one { one.clone() } else { - let abs_q = ::Monty::new(T::from(abs_q), params.clone()); + let abs_q = ::Monty::new(to_integer(abs_q), params.clone()); if q_is_negative { -abs_q } else { @@ -364,7 +368,7 @@ pub fn lucas_test( let p = if p_is_one { one.clone() } else { - ::Monty::new(T::from(p), params.clone()) + ::Monty::new(to_integer(p), params.clone()) }; // Compute d-th element of Lucas sequence (U_d(P, Q), V_d(P, Q)), where: @@ -387,7 +391,7 @@ pub fn lucas_test( let mut qk = one.clone(); // keeps Q^k // D in Montgomery representation - note that it can be negative. - let abs_d = ::Monty::new(T::from(abs_d), params); + let abs_d = ::Monty::new(to_integer(abs_d), params); let d_m = if d_is_negative { -abs_d } else { abs_d }; for i in (0..d.bits_vartime()).rev() { diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 31e2439..9bedb92 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -1,6 +1,6 @@ //! Miller-Rabin primality test. -use crypto_bigint::{Integer, Monty, NonZero, Odd, PowBoundedExp, RandomMod, Square}; +use crypto_bigint::{Integer, Limb, Monty, NonZero, Odd, PowBoundedExp, RandomMod, Square}; use rand_core::CryptoRngCore; use super::Primality; @@ -28,14 +28,16 @@ impl MillerRabin { /// Initializes a Miller-Rabin test for `candidate`. pub fn new(candidate: &Odd) -> Self { let params = ::Monty::new_params_vartime(candidate.clone()); - let one = ::Monty::one(params.clone()); - let minus_one = -one.clone(); + let m_one = ::Monty::one(params.clone()); + let m_minus_one = -m_one.clone(); + + let one = T::one_like(candidate.as_ref()); // Find `s` and odd `d` such that `candidate - 1 == 2^s * d`. - let (s, d) = if candidate.as_ref() == &T::one() { - (0, T::one()) + let (s, d) = if candidate.as_ref() == &one { + (0, one) } else { - let candidate_minus_one = candidate.wrapping_sub(&T::one()); + let candidate_minus_one = candidate.wrapping_sub(&one); let s = candidate_minus_one.trailing_zeros_vartime(); // Will not overflow because `candidate` is odd and greater than 1. let d = candidate_minus_one @@ -48,8 +50,8 @@ impl MillerRabin { candidate: candidate.as_ref().clone(), bit_length: candidate.bits_vartime(), montgomery_params: params, - one, - minus_one, + one: m_one, + minus_one: m_minus_one, s, d, } @@ -85,7 +87,7 @@ impl MillerRabin { /// Perform a Miller-Rabin check with base 2. pub fn test_base_two(&self) -> Primality { - self.test(&T::from(2u32)) + self.test(&T::from_limb_like(Limb::from(2u32), &self.candidate)) } /// Perform a Miller-Rabin check with a random base (in the range `[3, candidate-2]`) @@ -189,7 +191,8 @@ mod tests { #[test] fn trivial() { let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); - let start = random_odd_integer::(&mut rng, NonZeroU32::new(1024).unwrap()); + let start = + random_odd_integer::(&mut rng, NonZeroU32::new(1024).unwrap(), U1024::BITS); for num in Sieve::new(start.as_ref(), NonZeroU32::new(1024).unwrap(), false).take(10) { let mr = MillerRabin::new(&Odd::new(num).unwrap()); diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 57f183c..31baf42 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -14,11 +14,12 @@ use crate::hazmat::precomputed::{SmallPrime, RECIPROCALS, SMALL_PRIMES}; pub fn random_odd_integer( rng: &mut impl CryptoRngCore, bit_length: NonZeroU32, + bits_precision: u32, ) -> Odd { let bit_length = bit_length.get(); - let mut random = T::random_bits(rng, bit_length); - + let mut random = T::random_bits_with_precision(rng, bit_length, bits_precision); + assert!(random.bits_precision() == bits_precision); // Make it odd random.set_bit_vartime(0, true); @@ -99,7 +100,7 @@ impl Sieve { base = T::from(3u32); } else { // Adjust the base so that we hit odd numbers when incrementing it by 2. - base |= T::one(); + base |= T::one_like(start); } // Only calculate residues by primes up to and not including `base`, @@ -157,9 +158,12 @@ impl Sieve { } // Find the increment limit. - let max_value = match T::one().overflowing_shl_vartime(self.max_bit_length).into() { + let max_value = match T::one_like(&self.base) + .overflowing_shl_vartime(self.max_bit_length) + .into() + { Some(val) => val, - None => T::one(), + None => T::one_like(&self.base), }; let incr_limit = max_value.wrapping_sub(&self.base); self.incr_limit = if incr_limit > T::from(INCR_LIMIT) { @@ -218,7 +222,7 @@ impl Sieve { .checked_add(&self.incr.into()) .expect("addition should not overflow by construction"); if self.safe_primes { - num = num.wrapping_shl_vartime(1) | T::one(); + num = num.wrapping_shl_vartime(1) | T::one_like(&self.base); } Some(num) }; @@ -279,7 +283,8 @@ mod tests { let max_prime = SMALL_PRIMES[SMALL_PRIMES.len() - 1]; let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); - let start = random_odd_integer::(&mut rng, NonZeroU32::new(32).unwrap()).get(); + let start = + random_odd_integer::(&mut rng, NonZeroU32::new(32).unwrap(), U64::BITS).get(); for num in Sieve::new(&start, NonZeroU32::new(32).unwrap(), false).take(100) { let num_u64 = u64::from(num); assert!(num_u64.leading_zeros() == 32); @@ -360,17 +365,18 @@ mod tests { #[test] fn random_below_max_length() { for _ in 0..10 { - let r = random_odd_integer::(&mut OsRng, NonZeroU32::new(50).unwrap()).get(); + let r = random_odd_integer::(&mut OsRng, NonZeroU32::new(50).unwrap(), U64::BITS) + .get(); assert_eq!(r.bits(), 50); } } #[test] #[should_panic( - expected = "try_random_bits() failed: BitLengthTooLarge { bit_length: 65, bits_precision: 64 }" + expected = "try_random_bits_with_precision() failed: BitLengthTooLarge { bit_length: 65, bits_precision: 64 }" )] fn random_odd_uint_too_many_bits() { - let _p = random_odd_integer::(&mut OsRng, NonZeroU32::new(65).unwrap()); + let _p = random_odd_integer::(&mut OsRng, NonZeroU32::new(65).unwrap(), U64::BITS); } #[test] diff --git a/src/presets.rs b/src/presets.rs index ba80c98..bc6023e 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -1,6 +1,6 @@ use core::num::NonZeroU32; -use crypto_bigint::{Integer, Odd, RandomBits, RandomMod}; +use crypto_bigint::{Integer, Limb, Odd, RandomBits, RandomMod}; use rand_core::CryptoRngCore; #[cfg(feature = "default-rng")] @@ -15,8 +15,11 @@ use crate::hazmat::{ /// /// See [`is_prime_with_rng`] for details about the performed checks. #[cfg(feature = "default-rng")] -pub fn generate_prime(bit_length: u32) -> T { - generate_prime_with_rng(&mut OsRng, bit_length) +pub fn generate_prime( + bit_length: u32, + bits_precision: u32, +) -> T { + generate_prime_with_rng(&mut OsRng, bit_length, bits_precision) } /// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime) @@ -25,8 +28,11 @@ pub fn generate_prime(bit_length: u32) -> T /// /// See [`is_prime_with_rng`] for details about the performed checks. #[cfg(feature = "default-rng")] -pub fn generate_safe_prime(bit_length: u32) -> T { - generate_safe_prime_with_rng(&mut OsRng, bit_length) +pub fn generate_safe_prime( + bit_length: u32, + bits_precision: u32, +) -> T { + generate_safe_prime_with_rng(&mut OsRng, bit_length, bits_precision) } /// Checks probabilistically if the given number is prime using [`OsRng`] as the RNG. @@ -56,13 +62,14 @@ pub fn is_safe_prime(num: &T) -> bool { pub fn generate_prime_with_rng( rng: &mut impl CryptoRngCore, bit_length: u32, + bits_precision: u32, ) -> T { if bit_length < 2 { panic!("`bit_length` must be 2 or greater."); } let bit_length = NonZeroU32::new(bit_length).expect("`bit_length` should be non-zero"); loop { - let start = random_odd_integer::(rng, bit_length); + let start = random_odd_integer::(rng, bit_length, bits_precision); let sieve = Sieve::new(start.as_ref(), bit_length, false); for num in sieve { if is_prime_with_rng(rng, &num) { @@ -82,13 +89,14 @@ pub fn generate_prime_with_rng( pub fn generate_safe_prime_with_rng( rng: &mut impl CryptoRngCore, bit_length: u32, + bits_precision: u32, ) -> T { if bit_length < 3 { panic!("`bit_length` must be 3 or greater."); } let bit_length = NonZeroU32::new(bit_length).expect("`bit_length` should be non-zero"); loop { - let start = random_odd_integer::(rng, bit_length); + let start = random_odd_integer::(rng, bit_length, bits_precision); let sieve = Sieve::new(start.as_ref(), bit_length, true); for num in sieve { if is_safe_prime_with_rng(rng, &num) { @@ -124,7 +132,7 @@ pub fn generate_safe_prime_with_rng( /// Math. Comp. 90 1931-1955 (2021), /// DOI: [10.1090/mcom/3616](https://doi.org/10.1090/mcom/3616) pub fn is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &T) -> bool { - if num == &T::from(2u32) { + if num == &T::from_limb_like(Limb::from(2u32), num) { return true; } @@ -146,7 +154,7 @@ pub fn is_safe_prime_with_rng( // Since, by the definition of safe prime, `(num - 1) / 2` must also be prime, // and therefore odd, `num` has to be equal to 3 modulo 4. // 5 is the only exception, so we check for it. - if num == &T::from(5u32) { + if num == &T::from_limb_like(Limb::from(5u32), num) { return true; } if num.as_ref()[0].0 & 3 != 3 { @@ -184,7 +192,7 @@ fn _is_prime_with_rng(rng: &mut impl CryptoRngCore, num: #[cfg(test)] mod tests { - use crypto_bigint::{CheckedAdd, Uint, Word, U128, U64}; + use crypto_bigint::{BoxedUint, CheckedAdd, Uint, Word, U128, U64}; use num_prime::nt_funcs::is_prime64; use rand_core::OsRng; @@ -265,7 +273,16 @@ mod tests { #[test] fn prime_generation() { for bit_length in (28..=128).step_by(10) { - let p: U128 = generate_prime(bit_length); + let p: U128 = generate_prime(bit_length, U128::BITS); + assert!(p.bits_vartime() == bit_length); + assert!(is_prime(&p)); + } + } + + #[test] + fn prime_generation_boxed() { + for bit_length in (28..=128).step_by(10) { + let p: BoxedUint = generate_prime(bit_length, 128); assert!(p.bits_vartime() == bit_length); assert!(is_prime(&p)); } @@ -274,7 +291,16 @@ mod tests { #[test] fn safe_prime_generation() { for bit_length in (28..=128).step_by(10) { - let p: U128 = generate_safe_prime(bit_length); + let p: U128 = generate_safe_prime(bit_length, U128::BITS); + assert!(p.bits_vartime() == bit_length); + assert!(is_safe_prime(&p)); + } + } + + #[test] + fn safe_prime_generation_boxed() { + for bit_length in (28..=128).step_by(10) { + let p: BoxedUint = generate_safe_prime(bit_length, 128); assert!(p.bits_vartime() == bit_length); assert!(is_safe_prime(&p)); } @@ -307,29 +333,29 @@ mod tests { #[test] #[should_panic(expected = "`bit_length` must be 2 or greater")] fn generate_prime_too_few_bits() { - let _p: U64 = generate_prime_with_rng(&mut OsRng, 1); + let _p: U64 = generate_prime_with_rng(&mut OsRng, 1, U64::BITS); } #[test] #[should_panic(expected = "`bit_length` must be 3 or greater")] fn generate_safe_prime_too_few_bits() { - let _p: U64 = generate_safe_prime_with_rng(&mut OsRng, 2); + let _p: U64 = generate_safe_prime_with_rng(&mut OsRng, 2, U64::BITS); } #[test] #[should_panic( - expected = "try_random_bits() failed: BitLengthTooLarge { bit_length: 65, bits_precision: 64 }" + expected = "try_random_bits_with_precision() failed: BitLengthTooLarge { bit_length: 65, bits_precision: 64 }" )] fn generate_prime_too_many_bits() { - let _p: U64 = generate_prime_with_rng(&mut OsRng, 65); + let _p: U64 = generate_prime_with_rng(&mut OsRng, 65, U64::BITS); } #[test] #[should_panic( - expected = "try_random_bits() failed: BitLengthTooLarge { bit_length: 65, bits_precision: 64 }" + expected = "try_random_bits_with_precision() failed: BitLengthTooLarge { bit_length: 65, bits_precision: 64 }" )] fn generate_safe_prime_too_many_bits() { - let _p: U64 = generate_safe_prime_with_rng(&mut OsRng, 65); + let _p: U64 = generate_safe_prime_with_rng(&mut OsRng, 65, U64::BITS); } fn is_prime_ref(num: Word) -> bool { @@ -340,7 +366,7 @@ mod tests { fn corner_cases_generate_prime() { for bits in 2..5 { for _ in 0..100 { - let p: U64 = generate_prime(bits); + let p: U64 = generate_prime(bits, U64::BITS); let p_word = p.as_words()[0]; assert!(is_prime_ref(p_word)); } @@ -351,7 +377,7 @@ mod tests { fn corner_cases_generate_safe_prime() { for bits in 3..5 { for _ in 0..100 { - let p: U64 = generate_safe_prime(bits); + let p: U64 = generate_safe_prime(bits, U64::BITS); let p_word = p.as_words()[0]; assert!(is_prime_ref(p_word) && is_prime_ref(p_word / 2)); } @@ -390,7 +416,7 @@ mod tests_openssl { // Generate primes, let OpenSSL check them for _ in 0..100 { - let p: U128 = generate_prime(128); + let p: U128 = generate_prime(128, U128::BITS); let p_bn = to_openssl(&p); assert!( openssl_is_prime(&p_bn, &mut ctx), @@ -408,7 +434,8 @@ mod tests_openssl { // Generate random numbers, check if our test agrees with OpenSSL for _ in 0..100 { - let p = random_odd_integer::(&mut OsRng, NonZeroU32::new(128).unwrap()); + let p = + random_odd_integer::(&mut OsRng, NonZeroU32::new(128).unwrap(), U128::BITS); let actual = is_prime(p.as_ref()); let p_bn = to_openssl(&p); let expected = openssl_is_prime(&p_bn, &mut ctx); @@ -451,14 +478,15 @@ mod tests_gmp { fn gmp_cross_check() { // Generate primes, let GMP check them for _ in 0..100 { - let p: U128 = generate_prime(128); + let p: U128 = generate_prime(128, U128::BITS); let p_bn = to_gmp(&p); assert!(gmp_is_prime(&p_bn), "GMP reports {p} as composite"); } // Generate primes with GMP, check them for _ in 0..100 { - let start = random_odd_integer::(&mut OsRng, NonZeroU32::new(128).unwrap()); + let start = + random_odd_integer::(&mut OsRng, NonZeroU32::new(128).unwrap(), U128::BITS); let start_bn = to_gmp(&start); let p_bn = start_bn.next_prime(); let p = from_gmp(&p_bn); @@ -467,7 +495,8 @@ mod tests_gmp { // Generate random numbers, check if our test agrees with GMP for _ in 0..100 { - let p = random_odd_integer::(&mut OsRng, NonZeroU32::new(128).unwrap()); + let p = + random_odd_integer::(&mut OsRng, NonZeroU32::new(128).unwrap(), U128::BITS); let actual = is_prime(p.as_ref()); let p_bn = to_gmp(&p); let expected = gmp_is_prime(&p_bn); diff --git a/src/traits.rs b/src/traits.rs index 46e7c99..2aadd76 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -15,7 +15,11 @@ 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: u32) -> Self; + fn generate_prime_with_rng( + rng: &mut impl CryptoRngCore, + bit_length: u32, + bits_precision: u32, + ) -> 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,7 +28,11 @@ 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: u32) -> Self; + fn generate_safe_prime_with_rng( + rng: &mut impl CryptoRngCore, + bit_length: u32, + bits_precision: u32, + ) -> Self; /// Checks probabilistically if the given number is prime using the provided RNG. /// @@ -38,11 +46,19 @@ pub trait RandomPrimeWithRng { } impl RandomPrimeWithRng for T { - fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { - generate_prime_with_rng(rng, bit_length) + fn generate_prime_with_rng( + rng: &mut impl CryptoRngCore, + bit_length: u32, + bits_precision: u32, + ) -> Self { + generate_prime_with_rng(rng, bit_length, bits_precision) } - fn generate_safe_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { - generate_safe_prime_with_rng(rng, bit_length) + fn generate_safe_prime_with_rng( + rng: &mut impl CryptoRngCore, + bit_length: u32, + bits_precision: u32, + ) -> Self { + generate_safe_prime_with_rng(rng, bit_length, bits_precision) } fn is_prime_with_rng(&self, rng: &mut impl CryptoRngCore) -> bool { is_prime_with_rng(rng, self) @@ -67,9 +83,10 @@ mod tests { assert!(!U64::from(13u32).is_safe_prime_with_rng(&mut OsRng)); assert!(U64::from(11u32).is_safe_prime_with_rng(&mut OsRng)); - assert!(U64::generate_prime_with_rng(&mut OsRng, 10).is_prime_with_rng(&mut OsRng)); assert!( - U64::generate_safe_prime_with_rng(&mut OsRng, 10).is_safe_prime_with_rng(&mut OsRng) + U64::generate_prime_with_rng(&mut OsRng, 10, U64::BITS).is_prime_with_rng(&mut OsRng) ); + assert!(U64::generate_safe_prime_with_rng(&mut OsRng, 10, U64::BITS) + .is_safe_prime_with_rng(&mut OsRng)); } }