From fed515e65ff7eadc0a74905643e89e904f8dc8ae Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Tue, 28 Nov 2023 22:14:38 -0800 Subject: [PATCH 01/39] Bump crypto-bigint and start transitioning to traits --- Cargo.toml | 2 +- src/hazmat.rs | 14 ++--- src/hazmat/lucas.rs | 98 +++++++++++++++--------------- src/lib.rs | 21 ++++--- src/uint_traits.rs | 145 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 214 insertions(+), 66 deletions(-) create mode 100644 src/uint_traits.rs diff --git a/Cargo.toml b/Cargo.toml index 96fcd7b..7891571 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ categories = ["cryptography", "no-std"] rust-version = "1.65" [dependencies] -crypto-bigint = { version = "0.5.4", default-features = false, features = ["rand_core"] } +crypto-bigint = { version = "0.6.0-pre.0", 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/src/hazmat.rs b/src/hazmat.rs index f07e5b8..6d987c4 100644 --- a/src/hazmat.rs +++ b/src/hazmat.rs @@ -1,20 +1,20 @@ //! Components to build your own primality test. //! Handle with care. -mod gcd; -mod jacobi; +//mod gcd; +//mod jacobi; mod lucas; -mod miller_rabin; -mod precomputed; +//mod miller_rabin; +//mod precomputed; #[cfg(test)] pub(crate) mod primes; #[cfg(test)] pub(crate) mod pseudoprimes; -mod sieve; +//mod sieve; pub use lucas::{lucas_test, AStarBase, BruteForceBase, LucasBase, LucasCheck, SelfridgeBase}; -pub use miller_rabin::MillerRabin; -pub use sieve::{random_odd_uint, Sieve}; +//pub use miller_rabin::MillerRabin; +//pub use sieve::{random_odd_uint, Sieve}; /// Possible results of various primality tests. #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 69f0039..60b6f08 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -1,14 +1,8 @@ //! Lucas primality test. -use crypto_bigint::{ - modular::runtime_mod::{DynResidue, DynResidueParams}, - CheckedAdd, Integer, Limb, Uint, Word, -}; -use super::{ - gcd::gcd, - jacobi::{jacobi_symbol, JacobiSymbol}, - Primality, -}; +use crate::{JacobiSymbol, UintLike, UintModLike}; + +use super::Primality; /// The maximum number of attempts to find `D` such that `(D/n) == -1`. // This is widely believed to be impossible. @@ -30,7 +24,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: &T) -> Result<(u32, i32), Primality>; } /// "Method A" for selecting the base given in Baillie & Wagstaff[^Baillie1980], @@ -48,11 +42,8 @@ pub trait LucasBase { pub struct SelfridgeBase; impl LucasBase for SelfridgeBase { - fn generate(&self, n: &Uint) -> Result<(u32, i32), Primality> { + fn generate(&self, n: &T) -> 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 `&` - let small_n: u32 = (n.as_words()[0] & Word::from(u32::MAX)).try_into().unwrap(); let mut attempts = 0; loop { if attempts >= MAX_ATTEMPTS { @@ -66,7 +57,7 @@ impl LucasBase for SelfridgeBase { } } - let j = jacobi_symbol(d, n); + let j = T::jacobi_symbol_small(d, n); if j == JacobiSymbol::MinusOne { break; @@ -78,7 +69,7 @@ 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 != &T::from(d.abs_diff(0)) { return Err(Primality::Composite); } } @@ -108,7 +99,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: &T) -> Result<(u32, i32), Primality> { SelfridgeBase .generate(n) .map(|(p, q)| if q == -1 { (5, 5) } else { (p, q) }) @@ -127,7 +118,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: &T) -> Result<(u32, i32), Primality> { let mut p = 3_u32; let mut attempts = 0; @@ -144,7 +135,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 = T::jacobi_symbol_small((p * p - 4).try_into().unwrap(), n); if j == JacobiSymbol::MinusOne { break; @@ -155,7 +146,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 == &T::from(p + 2) { Primality::Prime } else { Primality::Composite @@ -172,7 +163,7 @@ 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) { +fn decompose(n: &T) -> (usize, T) { debug_assert!(bool::from(n.is_odd())); // Need to be careful here since `n + 1` can overflow. @@ -180,7 +171,7 @@ fn decompose(n: &Uint) -> (usize, Uint) { 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 = Option::from(n.shr(s).checked_add(&T::one())).expect("Integer overflow"); (s, d) } @@ -268,8 +259,8 @@ pub enum LucasCheck { /// Performs the primality test based on Lucas sequence. /// See [`LucasCheck`] for possible checks, and the implementors of [`LucasBase`] /// for the corresponding bases. -pub fn lucas_test( - candidate: &Uint, +pub fn lucas_test( + candidate: &T, base: impl LucasBase, check: LucasCheck, ) -> Primality { @@ -305,7 +296,7 @@ pub fn lucas_test( // 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) { + if abs_q != 1 && candidate.gcd_small(abs_q) != 1 && candidate > &T::from(abs_q) { return Primality::Composite; } @@ -315,19 +306,21 @@ pub fn lucas_test( // Some constants in Montgomery form - let params = DynResidueParams::::new(candidate); + // Can unwrap here since we already checked that `candidate` is odd + let params = + ::Modular::new_params(candidate).expect("Montgomery modulus must be odd"); - let zero = DynResidue::::zero(params); - let one = DynResidue::::one(params); - let two = one + one; - let minus_two = -two; + let zero = ::Modular::zero(¶ms); + let one = ::Modular::one(¶ms); + let two = one.clone() + &one; + let minus_two = -two.clone(); // Convert Q to Montgomery form let q = if q_is_one { - one + one.clone() } else { - let abs_q = DynResidue::::new(&Uint::::from(q.abs_diff(0)), params); + let abs_q = ::Modular::new(&T::from(q.abs_diff(0)), ¶ms); if q < 0 { -abs_q } else { @@ -338,9 +331,9 @@ pub fn lucas_test( // Convert P to Montgomery form let p = if p_is_one { - one + one.clone() } else { - DynResidue::::new(&Uint::::from(p), params) + ::Modular::new(&T::from(p), ¶ms) }; // Compute d-th element of Lucas sequence (U_d(P, Q), V_d(P, Q)), where: @@ -358,20 +351,20 @@ pub fn lucas_test( // We can therefore start with k=0 and build up to k=d in log2(d) steps. // Starting with k = 0 - let mut vk = two; // keeps V_k - let mut uk = DynResidue::::zero(params); // keeps U_k - let mut qk = one; // keeps Q^k + let mut vk = two.clone(); // keeps V_k + let mut uk = ::Modular::zero(¶ms); // keeps U_k + let mut qk = one.clone(); // 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 = ::Modular::new(&T::from(discriminant.abs_diff(0)), ¶ms); let d_m = if discriminant < 0 { -abs_d } else { abs_d }; for i in (0..d.bits_vartime()).rev() { // k' = k * 2 - let u_2k = uk * vk; - let v_2k = vk.square() - (qk + qk); let q_2k = qk.square(); + let u_2k = uk * &vk; + let v_2k = vk.square() - &(qk.clone() + &qk); uk = u_2k; vk = v_2k; @@ -380,11 +373,15 @@ pub fn lucas_test( if d.bit_vartime(i) { // k' = k + 1 - let (p_uk, p_vk) = if p_is_one { (uk, vk) } else { (p * uk, p * vk) }; + let (p_uk, p_vk) = if p_is_one { + (uk.clone(), vk.clone()) + } else { + (p.clone() * &uk, p.clone() * &vk) + }; - let u_k1 = (p_uk + vk).div_by_2(); - let v_k1 = (d_m * uk + p_vk).div_by_2(); - let q_k1 = qk * q; + let u_k1 = (p_uk + &vk).div_by_2(); + let v_k1 = (d_m.clone() * &uk + &p_vk).div_by_2(); + let q_k1 = qk * &q; uk = u_k1; vk = v_k1; @@ -442,7 +439,7 @@ pub fn lucas_test( // k' = 2k // V_{k'} = V_k^2 - 2 Q^k - vk = vk * vk - qk - qk; + vk = vk.square() - &qk - &qk; if check != LucasCheck::LucasV && vk == zero { return Primality::ProbablyPrime; @@ -456,10 +453,10 @@ pub fn lucas_test( if check == LucasCheck::LucasV { // At this point vk = V_{d * 2^(s-1)}. // Double the index again: - vk = vk * vk - qk - qk; // now vk = V_{d * 2^s} = V_{n+1} + vk = vk.square() - &qk - &qk; // now vk = V_{d * 2^s} = V_{n+1} // Lucas-V check[^Baillie2021]: if V_{n+1} == 2 Q, report `n` as prime. - if vk == q + q { + if vk == q.clone() + &q { return Primality::ProbablyPrime; } } @@ -480,7 +477,10 @@ mod tests { use super::{ decompose, lucas_test, AStarBase, BruteForceBase, LucasBase, LucasCheck, SelfridgeBase, }; - use crate::hazmat::{primes, pseudoprimes, Primality}; + use crate::{ + hazmat::{primes, pseudoprimes, Primality}, + UintLike, + }; #[test] fn bases_derived_traits() { @@ -534,7 +534,7 @@ mod tests { struct TestBase; impl LucasBase for TestBase { - fn generate(&self, _n: &Uint) -> Result<(u32, i32), Primality> { + fn generate(&self, _n: &T) -> Result<(u32, i32), Primality> { Ok((5, 5)) } } diff --git a/src/lib.rs b/src/lib.rs index f708159..0f8b8ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,14 +16,17 @@ extern crate alloc; pub mod hazmat; -mod presets; -mod traits; +//mod presets; +//mod traits; +mod uint_traits; -pub use presets::{ - generate_prime_with_rng, generate_safe_prime_with_rng, is_prime_with_rng, - is_safe_prime_with_rng, -}; -pub use traits::RandomPrimeWithRng; +//pub use presets::{ +// generate_prime_with_rng, generate_safe_prime_with_rng, is_prime_with_rng, +// is_safe_prime_with_rng, +//}; +//pub use traits::RandomPrimeWithRng; -#[cfg(feature = "default-rng")] -pub use presets::{generate_prime, generate_safe_prime, is_prime, is_safe_prime}; +//#[cfg(feature = "default-rng")] +//pub use presets::{generate_prime, generate_safe_prime, is_prime, is_safe_prime}; + +pub use uint_traits::{JacobiSymbol, UintLike, UintModLike}; diff --git a/src/uint_traits.rs b/src/uint_traits.rs new file mode 100644 index 0000000..9818fda --- /dev/null +++ b/src/uint_traits.rs @@ -0,0 +1,145 @@ +use core::ops::{Add, Mul, Neg, Sub}; + +use crypto_bigint::{ + modular::{DynResidue, DynResidueParams}, + subtle::{Choice, CtOption}, + CheckedAdd, Integer, Uint, Zero, +}; + +pub trait UintLike: + Clone + core::fmt::Debug + Eq + From + Ord + for<'a> CheckedAdd<&'a Self> + Zero +{ + type Modular: UintModLike; + + // We can get by with non-small versions of jacobi_symbol and gcd, they don't have a big impact + // on the performance. + fn jacobi_symbol_small(lhs: i32, rhs: &Self) -> JacobiSymbol; + fn gcd_small(&self, rhs: u32) -> u32; + fn bits_vartime(&self) -> usize; + fn bit_vartime(&self, index: usize) -> bool; + fn trailing_ones(&self) -> usize; + fn wrapping_mul(&self, rhs: &Self) -> Self; + fn sqrt_vartime(&self) -> Self; + fn is_even(&self) -> Choice; + fn is_odd(&self) -> Choice; + fn shr(&self, shift: usize) -> Self; + fn one() -> Self; +} + +pub trait UintModLike: + Clone + + Eq + + Sized + + for<'a> Add<&'a Self, Output = Self> + + for<'a> Sub<&'a Self, Output = Self> + + for<'a> Mul<&'a Self, Output = Self> + + Neg +{ + type Raw: UintLike; + type Params; + + fn new_params(modulus: &Self::Raw) -> CtOption; + fn new(raw: &Self::Raw, params: &Self::Params) -> Self; + + fn zero(params: &Self::Params) -> Self; + fn one(params: &Self::Params) -> Self; + fn square(&self) -> Self; + fn div_by_2(&self) -> Self; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum JacobiSymbol { + Zero, + One, + MinusOne, +} + +impl Neg for JacobiSymbol { + type Output = Self; + fn neg(self) -> Self { + match self { + Self::Zero => Self::Zero, + Self::One => Self::MinusOne, + Self::MinusOne => Self::One, + } + } +} + +// Uint impls + +impl UintLike for Uint { + type Modular = DynResidue; + + fn jacobi_symbol_small(lhs: i32, rhs: &Self) -> JacobiSymbol { + unimplemented!() + } + + fn gcd_small(&self, rhs: u32) -> u32 { + unimplemented!() + } + + fn trailing_ones(&self) -> usize { + Self::trailing_ones(self) + } + + fn wrapping_mul(&self, rhs: &Self) -> Self { + Self::wrapping_mul(self, rhs) + } + + fn bits_vartime(&self) -> usize { + Self::bits_vartime(self) + } + + fn bit_vartime(&self, index: usize) -> bool { + Self::bit_vartime(self, index) + } + + fn sqrt_vartime(&self) -> Self { + Self::sqrt_vartime(self) + } + + fn is_even(&self) -> Choice { + Integer::is_even(self) + } + + fn is_odd(&self) -> Choice { + Integer::is_odd(self) + } + + fn shr(&self, shift: usize) -> Self { + Self::shr(self, shift) + } + + fn one() -> Self { + Self::ONE + } +} + +impl UintModLike for DynResidue { + type Raw = Uint; + type Params = DynResidueParams; + + fn new_params(modulus: &Self::Raw) -> CtOption { + Self::Params::new(modulus) + } + + fn new(value: &Self::Raw, params: &Self::Params) -> Self { + Self::new(value, *params) + } + + fn zero(params: &Self::Params) -> Self { + Self::zero(*params) + } + + fn one(precomputed: &Self::Params) -> Self { + Self::one(*precomputed) + } + + fn square(&self) -> Self { + Self::square(self) + } + + fn div_by_2(&self) -> Self { + Self::div_by_2(self) + } +} From e5ee6bebc1e3d243ad47272f7042ecb071bafc52 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Tue, 28 Nov 2023 22:49:00 -0800 Subject: [PATCH 02/39] Convert `sieve` --- src/hazmat.rs | 4 +-- src/hazmat/sieve.rs | 75 ++++++++++++++++++++++++--------------------- src/uint_traits.rs | 51 ++++++++++++++++++++++++++++-- 3 files changed, 90 insertions(+), 40 deletions(-) diff --git a/src/hazmat.rs b/src/hazmat.rs index 6d987c4..817b84e 100644 --- a/src/hazmat.rs +++ b/src/hazmat.rs @@ -5,12 +5,12 @@ //mod jacobi; mod lucas; //mod miller_rabin; -//mod precomputed; +mod precomputed; #[cfg(test)] pub(crate) mod primes; #[cfg(test)] pub(crate) mod pseudoprimes; -//mod sieve; +mod sieve; pub use lucas::{lucas_test, AStarBase, BruteForceBase, LucasBase, LucasCheck, SelfridgeBase}; //pub use miller_rabin::MillerRabin; diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 7b29625..bc720e5 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -3,38 +3,39 @@ use alloc::{vec, vec::Vec}; -use crypto_bigint::{CheckedAdd, Random, Uint}; use rand_core::CryptoRngCore; -use crate::hazmat::precomputed::{SmallPrime, RECIPROCALS, SMALL_PRIMES}; +use crate::{ + hazmat::precomputed::{SmallPrime, RECIPROCALS, SMALL_PRIMES}, + UintLike, +}; /// Returns a random odd integer with given bit length /// (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: usize) -> T { if bit_length == 0 { panic!("Bit length must be non-zero"); } - if bit_length > Uint::::BITS { + // TODO: what do we do here if `bit_length` is greater than Uint::BITS? + // assume that the user knows what he's doing since it is a hazmat function? + /*if bit_length > Uint::::BITS { panic!( "The requested bit length ({}) is larger than the chosen Uint size", bit_length ); - } + }*/ // TODO: not particularly efficient, can be improved by zeroing high bits instead of shifting - let mut random = Uint::::random(rng); - if bit_length != Uint::::BITS { - random >>= Uint::::BITS - bit_length; - } + let random = T::random_bits(rng, bit_length); // Make it odd - random |= Uint::::ONE; + let random = random | T::one(); // Make sure it's the correct bit size - random |= Uint::::ONE << (bit_length - 1); + let random = random | T::one().shr(bit_length - 1); random } @@ -50,11 +51,11 @@ const INCR_LIMIT: Residue = Residue::MAX - SMALL_PRIMES[SMALL_PRIMES.len() - 1] /// An iterator returning numbers with up to and including given bit length, /// starting from a given number, that are not multiples of the first 2048 small primes. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct Sieve { - // Instead of dividing `Uint` by small primes every time (which is slow), +pub struct Sieve { + // Instead of dividing a big integer by small primes every time (which is slow), // we keep a "base" and a small increment separately, // so that we can only calculate the residues of the increment. - base: Uint, + base: T, incr: Residue, incr_limit: Residue, safe_primes: bool, @@ -65,7 +66,7 @@ pub struct Sieve { last_round: bool, } -impl Sieve { +impl Sieve { /// Creates a new sieve, iterating from `start` and /// until the last number with `max_bit_length` bits, /// producing numbers that are not non-trivial multiples @@ -78,50 +79,52 @@ 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: &T, max_bit_length: usize, safe_primes: bool) -> Self { if max_bit_length == 0 { panic!("The requested bit length cannot be zero"); } - if max_bit_length > Uint::::BITS { + // TODO: what do we do here if `bit_length` is greater than Uint::BITS? + // assume that the user knows what he's doing since it is a hazmat function? + /*if max_bit_length > Uint::::BITS { panic!( "The requested bit length ({}) is larger than the chosen Uint size", max_bit_length ); - } + }*/ // 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.shr_vartime(1)) } else { - (max_bit_length, *start) + (max_bit_length, start.clone()) }; let mut base = base; // This is easier than making all the methods generic enough to handle these corner cases. - let produces_nothing = max_bit_length < base.bits() || max_bit_length < 2; + let produces_nothing = max_bit_length < base.bits_vartime() || max_bit_length < 2; // Add the exception to the produced candidates - the only one that doesn't fit // the general pattern of incrementing the base by 2. let mut starts_from_exception = false; - if base <= Uint::::from(2u32) { + if base <= T::from(2u32) { starts_from_exception = true; - base = Uint::::from(3u32); + base = T::from(3u32); } else { // Adjust the base so that we hit odd numbers when incrementing it by 2. - base |= Uint::::ONE; + base |= T::one(); } // Only calculate residues by primes up to and not including `base`, // because when we only have the resiude, // we cannot distinguish between a prime itself and a multiple of that prime. - let residues_len = if Uint::::from(SMALL_PRIMES[SMALL_PRIMES.len() - 1]) >= base { + let residues_len = if T::from(SMALL_PRIMES[SMALL_PRIMES.len() - 1]) >= base { SMALL_PRIMES .iter() .enumerate() - .find(|(_i, p)| Uint::::from(**p) >= base) + .find(|(_i, p)| T::from(**p) >= base) .map(|(i, _p)| i) .unwrap_or(SMALL_PRIMES.len()) } else { @@ -167,7 +170,9 @@ impl Sieve { } // Find the increment limit. - let max_value = (Uint::::ONE << self.max_bit_length).wrapping_sub(&Uint::::ONE); + let max_value = T::one() + .shl_vartime(self.max_bit_length) + .wrapping_sub(&T::one()); let incr_limit = max_value.wrapping_sub(&self.base); self.incr_limit = if incr_limit > INCR_LIMIT.into() { INCR_LIMIT @@ -177,7 +182,7 @@ 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.try_into_u32().unwrap(); incr_limit_small }; @@ -210,17 +215,17 @@ impl Sieve { // Returns the restored `base + incr` if it is not composite (wrt the small primes), // and bumps the increment unconditionally. - fn maybe_next(&mut self) -> Option> { + fn maybe_next(&mut self) -> Option { let result = if self.current_is_composite() { None } else { // 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: T = Option::from(self.base.checked_add(&self.incr.into())).expect("Integer overflow"); if self.safe_primes { - num = (num << 1) | Uint::::ONE; + num = num.shl_vartime(1) | T::one(); } Some(num) }; @@ -229,7 +234,7 @@ impl Sieve { result } - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option { // Corner cases handled here if self.produces_nothing { @@ -238,7 +243,7 @@ impl Sieve { if self.starts_from_exception { self.starts_from_exception = false; - return Some(Uint::::from(if self.safe_primes { 5u32 } else { 2u32 })); + return Some(T::from(if self.safe_primes { 5u32 } else { 2u32 })); } // Main loop @@ -253,8 +258,8 @@ impl Sieve { } } -impl Iterator for Sieve { - type Item = Uint; +impl Iterator for Sieve { + type Item = T; fn next(&mut self) -> Option { Self::next(self) diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 9818fda..b9b1c9a 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -1,13 +1,23 @@ -use core::ops::{Add, Mul, Neg, Sub}; +use core::ops::{Add, BitOr, BitOrAssign, Mul, Neg, Sub}; use crypto_bigint::{ modular::{DynResidue, DynResidueParams}, subtle::{Choice, CtOption}, - CheckedAdd, Integer, Uint, Zero, + CheckedAdd, Integer, Limb, Reciprocal, Uint, Zero, }; +use rand_core::CryptoRngCore; pub trait UintLike: - Clone + core::fmt::Debug + Eq + From + Ord + for<'a> CheckedAdd<&'a Self> + Zero + Clone + + core::fmt::Debug + + Eq + + From + + From + + Ord + + for<'a> CheckedAdd<&'a Self> + + Zero + + BitOr + + BitOrAssign { type Modular: UintModLike; @@ -18,12 +28,19 @@ pub trait UintLike: fn bits_vartime(&self) -> usize; fn bit_vartime(&self, index: usize) -> bool; fn trailing_ones(&self) -> usize; + fn wrapping_sub(&self, rhs: &Self) -> Self; fn wrapping_mul(&self, rhs: &Self) -> Self; fn sqrt_vartime(&self) -> Self; fn is_even(&self) -> Choice; fn is_odd(&self) -> Choice; fn shr(&self, shift: usize) -> Self; + fn shr_vartime(&self, shift: usize) -> Self; + fn shl(&self, shift: usize) -> Self; + fn shl_vartime(&self, shift: usize) -> Self; fn one() -> Self; + fn random_bits(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self; + fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb); + fn try_into_u32(&self) -> Option; // Will have to be implemented at Uint level if we want to use TryFrom trait } pub trait UintModLike: @@ -82,6 +99,10 @@ impl UintLike for Uint { Self::trailing_ones(self) } + fn wrapping_sub(&self, rhs: &Self) -> Self { + Self::wrapping_sub(self, rhs) + } + fn wrapping_mul(&self, rhs: &Self) -> Self { Self::wrapping_mul(self, rhs) } @@ -110,9 +131,33 @@ impl UintLike for Uint { Self::shr(self, shift) } + fn shr_vartime(&self, shift: usize) -> Self { + Self::shr_vartime(self, shift) + } + + fn shl(&self, shift: usize) -> Self { + Self::shl(self, shift) + } + + fn shl_vartime(&self, shift: usize) -> Self { + Self::shl_vartime(self, shift) + } + fn one() -> Self { Self::ONE } + + fn random_bits(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self { + unimplemented!() + } + + fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb) { + Self::ct_div_rem_limb_with_reciprocal(self, reciprocal) + } + + fn try_into_u32(&self) -> Option { + self.as_words()[0].try_into().ok() + } } impl UintModLike for DynResidue { From d5fff443f057930879193680e9825bb6c1e5db52 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Tue, 28 Nov 2023 22:54:46 -0800 Subject: [PATCH 03/39] Convert `miller_rabin` --- src/hazmat.rs | 6 ++-- src/hazmat/miller_rabin.rs | 62 +++++++++++++++++++------------------- src/uint_traits.rs | 19 ++++++++++-- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/src/hazmat.rs b/src/hazmat.rs index 817b84e..7df8174 100644 --- a/src/hazmat.rs +++ b/src/hazmat.rs @@ -4,7 +4,7 @@ //mod gcd; //mod jacobi; mod lucas; -//mod miller_rabin; +mod miller_rabin; mod precomputed; #[cfg(test)] pub(crate) mod primes; @@ -13,8 +13,8 @@ pub(crate) mod pseudoprimes; mod sieve; pub use lucas::{lucas_test, AStarBase, BruteForceBase, LucasBase, LucasCheck, SelfridgeBase}; -//pub use miller_rabin::MillerRabin; -//pub use sieve::{random_odd_uint, Sieve}; +pub use miller_rabin::MillerRabin; +pub use sieve::{random_odd_uint, Sieve}; /// Possible results of various primality tests. #[derive(Copy, Clone, Debug, PartialEq, Eq)] diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 29c401a..79b4ec6 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -1,13 +1,10 @@ //! Miller-Rabin primality test. +use crypto_bigint::{NonZero, PowBoundedExp}; use rand_core::CryptoRngCore; -use crypto_bigint::{ - modular::runtime_mod::{DynResidue, DynResidueParams}, - CheckedAdd, Integer, NonZero, RandomMod, Uint, -}; - use super::Primality; +use crate::{UintLike, UintModLike}; /// Precomputed data used to perform Miller-Rabin primality test[^Pomerance1980]. /// The numbers that pass it are commonly called "strong probable primes" @@ -17,37 +14,39 @@ use super::Primality; /// C. Pomerance, J. L. Selfridge, S. S. Wagstaff "The Pseudoprimes to 25*10^9", /// Math. Comp. 35 1003-1026 (1980), /// DOI: [10.2307/2006210](https://dx.doi.org/10.2307/2006210) -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct MillerRabin { - candidate: Uint, +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MillerRabin { + candidate: T, bit_length: usize, - montgomery_params: DynResidueParams, - one: DynResidue, - minus_one: DynResidue, + montgomery_params: ::Params, + one: T::Modular, + minus_one: T::Modular, s: usize, - d: Uint, + d: T, } -impl MillerRabin { +impl MillerRabin { /// Initializes a Miller-Rabin test for `candidate`. /// /// Panics if `candidate` is even. - pub fn new(candidate: &Uint) -> Self { + pub fn new(candidate: &T) -> Self { if candidate.is_even().into() { panic!("`candidate` must be odd."); } - let params = DynResidueParams::::new(candidate); - let one = DynResidue::::one(params); - let minus_one = -one; + // Can unwrap here since we already checked that `candidate` is odd + let params = ::new_params(candidate) + .expect("Montgomery modulus must be odd"); + let one = ::one(¶ms); + let minus_one = -one.clone(); // Find `s` and odd `d` such that `candidate - 1 == 2^s * d`. - let candidate_minus_one = candidate.wrapping_sub(&Uint::::ONE); + let candidate_minus_one = candidate.wrapping_sub(&T::one()); let s = candidate_minus_one.trailing_zeros(); - let d = candidate_minus_one >> s; + let d = candidate_minus_one.shr(s); Self { - candidate: *candidate, + candidate: candidate.clone(), bit_length: candidate.bits_vartime(), montgomery_params: params, one, @@ -58,11 +57,11 @@ impl MillerRabin { } /// Perform a Miller-Rabin check with a given base. - pub fn test(&self, base: &Uint) -> Primality { + pub fn test(&self, base: &T) -> Primality { // 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 = ::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. @@ -87,7 +86,7 @@ impl MillerRabin { /// Perform a Miller-Rabin check with base 2. pub fn test_base_two(&self) -> Primality { - self.test(&Uint::::from(2u32)) + self.test(&T::from(2u32)) } /// Perform a Miller-Rabin check with a random base (in the range `[3, candidate-2]`) @@ -102,14 +101,12 @@ impl MillerRabin { panic!("No suitable random base possible when `candidate == 3`; use the base 2 test.") } - let range = self.candidate.wrapping_sub(&Uint::::from(4u32)); + let range = self.candidate.wrapping_sub(&T::from(4u32)); let range_nonzero = NonZero::new(range).unwrap(); // 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 = Option::from(T::random_mod(rng, &range_nonzero).checked_add(&T::from(3u32))) + .expect("Integer overflow"); self.test(&random) } } @@ -127,7 +124,10 @@ mod tests { use num_prime::nt_funcs::is_prime64; use super::MillerRabin; - use crate::hazmat::{primes, pseudoprimes, random_odd_uint, Sieve}; + use crate::{ + hazmat::{primes, pseudoprimes, random_odd_uint, Sieve}, + UintLike, + }; #[test] fn miller_rabin_derived_traits() { @@ -155,9 +155,9 @@ mod tests { pseudoprimes::STRONG_BASE_2.iter().any(|x| *x == num) } - fn random_checks( + fn random_checks( rng: &mut impl CryptoRngCore, - mr: &MillerRabin, + mr: &MillerRabin, count: usize, ) -> usize { (0..count) diff --git a/src/uint_traits.rs b/src/uint_traits.rs index b9b1c9a..eec64f4 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -3,7 +3,7 @@ use core::ops::{Add, BitOr, BitOrAssign, Mul, Neg, Sub}; use crypto_bigint::{ modular::{DynResidue, DynResidueParams}, subtle::{Choice, CtOption}, - CheckedAdd, Integer, Limb, Reciprocal, Uint, Zero, + CheckedAdd, Integer, Limb, PowBoundedExp, RandomMod, Reciprocal, Uint, Zero, }; use rand_core::CryptoRngCore; @@ -18,6 +18,7 @@ pub trait UintLike: + Zero + BitOr + BitOrAssign + + RandomMod { type Modular: UintModLike; @@ -25,8 +26,10 @@ pub trait UintLike: // on the performance. fn jacobi_symbol_small(lhs: i32, rhs: &Self) -> JacobiSymbol; fn gcd_small(&self, rhs: u32) -> u32; + fn bits(&self) -> usize; fn bits_vartime(&self) -> usize; fn bit_vartime(&self, index: usize) -> bool; + fn trailing_zeros(&self) -> usize; fn trailing_ones(&self) -> usize; fn wrapping_sub(&self, rhs: &Self) -> Self; fn wrapping_mul(&self, rhs: &Self) -> Self; @@ -44,16 +47,18 @@ pub trait UintLike: } pub trait UintModLike: - Clone + core::fmt::Debug + + Clone + Eq + Sized + for<'a> Add<&'a Self, Output = Self> + for<'a> Sub<&'a Self, Output = Self> + for<'a> Mul<&'a Self, Output = Self> + Neg + + PowBoundedExp { type Raw: UintLike; - type Params; + type Params: Clone + Eq + core::fmt::Debug; fn new_params(modulus: &Self::Raw) -> CtOption; fn new(raw: &Self::Raw, params: &Self::Params) -> Self; @@ -95,6 +100,10 @@ impl UintLike for Uint { unimplemented!() } + fn trailing_zeros(&self) -> usize { + Self::trailing_zeros(self) + } + fn trailing_ones(&self) -> usize { Self::trailing_ones(self) } @@ -107,6 +116,10 @@ impl UintLike for Uint { Self::wrapping_mul(self, rhs) } + fn bits(&self) -> usize { + Self::bits(self) + } + fn bits_vartime(&self) -> usize { Self::bits_vartime(self) } From 06325144b0200b3af766fc50960b259f849b1dd0 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Tue, 28 Nov 2023 23:01:12 -0800 Subject: [PATCH 04/39] Convert `presets` --- src/lib.rs | 2 +- src/presets.rs | 57 +++++++++++++++++++++------------------------- src/uint_traits.rs | 3 ++- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0f8b8ea..8d0c72f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ extern crate alloc; pub mod hazmat; -//mod presets; +mod presets; //mod traits; mod uint_traits; diff --git a/src/presets.rs b/src/presets.rs index 50eada1..2ddc496 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -1,4 +1,3 @@ -use crypto_bigint::{Integer, Uint}; use rand_core::CryptoRngCore; #[cfg(feature = "default-rng")] @@ -7,13 +6,14 @@ use rand_core::OsRng; use crate::hazmat::{ lucas_test, random_odd_uint, AStarBase, LucasCheck, MillerRabin, Primality, Sieve, }; +use crate::UintLike; /// Returns a random prime of size `bit_length` using [`OsRng`] as the RNG. /// If `bit_length` is `None`, the full size of `Uint` is used. /// /// 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: usize) -> T { 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: usize) -> T { generate_safe_prime_with_rng(&mut OsRng, bit_length) } @@ -31,7 +31,7 @@ pub fn generate_safe_prime(bit_length: Option) -> Uint /// /// See [`is_prime_with_rng`] for details about the performed checks. #[cfg(feature = "default-rng")] -pub fn is_prime(num: &Uint) -> bool { +pub fn is_prime(num: &T) -> bool { is_prime_with_rng(&mut OsRng, num) } @@ -41,7 +41,7 @@ pub fn is_prime(num: &Uint) -> bool { /// /// See [`is_prime_with_rng`] for details about the performed checks. #[cfg(feature = "default-rng")] -pub fn is_safe_prime(num: &Uint) -> bool { +pub fn is_safe_prime(num: &T) -> bool { is_safe_prime_with_rng(&mut OsRng, num) } @@ -51,16 +51,12 @@ pub fn is_safe_prime(num: &Uint) -> bool { /// 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. -pub fn generate_prime_with_rng( - rng: &mut impl CryptoRngCore, - bit_length: Option, -) -> Uint { - let bit_length = bit_length.unwrap_or(Uint::::BITS); +pub fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: usize) -> T { 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) { @@ -77,16 +73,15 @@ pub fn generate_prime_with_rng( /// Panics if `bit_length` is less than 3, or is greater than the bit size of the target `Uint`. /// /// See [`is_prime_with_rng`] for details about the performed checks. -pub fn generate_safe_prime_with_rng( +pub fn generate_safe_prime_with_rng( rng: &mut impl CryptoRngCore, - bit_length: Option, -) -> Uint { - let bit_length = bit_length.unwrap_or(Uint::::BITS); + bit_length: usize, +) -> T { 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) { @@ -121,8 +116,8 @@ pub fn generate_safe_prime_with_rng( /// "Strengthening the Baillie-PSW primality test", /// 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: &Uint) -> bool { - if num == &Uint::::from(2u32) { +pub fn is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &T) -> bool { + if num == &T::from(2u32) { return true; } if num.is_even().into() { @@ -135,22 +130,22 @@ pub fn is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &Uin /// Checks probabilistically if the given number is a safe prime using the provided RNG. /// /// See [`is_prime_with_rng`] for details about the performed checks. -pub fn is_safe_prime_with_rng(rng: &mut impl CryptoRngCore, num: &Uint) -> bool { +pub fn is_safe_prime_with_rng(rng: &mut impl CryptoRngCore, num: &T) -> bool { // 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 == &Uint::::from(5u32) { + if num == &T::from(5u32) { return true; } - if num.as_words()[0] & 3 != 3 { + if T::from(3u32) & num != T::from(3u32) { return false; } - _is_prime_with_rng(rng, num) && _is_prime_with_rng(rng, &(num >> 1)) + _is_prime_with_rng(rng, num) && _is_prime_with_rng(rng, &num.shr_vartime(1)) } /// Checks for primality assuming that `num` is odd. -fn _is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &Uint) -> bool { +fn _is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &T) -> bool { debug_assert!(bool::from(num.is_odd())); let mr = MillerRabin::new(num); @@ -252,7 +247,7 @@ mod tests { #[test] fn prime_generation() { for bit_length in (28..=128).step_by(10) { - let p: U128 = generate_prime(Some(bit_length)); + let p: U128 = generate_prime(bit_length); assert!(p.bits_vartime() == bit_length); assert!(is_prime(&p)); } @@ -261,7 +256,7 @@ mod tests { #[test] fn safe_prime_generation() { for bit_length in (28..=128).step_by(10) { - let p: U128 = generate_safe_prime(Some(bit_length)); + let p: U128 = generate_safe_prime(bit_length); assert!(p.bits_vartime() == bit_length); assert!(is_safe_prime(&p)); } @@ -294,25 +289,25 @@ 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, Some(1)); + let _p: U64 = generate_prime_with_rng(&mut OsRng, 1); } #[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, Some(2)); + let _p: U64 = generate_safe_prime_with_rng(&mut OsRng, 2); } #[test] #[should_panic(expected = "The requested bit length (65) is larger than the chosen Uint size")] fn generate_prime_too_many_bits() { - let _p: U64 = generate_prime_with_rng(&mut OsRng, Some(65)); + let _p: U64 = generate_prime_with_rng(&mut OsRng, 65); } #[test] #[should_panic(expected = "The requested bit length (65) is larger than the chosen Uint size")] fn generate_safe_prime_too_many_bits() { - let _p: U64 = generate_safe_prime_with_rng(&mut OsRng, Some(65)); + let _p: U64 = generate_safe_prime_with_rng(&mut OsRng, 65); } fn is_prime_ref(num: Word) -> bool { @@ -323,7 +318,7 @@ mod tests { fn corner_cases_generate_prime() { for bits in 2usize..5 { for _ in 0..100 { - let p: U64 = generate_prime(Some(bits)); + let p: U64 = generate_prime(bits); let p_word = p.as_words()[0]; assert!(is_prime_ref(p_word)); } @@ -334,7 +329,7 @@ mod tests { fn corner_cases_generate_safe_prime() { for bits in 3usize..5 { for _ in 0..100 { - let p: U64 = generate_safe_prime(Some(bits)); + let p: U64 = generate_safe_prime(bits); let p_word = p.as_words()[0]; assert!(is_prime_ref(p_word) && is_prime_ref(p_word / 2)); } diff --git a/src/uint_traits.rs b/src/uint_traits.rs index eec64f4..7c35b38 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -1,4 +1,4 @@ -use core::ops::{Add, BitOr, BitOrAssign, Mul, Neg, Sub}; +use core::ops::{Add, BitAnd, BitOr, BitOrAssign, Mul, Neg, Sub}; use crypto_bigint::{ modular::{DynResidue, DynResidueParams}, @@ -17,6 +17,7 @@ pub trait UintLike: + for<'a> CheckedAdd<&'a Self> + Zero + BitOr + + for<'a> BitAnd<&'a Self, Output = Self> + BitOrAssign + RandomMod { From 8041216e10c8341309c99e62214d3a203a9d267f Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Tue, 28 Nov 2023 23:02:17 -0800 Subject: [PATCH 05/39] Convert `traits` --- src/lib.rs | 16 ++++++++-------- src/traits.rs | 21 ++++++++------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8d0c72f..8362d9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,16 +17,16 @@ extern crate alloc; pub mod hazmat; mod presets; -//mod traits; +mod traits; mod uint_traits; -//pub use presets::{ -// generate_prime_with_rng, generate_safe_prime_with_rng, is_prime_with_rng, -// is_safe_prime_with_rng, -//}; -//pub use traits::RandomPrimeWithRng; +pub use presets::{ + generate_prime_with_rng, generate_safe_prime_with_rng, is_prime_with_rng, + is_safe_prime_with_rng, +}; +pub use traits::RandomPrimeWithRng; -//#[cfg(feature = "default-rng")] -//pub use presets::{generate_prime, generate_safe_prime, is_prime, is_safe_prime}; +#[cfg(feature = "default-rng")] +pub use presets::{generate_prime, generate_safe_prime, is_prime, is_safe_prime}; pub use uint_traits::{JacobiSymbol, UintLike, UintModLike}; diff --git a/src/traits.rs b/src/traits.rs index c36c11d..632411b 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: usize) -> 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: usize) -> 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: usize) -> 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: usize) -> Self { generate_safe_prime_with_rng(rng, bit_length) } fn is_prime_with_rng(&self, rng: &mut impl CryptoRngCore) -> bool { @@ -73,8 +67,9 @@ 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, Some(10)).is_prime_with_rng(&mut OsRng)); - assert!(U64::generate_safe_prime_with_rng(&mut OsRng, Some(10)) - .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) + ); } } From bf44bb5cd17c5d37c03a4072a00cfa42370395ec Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Wed, 29 Nov 2023 21:02:55 -0800 Subject: [PATCH 06/39] Updates for the crypto-bigint@bcb41ff42b0eae91440d60d67c9173e8f2280de5 --- src/hazmat/lucas.rs | 2 +- src/hazmat/sieve.rs | 2 +- src/presets.rs | 2 +- src/uint_traits.rs | 42 +++++------------------------------------- 4 files changed, 8 insertions(+), 40 deletions(-) diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 60b6f08..271f076 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -171,7 +171,7 @@ fn decompose(n: &T) -> (usize, T) { 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.shr(s).checked_add(&T::one())).expect("Integer overflow"); + let d = Option::from((n.clone() >> s).checked_add(&T::one())).expect("Integer overflow"); (s, d) } diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index bc720e5..aaa2baf 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -114,7 +114,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 = base | T::one(); } // Only calculate residues by primes up to and not including `base`, diff --git a/src/presets.rs b/src/presets.rs index 2ddc496..aed5ab0 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -137,7 +137,7 @@ pub fn is_safe_prime_with_rng(rng: &mut impl CryptoRngCore, num: &T if num == &T::from(5u32) { return true; } - if T::from(3u32) & num != T::from(3u32) { + if T::from(3u32) & num.clone() != T::from(3u32) { return false; } diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 7c35b38..b28db3a 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -1,24 +1,17 @@ -use core::ops::{Add, BitAnd, BitOr, BitOrAssign, Mul, Neg, Sub}; +use core::ops::{Add, Mul, Neg, Sub}; use crypto_bigint::{ modular::{DynResidue, DynResidueParams}, - subtle::{Choice, CtOption}, - CheckedAdd, Integer, Limb, PowBoundedExp, RandomMod, Reciprocal, Uint, Zero, + subtle::{CtOption}, + Integer, Limb, PowBoundedExp, RandomMod, Reciprocal, Uint, }; use rand_core::CryptoRngCore; +// would be nice to have: *Assign traits; arithmetic traits for &self (BitAnd and Shr in particular); pub trait UintLike: - Clone - + core::fmt::Debug - + Eq + Integer + From + From - + Ord - + for<'a> CheckedAdd<&'a Self> - + Zero - + BitOr - + for<'a> BitAnd<&'a Self, Output = Self> - + BitOrAssign + RandomMod { type Modular: UintModLike; @@ -35,13 +28,8 @@ pub trait UintLike: fn wrapping_sub(&self, rhs: &Self) -> Self; fn wrapping_mul(&self, rhs: &Self) -> Self; fn sqrt_vartime(&self) -> Self; - fn is_even(&self) -> Choice; - fn is_odd(&self) -> Choice; - fn shr(&self, shift: usize) -> Self; fn shr_vartime(&self, shift: usize) -> Self; - fn shl(&self, shift: usize) -> Self; fn shl_vartime(&self, shift: usize) -> Self; - fn one() -> Self; fn random_bits(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self; fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb); fn try_into_u32(&self) -> Option; // Will have to be implemented at Uint level if we want to use TryFrom trait @@ -133,34 +121,14 @@ impl UintLike for Uint { Self::sqrt_vartime(self) } - fn is_even(&self) -> Choice { - Integer::is_even(self) - } - - fn is_odd(&self) -> Choice { - Integer::is_odd(self) - } - - fn shr(&self, shift: usize) -> Self { - Self::shr(self, shift) - } - fn shr_vartime(&self, shift: usize) -> Self { Self::shr_vartime(self, shift) } - fn shl(&self, shift: usize) -> Self { - Self::shl(self, shift) - } - fn shl_vartime(&self, shift: usize) -> Self { Self::shl_vartime(self, shift) } - fn one() -> Self { - Self::ONE - } - fn random_bits(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self { unimplemented!() } From 6d5e81a50c7093a128cd67ce4cfb18f42151e7bc Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 16:08:18 -0500 Subject: [PATCH 07/39] Bit count uses u32 instead of usize --- src/hazmat/lucas.rs | 2 +- src/hazmat/miller_rabin.rs | 4 ++-- src/hazmat/sieve.rs | 8 ++++---- src/presets.rs | 14 ++++++------- src/traits.rs | 8 ++++---- src/uint_traits.rs | 41 +++++++++++++++++--------------------- 6 files changed, 36 insertions(+), 41 deletions(-) diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 271f076..bd0f880 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -163,7 +163,7 @@ impl LucasBase for BruteForceBase { } /// For the given odd `n`, finds `s` and odd `d` such that `n + 1 == 2^s * d`. -fn decompose(n: &T) -> (usize, T) { +fn decompose(n: &T) -> (u32, T) { debug_assert!(bool::from(n.is_odd())); // Need to be careful here since `n + 1` can overflow. diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 79b4ec6..43f01e0 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -17,11 +17,11 @@ use crate::{UintLike, UintModLike}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct MillerRabin { candidate: T, - bit_length: usize, + bit_length: u32, montgomery_params: ::Params, one: T::Modular, minus_one: T::Modular, - s: usize, + s: u32, d: T, } diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index aaa2baf..e1f3342 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -14,7 +14,7 @@ use crate::{ /// (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) -> T { +pub fn random_odd_uint(rng: &mut impl CryptoRngCore, bit_length: u32) -> T { if bit_length == 0 { panic!("Bit length must be non-zero"); } @@ -60,7 +60,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, @@ -79,7 +79,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: &T, max_bit_length: usize, safe_primes: bool) -> Self { + pub fn new(start: &T, max_bit_length: u32, safe_primes: bool) -> Self { if max_bit_length == 0 { panic!("The requested bit length cannot be zero"); } @@ -297,7 +297,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()) { diff --git a/src/presets.rs b/src/presets.rs index aed5ab0..6f92477 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -13,7 +13,7 @@ use crate::UintLike; /// /// See [`is_prime_with_rng`] for details about the performed checks. #[cfg(feature = "default-rng")] -pub fn generate_prime(bit_length: usize) -> T { +pub fn generate_prime(bit_length: u32) -> T { generate_prime_with_rng(&mut OsRng, bit_length) } @@ -23,7 +23,7 @@ pub fn generate_prime(bit_length: usize) -> T { /// /// See [`is_prime_with_rng`] for details about the performed checks. #[cfg(feature = "default-rng")] -pub fn generate_safe_prime(bit_length: usize) -> T { +pub fn generate_safe_prime(bit_length: u32) -> T { generate_safe_prime_with_rng(&mut OsRng, bit_length) } @@ -51,7 +51,7 @@ pub fn is_safe_prime(num: &T) -> bool { /// 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. -pub fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: usize) -> T { +pub fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: u32) -> T { if bit_length < 2 { panic!("`bit_length` must be 2 or greater."); } @@ -75,7 +75,7 @@ pub fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_le /// See [`is_prime_with_rng`] for details about the performed checks. pub fn generate_safe_prime_with_rng( rng: &mut impl CryptoRngCore, - bit_length: usize, + bit_length: u32, ) -> T { if bit_length < 3 { panic!("`bit_length` must be 3 or greater."); @@ -246,7 +246,7 @@ mod tests { #[test] fn prime_generation() { - for bit_length in (28..=128).step_by(10) { + for bit_length in (28u32..=128).step_by(10) { let p: U128 = generate_prime(bit_length); assert!(p.bits_vartime() == bit_length); assert!(is_prime(&p)); @@ -316,7 +316,7 @@ mod tests { #[test] fn corner_cases_generate_prime() { - for bits in 2usize..5 { + for bits in 2u32..5 { for _ in 0..100 { let p: U64 = generate_prime(bits); let p_word = p.as_words()[0]; @@ -327,7 +327,7 @@ mod tests { #[test] fn corner_cases_generate_safe_prime() { - for bits in 3usize..5 { + for bits in 3u32..5 { for _ in 0..100 { let p: U64 = generate_safe_prime(bits); let p_word = p.as_words()[0]; diff --git a/src/traits.rs b/src/traits.rs index 632411b..458d85c 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: usize) -> Self; + fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: 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 +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: usize) -> Self; + fn generate_safe_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self; /// Checks probabilistically if the given number is prime using the provided RNG. /// @@ -38,10 +38,10 @@ pub trait RandomPrimeWithRng { } impl RandomPrimeWithRng for Uint { - fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self { + fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { generate_prime_with_rng(rng, bit_length) } - fn generate_safe_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self { + fn generate_safe_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { generate_safe_prime_with_rng(rng, bit_length) } fn is_prime_with_rng(&self, rng: &mut impl CryptoRngCore) -> bool { diff --git a/src/uint_traits.rs b/src/uint_traits.rs index b28db3a..e998f24 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -2,35 +2,30 @@ use core::ops::{Add, Mul, Neg, Sub}; use crypto_bigint::{ modular::{DynResidue, DynResidueParams}, - subtle::{CtOption}, + subtle::CtOption, Integer, Limb, PowBoundedExp, RandomMod, Reciprocal, Uint, }; use rand_core::CryptoRngCore; // would be nice to have: *Assign traits; arithmetic traits for &self (BitAnd and Shr in particular); -pub trait UintLike: - Integer - + From - + From - + RandomMod -{ +pub trait UintLike: Integer + From + From + RandomMod { type Modular: UintModLike; // We can get by with non-small versions of jacobi_symbol and gcd, they don't have a big impact // on the performance. fn jacobi_symbol_small(lhs: i32, rhs: &Self) -> JacobiSymbol; fn gcd_small(&self, rhs: u32) -> u32; - fn bits(&self) -> usize; - fn bits_vartime(&self) -> usize; - fn bit_vartime(&self, index: usize) -> bool; - fn trailing_zeros(&self) -> usize; - fn trailing_ones(&self) -> usize; + fn bits(&self) -> u32; + fn bits_vartime(&self) -> u32; + fn bit_vartime(&self, index: u32) -> bool; + fn trailing_zeros(&self) -> u32; + fn trailing_ones(&self) -> u32; fn wrapping_sub(&self, rhs: &Self) -> Self; fn wrapping_mul(&self, rhs: &Self) -> Self; fn sqrt_vartime(&self) -> Self; - fn shr_vartime(&self, shift: usize) -> Self; - fn shl_vartime(&self, shift: usize) -> Self; - fn random_bits(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self; + fn shr_vartime(&self, shift: u32) -> Self; + fn shl_vartime(&self, shift: u32) -> Self; + fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self; fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb); fn try_into_u32(&self) -> Option; // Will have to be implemented at Uint level if we want to use TryFrom trait } @@ -89,11 +84,11 @@ impl UintLike for Uint { unimplemented!() } - fn trailing_zeros(&self) -> usize { + fn trailing_zeros(&self) -> u32 { Self::trailing_zeros(self) } - fn trailing_ones(&self) -> usize { + fn trailing_ones(&self) -> u32 { Self::trailing_ones(self) } @@ -105,15 +100,15 @@ impl UintLike for Uint { Self::wrapping_mul(self, rhs) } - fn bits(&self) -> usize { + fn bits(&self) -> u32 { Self::bits(self) } - fn bits_vartime(&self) -> usize { + fn bits_vartime(&self) -> u32 { Self::bits_vartime(self) } - fn bit_vartime(&self, index: usize) -> bool { + fn bit_vartime(&self, index: u32) -> bool { Self::bit_vartime(self, index) } @@ -121,15 +116,15 @@ impl UintLike for Uint { Self::sqrt_vartime(self) } - fn shr_vartime(&self, shift: usize) -> Self { + fn shr_vartime(&self, shift: u32) -> Self { Self::shr_vartime(self, shift) } - fn shl_vartime(&self, shift: usize) -> Self { + fn shl_vartime(&self, shift: u32) -> Self { Self::shl_vartime(self, shift) } - fn random_bits(rng: &mut impl CryptoRngCore, bit_length: usize) -> Self { + fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { unimplemented!() } From 1609d002aafb66d6d96ec749165ccfbea314d73f Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 16:13:13 -0500 Subject: [PATCH 08/39] Removed ambiguous candidates between UintLike and Integer --- src/uint_traits.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/uint_traits.rs b/src/uint_traits.rs index e998f24..7ce7b69 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -8,15 +8,13 @@ use crypto_bigint::{ use rand_core::CryptoRngCore; // would be nice to have: *Assign traits; arithmetic traits for &self (BitAnd and Shr in particular); -pub trait UintLike: Integer + From + From + RandomMod { +pub trait UintLike: Integer + RandomMod { type Modular: UintModLike; // We can get by with non-small versions of jacobi_symbol and gcd, they don't have a big impact // on the performance. fn jacobi_symbol_small(lhs: i32, rhs: &Self) -> JacobiSymbol; fn gcd_small(&self, rhs: u32) -> u32; - fn bits(&self) -> u32; - fn bits_vartime(&self) -> u32; fn bit_vartime(&self, index: u32) -> bool; fn trailing_zeros(&self) -> u32; fn trailing_ones(&self) -> u32; @@ -100,14 +98,6 @@ impl UintLike for Uint { Self::wrapping_mul(self, rhs) } - fn bits(&self) -> u32 { - Self::bits(self) - } - - fn bits_vartime(&self) -> u32 { - Self::bits_vartime(self) - } - fn bit_vartime(&self, index: u32) -> bool { Self::bit_vartime(self, index) } From efeab193a951cc52ba7b568a7186fdc35c225fca Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 16:26:51 -0500 Subject: [PATCH 09/39] Parameterize benchmarks with T: UintLike --- benches/bench.rs | 81 +++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 7c469ed..a8e71c6 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,5 +1,6 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; -use crypto_bigint::{nlimbs, Uint, U1024}; +use crypto_bigint::{U1024, U128, U256}; +use crypto_primes::UintLike; use rand_chacha::ChaCha8Rng; use rand_core::{CryptoRngCore, OsRng, SeedableRng}; @@ -22,27 +23,37 @@ fn make_rng() -> ChaCha8Rng { ChaCha8Rng::from_seed(*b"01234567890123456789012345678901") } -fn make_sieve(rng: &mut impl CryptoRngCore) -> Sieve { - let start: Uint = random_odd_uint(rng, Uint::::BITS); - Sieve::new(&start, Uint::::BITS, false) +// fn make_sieve_(rng: &mut impl CryptoRngCore) -> Sieve { +// let start: Uint = random_odd_uint(rng, Uint::::BITS); +// Sieve::new(&start, Uint::::BITS, false) +// } + +fn make_sieve(rng: &mut impl CryptoRngCore, bit_length: u32) -> Sieve { + let start: T = random_odd_uint(rng, bit_length); + return Sieve::new(&start, bit_length, false); } -fn make_presieved_num(rng: &mut impl CryptoRngCore) -> Uint { - let mut sieve = make_sieve(rng); - sieve.next().unwrap() +// fn make_presieved_num_(rng: &mut impl CryptoRngCore) -> Uint { +// let mut sieve = make_sieve_(rng); +// sieve.next().unwrap() +// } + +fn make_presieved_num(rng: &mut impl CryptoRngCore, bit_length: u32) -> T { + let mut sieve = make_sieve::(rng, bit_length); + return sieve.next().unwrap(); } 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::<{ nlimbs!(128) }>(&mut OsRng, 128)) + b.iter(|| random_odd_uint::(&mut OsRng, 128)) }); group.bench_function("(U128) creation", |b| { b.iter_batched( - || random_odd_uint::<{ nlimbs!(128) }>(&mut OsRng, 128), - |start| Sieve::new(&start, 128, false), + || random_odd_uint(&mut OsRng, 128), + |start| Sieve::::new(&start, 128, false), BatchSize::SmallInput, ) }); @@ -50,27 +61,27 @@ fn bench_sieve(c: &mut Criterion) { // 5 is the average number of pre-sieved samples we need to take before we encounter a prime group.bench_function("(U128) 5 samples", |b| { b.iter_batched( - || make_sieve::<{ nlimbs!(128) }>(&mut OsRng), + || make_sieve::(&mut OsRng, 128), |sieve| sieve.take(5).for_each(drop), BatchSize::SmallInput, ) }); group.bench_function("(U1024) random start", |b| { - b.iter(|| random_odd_uint::<{ nlimbs!(1024) }>(&mut OsRng, 1024)) + b.iter(|| random_odd_uint::(&mut OsRng, 1024)) }); group.bench_function("(U1024) creation", |b| { b.iter_batched( - || random_odd_uint::<{ nlimbs!(1024) }>(&mut OsRng, 1024), - |start| Sieve::new(&start, 1024, false), + || random_odd_uint(&mut OsRng, 1024), + |start| Sieve::::new(&start, 1024, false), BatchSize::SmallInput, ) }); group.bench_function("(U1024) 5 samples", |b| { b.iter_batched( - || make_sieve::<{ nlimbs!(1024) }>(&mut OsRng), + || make_sieve::(&mut OsRng, 1024), |sieve| sieve.take(5).for_each(drop), BatchSize::SmallInput, ) @@ -84,7 +95,7 @@ fn bench_miller_rabin(c: &mut Criterion) { group.bench_function("(U128) creation", |b| { b.iter_batched( - || random_odd_uint::<{ nlimbs!(128) }>(&mut OsRng, 128), + || random_odd_uint::(&mut OsRng, 128), |start| MillerRabin::new(&start), BatchSize::SmallInput, ) @@ -92,7 +103,7 @@ fn bench_miller_rabin(c: &mut Criterion) { 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::(&mut OsRng, 128)), |mr| mr.test_random_base(&mut OsRng), BatchSize::SmallInput, ) @@ -100,7 +111,7 @@ fn bench_miller_rabin(c: &mut Criterion) { group.bench_function("(U1024) creation", |b| { b.iter_batched( - || random_odd_uint::<{ nlimbs!(1024) }>(&mut OsRng, 1024), + || random_odd_uint::(&mut OsRng, 1024), |start| MillerRabin::new(&start), BatchSize::SmallInput, ) @@ -108,7 +119,7 @@ fn bench_miller_rabin(c: &mut Criterion) { 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::(&mut OsRng, 1024)), |mr| mr.test_random_base(&mut OsRng), BatchSize::SmallInput, ) @@ -121,7 +132,7 @@ fn bench_lucas(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U128) Selfridge base, strong check (pre-sieved)", |b| { b.iter_batched( - || make_presieved_num::<{ nlimbs!(128) }>(&mut rng), + || make_presieved_num::(&mut rng, 128), |n| lucas_test(&n, SelfridgeBase, LucasCheck::Strong), BatchSize::SmallInput, ) @@ -130,7 +141,7 @@ fn bench_lucas(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U1024) Selfridge base, strong check (pre-sieved)", |b| { b.iter_batched( - || make_presieved_num::<{ nlimbs!(1024) }>(&mut rng), + || make_presieved_num::(&mut rng, 1024), |n| lucas_test(&n, SelfridgeBase, LucasCheck::Strong), BatchSize::SmallInput, ) @@ -139,7 +150,7 @@ fn bench_lucas(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U1024) A* base, Lucas-V check (pre-sieved)", |b| { b.iter_batched( - || make_presieved_num::<{ nlimbs!(1024) }>(&mut rng), + || make_presieved_num::(&mut rng, 1024), |n| lucas_test(&n, AStarBase, LucasCheck::LucasV), BatchSize::SmallInput, ) @@ -150,7 +161,7 @@ fn bench_lucas(c: &mut Criterion) { "(U1024) brute force base, almost extra strong (pre-sieved)", |b| { b.iter_batched( - || make_presieved_num::<{ nlimbs!(1024) }>(&mut rng), + || make_presieved_num::(&mut rng, 1024), |n| lucas_test(&n, BruteForceBase, LucasCheck::AlmostExtraStrong), BatchSize::SmallInput, ) @@ -160,7 +171,7 @@ fn bench_lucas(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U1024) brute force base, extra strong (pre-sieved)", |b| { b.iter_batched( - || make_presieved_num::<{ nlimbs!(1024) }>(&mut rng), + || make_presieved_num::(&mut rng, 1024), |n| lucas_test(&n, BruteForceBase, LucasCheck::ExtraStrong), BatchSize::SmallInput, ) @@ -192,7 +203,7 @@ fn bench_presets(c: &mut Criterion) { group.bench_function("(U128) Prime test", |b| { b.iter_batched( - || random_odd_uint::<{ nlimbs!(128) }>(&mut OsRng, 128), + || random_odd_uint::(&mut OsRng, 128), |num| is_prime_with_rng(&mut OsRng, &num), BatchSize::SmallInput, ) @@ -200,7 +211,7 @@ fn bench_presets(c: &mut Criterion) { group.bench_function("(U128) Safe prime test", |b| { b.iter_batched( - || random_odd_uint::<{ nlimbs!(128) }>(&mut OsRng, 128), + || random_odd_uint::(&mut OsRng, 128), |num| is_safe_prime_with_rng(&mut OsRng, &num), BatchSize::SmallInput, ) @@ -208,23 +219,23 @@ fn bench_presets(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U128) Random prime", |b| { - b.iter(|| generate_prime_with_rng::<{ nlimbs!(128) }>(&mut rng, None)) + b.iter(|| generate_prime_with_rng::(&mut rng, 128)) }); let mut rng = make_rng(); group.bench_function("(U1024) Random prime", |b| { - b.iter(|| generate_prime_with_rng::<{ nlimbs!(1024) }>(&mut rng, None)) + b.iter(|| generate_prime_with_rng::(&mut rng, 1024)) }); let mut rng = make_rng(); group.bench_function("(U128) Random safe prime", |b| { - b.iter(|| generate_safe_prime_with_rng::<{ nlimbs!(128) }>(&mut rng, None)) + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 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::<{ nlimbs!(1024) }>(&mut rng, None)) + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 1024)) }); group.finish(); @@ -234,19 +245,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::<{ nlimbs!(128) }>(&mut rng, None)) + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 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::<{ nlimbs!(256) }>(&mut rng, Some(128))) + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 128)) }); // The upper bound for the previous test. group.bench_function("(U256) Random 256 bit safe prime", |b| { - b.iter(|| generate_safe_prime_with_rng::<{ nlimbs!(256) }>(&mut rng, None)) + b.iter(|| generate_safe_prime_with_rng::(&mut rng, 256)) }); group.finish(); @@ -263,7 +274,7 @@ fn bench_gmp(c: &mut Criterion) { group.bench_function("(U128) Random prime", |b| { b.iter_batched( - || random::<{ nlimbs!(128) }>(&mut OsRng), + || random::(&mut OsRng), |n| n.next_prime(), BatchSize::SmallInput, ) @@ -271,7 +282,7 @@ fn bench_gmp(c: &mut Criterion) { group.bench_function("(U1024) Random prime", |b| { b.iter_batched( - || random::<{ nlimbs!(1024) }>(&mut OsRng), + || random::(&mut OsRng), |n| n.next_prime(), BatchSize::SmallInput, ) From 7c95144932616eb3cf8156f07e3ccec4af20c4d2 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 21:22:44 -0500 Subject: [PATCH 10/39] Patch latest version of crypto-bigint --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7891571..748a89c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ categories = ["cryptography", "no-std"] rust-version = "1.65" [dependencies] -crypto-bigint = { version = "0.6.0-pre.0", default-features = false, features = ["rand_core"] } +crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint", branch = "master", features = ["alloc"] } 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 } From 1598586680dc016396a3a43b5568e9896e46dc94 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 21:27:06 -0500 Subject: [PATCH 11/39] Implemented SMallMod for BoxedUint --- src/hazmat.rs | 2 +- src/hazmat/jacobi.rs | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/hazmat.rs b/src/hazmat.rs index 7df8174..83bf2a3 100644 --- a/src/hazmat.rs +++ b/src/hazmat.rs @@ -2,7 +2,7 @@ //! Handle with care. //mod gcd; -//mod jacobi; +mod jacobi; mod lucas; mod miller_rabin; mod precomputed; diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index 8f2bc04..f1caa1c 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -1,6 +1,10 @@ //! Jacobi symbol calculation. -use crypto_bigint::{Integer, Limb, NonZero, Uint, Word}; +use core::fmt::Display; + +use crypto_bigint::{Limb, NonZero, Uint, Word, BoxedUint}; + +use crate::UintLike; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum JacobiSymbol { @@ -44,6 +48,16 @@ impl SmallMod for Uint { } } +impl SmallMod for BoxedUint { + fn mod8(&self) -> Word { + return self.as_limbs().get(0).unwrap().0 % 7; + } + + fn mod4(&self) -> Word { + return self.as_limbs().get(0).expect("Missing limbs").0 % 3; + } +} + /// Transforms `(a/p)` -> `(r/p)` for odd `p`, where the resulting `r` is odd, and `a = r * 2^s`. /// Takes a Jacobi symbol value, and returns `r` and the new Jacobi symbol, /// negated if the transformation changes parity. @@ -73,7 +87,7 @@ 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(a: i32, p_long: &T) -> JacobiSymbol { if p_long.is_even().into() { panic!("`p_long` must be an odd integer, but got {}", p_long); } @@ -94,7 +108,7 @@ pub(crate) fn jacobi_symbol(a: i32, p_long: &Uint) -> JacobiS }; // A degenerate case. - if a_pos == 1 || p_long == &Uint::::ONE { + if a_pos == 1 || p_long == T.oneONE { return result; } From 0a3686a3338399770dbe65b58c0bd0235b5d23b7 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 21:35:39 -0500 Subject: [PATCH 12/39] shift now returns self and ConstChoice --- src/uint_traits.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 7ce7b69..093438e 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -2,7 +2,7 @@ use core::ops::{Add, Mul, Neg, Sub}; use crypto_bigint::{ modular::{DynResidue, DynResidueParams}, - subtle::CtOption, + subtle::CtOption, ConstChoice, Integer, Limb, PowBoundedExp, RandomMod, Reciprocal, Uint, }; use rand_core::CryptoRngCore; @@ -21,11 +21,13 @@ pub trait UintLike: Integer + RandomMod { fn wrapping_sub(&self, rhs: &Self) -> Self; fn wrapping_mul(&self, rhs: &Self) -> Self; fn sqrt_vartime(&self) -> Self; - fn shr_vartime(&self, shift: u32) -> Self; - fn shl_vartime(&self, shift: u32) -> Self; + fn shr_vartime(&self, shift: u32) -> (Self, ConstChoice); + fn shl_vartime(&self, shift: u32) -> (Self, ConstChoice); fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self; fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb); fn try_into_u32(&self) -> Option; // Will have to be implemented at Uint level if we want to use TryFrom trait + + fn as_limbs(&self) -> &[Limb]; } pub trait UintModLike: @@ -106,14 +108,18 @@ impl UintLike for Uint { Self::sqrt_vartime(self) } - fn shr_vartime(&self, shift: u32) -> Self { + fn shr_vartime(&self, shift: u32) -> (Self, ConstChoice) { Self::shr_vartime(self, shift) } - fn shl_vartime(&self, shift: u32) -> Self { + fn shl_vartime(&self, shift: u32) -> (Self, ConstChoice) { Self::shl_vartime(self, shift) } + fn as_limbs(&self) -> &[Limb] { + self.as_limbs() + } + fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { unimplemented!() } From d90cb3d0d18ed12cf6658a254c5f8dc40026744e Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 21:48:52 -0500 Subject: [PATCH 13/39] Reciprocal::new is now const fn --- src/hazmat/precomputed.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hazmat/precomputed.rs b/src/hazmat/precomputed.rs index bdd363e..ff660ce 100644 --- a/src/hazmat/precomputed.rs +++ b/src/hazmat/precomputed.rs @@ -1,4 +1,4 @@ -use crypto_bigint::{Limb, Reciprocal, Word}; +use crypto_bigint::{Limb, NonZero, Reciprocal, Word}; /// The type that fits any small prime from the table. pub(crate) type SmallPrime = u16; @@ -151,7 +151,8 @@ 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; + let limb = NonZero::new(Limb(SMALL_PRIMES[i] as Word)).expect("Small prime is zero"); + arr[i] = Reciprocal::new(limb); i += 1; } arr From cd676babe2ddde81ee4dfcb833cb6da6487111f6 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 22:30:56 -0500 Subject: [PATCH 14/39] JacobiSymbol is public under hazmat::JacobiSymbol --- src/hazmat.rs | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hazmat.rs b/src/hazmat.rs index 83bf2a3..9fa004d 100644 --- a/src/hazmat.rs +++ b/src/hazmat.rs @@ -2,7 +2,7 @@ //! Handle with care. //mod gcd; -mod jacobi; +pub(crate) mod jacobi; mod lucas; mod miller_rabin; mod precomputed; diff --git a/src/lib.rs b/src/lib.rs index 8362d9b..4f7e8e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,4 +29,4 @@ pub use traits::RandomPrimeWithRng; #[cfg(feature = "default-rng")] pub use presets::{generate_prime, generate_safe_prime, is_prime, is_safe_prime}; -pub use uint_traits::{JacobiSymbol, UintLike, UintModLike}; +pub use uint_traits::{UintLike, UintModLike}; From e62362ceb5bf17e90b322101c9636e66c4c6f4ba Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 22:43:04 -0500 Subject: [PATCH 15/39] Implemented Jacobi with UintLike --- Cargo.toml | 2 +- src/hazmat/jacobi.rs | 12 +++++++----- src/hazmat/lucas.rs | 3 ++- src/hazmat/precomputed.rs | 2 +- src/hazmat/sieve.rs | 5 +++-- src/presets.rs | 2 +- src/uint_traits.rs | 37 ++++++++++++++----------------------- 7 files changed, 29 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 748a89c..eda63b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ categories = ["cryptography", "no-std"] rust-version = "1.65" [dependencies] -crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint", branch = "master", features = ["alloc"] } +crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint", branch = "master", features = ["alloc", "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/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index f1caa1c..46940db 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -2,12 +2,12 @@ use core::fmt::Display; -use crypto_bigint::{Limb, NonZero, Uint, Word, BoxedUint}; +use crypto_bigint::{BoxedUint, Limb, NonZero, Uint, Word}; use crate::UintLike; #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub(crate) enum JacobiSymbol { +pub enum JacobiSymbol { Zero, One, MinusOne, @@ -25,7 +25,7 @@ impl core::ops::Neg for JacobiSymbol { } // A helper trait to generalize some functions over Word and Uint. -trait SmallMod { +pub(crate) trait SmallMod { fn mod8(&self) -> Word; fn mod4(&self) -> Word; } @@ -108,7 +108,7 @@ pub(crate) fn jacobi_symbol(a: i32, p_long: &T }; // A degenerate case. - if a_pos == 1 || p_long == T.oneONE { + if a_pos == 1 || p_long == &T::one() { return result; } @@ -124,7 +124,9 @@ pub(crate) fn jacobi_symbol(a: i32, p_long: &T if a == 1 { return result; } - let (result, a_long, p) = swap(result, a, *p_long); + // TODO: used to be *p_long because Uint implements Copy + // However, BoxedUint does not implement Copy. Is clone the best idea? + let (result, a_long, p) = swap(result, a, p_long.clone()); // 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()); diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index bd0f880..4b4984b 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -1,6 +1,7 @@ //! Lucas primality test. -use crate::{JacobiSymbol, UintLike, UintModLike}; +use crate::{UintLike, UintModLike}; +use crate::hazmat::jacobi::JacobiSymbol; use super::Primality; diff --git a/src/hazmat/precomputed.rs b/src/hazmat/precomputed.rs index ff660ce..b9e2206 100644 --- a/src/hazmat/precomputed.rs +++ b/src/hazmat/precomputed.rs @@ -151,7 +151,7 @@ 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 { - let limb = NonZero::new(Limb(SMALL_PRIMES[i] as Word)).expect("Small prime is zero"); + let limb = NonZero::::const_new(Limb(SMALL_PRIMES[i] as Word)).0; arr[i] = Reciprocal::new(limb); i += 1; } diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index e1f3342..79df651 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -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.shr_vartime(1)) + (max_bit_length - 1, start.shr_vartime(1).0) } else { (max_bit_length, start.clone()) }; @@ -172,6 +172,7 @@ impl Sieve { // Find the increment limit. let max_value = T::one() .shl_vartime(self.max_bit_length) + .0 .wrapping_sub(&T::one()); let incr_limit = max_value.wrapping_sub(&self.base); self.incr_limit = if incr_limit > INCR_LIMIT.into() { @@ -225,7 +226,7 @@ impl Sieve { let mut num: T = Option::from(self.base.checked_add(&self.incr.into())).expect("Integer overflow"); if self.safe_primes { - num = num.shl_vartime(1) | T::one(); + num = num.shl_vartime(1).0 | T::one(); } Some(num) }; diff --git a/src/presets.rs b/src/presets.rs index 6f92477..2f73522 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -141,7 +141,7 @@ pub fn is_safe_prime_with_rng(rng: &mut impl CryptoRngCore, num: &T return false; } - _is_prime_with_rng(rng, num) && _is_prime_with_rng(rng, &num.shr_vartime(1)) + _is_prime_with_rng(rng, num) && _is_prime_with_rng(rng, &num.shr_vartime(1).0) } /// Checks for primality assuming that `num` is odd. diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 093438e..66150bb 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -1,13 +1,15 @@ use core::ops::{Add, Mul, Neg, Sub}; +use crate::hazmat::jacobi::{self, JacobiSymbol}; use crypto_bigint::{ modular::{DynResidue, DynResidueParams}, - subtle::CtOption, ConstChoice, - Integer, Limb, PowBoundedExp, RandomMod, Reciprocal, Uint, + subtle::CtOption, + ConstChoice, Integer, Limb, NonZero, PowBoundedExp, RandomMod, Reciprocal, Uint, }; use rand_core::CryptoRngCore; // would be nice to have: *Assign traits; arithmetic traits for &self (BitAnd and Shr in particular); +#[allow(missing_docs)] pub trait UintLike: Integer + RandomMod { type Modular: UintModLike; @@ -28,8 +30,10 @@ pub trait UintLike: Integer + RandomMod { fn try_into_u32(&self) -> Option; // Will have to be implemented at Uint level if we want to use TryFrom trait fn as_limbs(&self) -> &[Limb]; + fn div_rem_limb(&self, rhs: NonZero) -> (Self, Limb); } +#[allow(missing_docs)] pub trait UintModLike: core::fmt::Debug + Clone @@ -53,33 +57,15 @@ pub trait UintModLike: fn div_by_2(&self) -> Self; } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum JacobiSymbol { - Zero, - One, - MinusOne, -} - -impl Neg for JacobiSymbol { - type Output = Self; - fn neg(self) -> Self { - match self { - Self::Zero => Self::Zero, - Self::One => Self::MinusOne, - Self::MinusOne => Self::One, - } - } -} - -// Uint impls - +/// Uint impls impl UintLike for Uint { type Modular = DynResidue; fn jacobi_symbol_small(lhs: i32, rhs: &Self) -> JacobiSymbol { - unimplemented!() + jacobi::jacobi_symbol(lhs, rhs) } + #[allow(unused_variables)] fn gcd_small(&self, rhs: u32) -> u32 { unimplemented!() } @@ -120,6 +106,7 @@ impl UintLike for Uint { self.as_limbs() } + #[allow(unused_variables)] fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { unimplemented!() } @@ -131,6 +118,10 @@ impl UintLike for Uint { fn try_into_u32(&self) -> Option { self.as_words()[0].try_into().ok() } + + fn div_rem_limb(&self, rhs: NonZero) -> (Self, Limb) { + self.div_rem_limb(rhs) + } } impl UintModLike for DynResidue { From cabe201e20f2b48e4a519ced0bb67b6f9a2f3a60 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 22:47:43 -0500 Subject: [PATCH 16/39] Implemented gcd with UintLike --- src/hazmat.rs | 3 ++- src/hazmat/gcd.rs | 8 +++++--- src/hazmat/lucas.rs | 2 +- src/uint_traits.rs | 15 +++++++++++---- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/hazmat.rs b/src/hazmat.rs index 9fa004d..e4d6bbf 100644 --- a/src/hazmat.rs +++ b/src/hazmat.rs @@ -1,7 +1,7 @@ //! Components to build your own primality test. //! Handle with care. -//mod gcd; +pub(crate) mod gcd; pub(crate) mod jacobi; mod lucas; mod miller_rabin; @@ -12,6 +12,7 @@ pub(crate) mod primes; pub(crate) mod pseudoprimes; mod sieve; +pub use jacobi::JacobiSymbol; pub use lucas::{lucas_test, AStarBase, BruteForceBase, LucasBase, LucasCheck, SelfridgeBase}; pub use miller_rabin::MillerRabin; pub use sieve::{random_odd_uint, Sieve}; diff --git a/src/hazmat/gcd.rs b/src/hazmat/gcd.rs index 0b3d6bf..ef1a3a9 100644 --- a/src/hazmat/gcd.rs +++ b/src/hazmat/gcd.rs @@ -1,9 +1,11 @@ use crypto_bigint::{Limb, NonZero, Uint}; +use crate::UintLike; + /// 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: &T, m: u32) -> u32 { // 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`). @@ -11,12 +13,12 @@ pub(crate) fn gcd(n: &Uint, m: u32) -> u32 { // This we can check since it doesn't affect the return type, // even though `n` will not be 0 either in the application. - if n == &Uint::::ZERO { + if n.is_zero().into() { return m; } // 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`. diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 4b4984b..51d41ce 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -1,7 +1,7 @@ //! Lucas primality test. -use crate::{UintLike, UintModLike}; use crate::hazmat::jacobi::JacobiSymbol; +use crate::{UintLike, UintModLike}; use super::Primality; diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 66150bb..64f81cf 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -1,10 +1,13 @@ +use crate::hazmat::{ + gcd::gcd, + jacobi::{self, JacobiSymbol}, +}; use core::ops::{Add, Mul, Neg, Sub}; -use crate::hazmat::jacobi::{self, JacobiSymbol}; use crypto_bigint::{ modular::{DynResidue, DynResidueParams}, subtle::CtOption, - ConstChoice, Integer, Limb, NonZero, PowBoundedExp, RandomMod, Reciprocal, Uint, + ConstChoice, Integer, Limb, NonZero, PowBoundedExp, RandomMod, Reciprocal, Uint, Word, }; use rand_core::CryptoRngCore; @@ -30,6 +33,7 @@ pub trait UintLike: Integer + RandomMod { fn try_into_u32(&self) -> Option; // Will have to be implemented at Uint level if we want to use TryFrom trait fn as_limbs(&self) -> &[Limb]; + fn as_words(&self) -> &[Word]; fn div_rem_limb(&self, rhs: NonZero) -> (Self, Limb); } @@ -61,13 +65,16 @@ pub trait UintModLike: impl UintLike for Uint { type Modular = DynResidue; + fn as_words(&self) -> &[Word] { + self.as_words() + } + fn jacobi_symbol_small(lhs: i32, rhs: &Self) -> JacobiSymbol { jacobi::jacobi_symbol(lhs, rhs) } - #[allow(unused_variables)] fn gcd_small(&self, rhs: u32) -> u32 { - unimplemented!() + gcd(self, rhs) } fn trailing_zeros(&self) -> u32 { From 0fa058254ee8535c280cd0e5e3fe61b5f2870d43 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 23:07:23 -0500 Subject: [PATCH 17/39] But ct_div_rem_limb_with_reciprocal is actually not const fn --- src/hazmat/gcd.rs | 2 +- src/hazmat/jacobi.rs | 1 + src/uint_traits.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hazmat/gcd.rs b/src/hazmat/gcd.rs index ef1a3a9..d64fd0d 100644 --- a/src/hazmat/gcd.rs +++ b/src/hazmat/gcd.rs @@ -1,4 +1,4 @@ -use crypto_bigint::{Limb, NonZero, Uint}; +use crypto_bigint::{Limb, NonZero}; use crate::UintLike; diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index 46940db..192877f 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -6,6 +6,7 @@ use crypto_bigint::{BoxedUint, Limb, NonZero, Uint, Word}; use crate::UintLike; +#[allow(missing_docs)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum JacobiSymbol { Zero, diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 64f81cf..33106ba 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -119,7 +119,7 @@ impl UintLike for Uint { } fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb) { - Self::ct_div_rem_limb_with_reciprocal(self, reciprocal) + self.div_rem_limb_with_reciprocal(reciprocal) } fn try_into_u32(&self) -> Option { From ba9d3c5633020e7b50e5569e6ddb8e831fbd3d2c Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 23:27:19 -0500 Subject: [PATCH 18/39] Flawed implementation of Random --- src/uint_traits.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 33106ba..6f15784 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -8,6 +8,7 @@ use crypto_bigint::{ modular::{DynResidue, DynResidueParams}, subtle::CtOption, ConstChoice, Integer, Limb, NonZero, PowBoundedExp, RandomMod, Reciprocal, Uint, Word, + Random }; use rand_core::CryptoRngCore; @@ -113,9 +114,8 @@ impl UintLike for Uint { self.as_limbs() } - #[allow(unused_variables)] fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { - unimplemented!() + Self::random(rng).shl(Self::BITS - bit_length).0 } fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb) { From 6985061d189afcf0e9604e6c8ad50e30a513fb8a Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Wed, 13 Dec 2023 23:59:50 -0500 Subject: [PATCH 19/39] passed more prime generation tests --- Cargo.toml | 2 +- src/uint_traits.rs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eda63b9..74b06f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ categories = ["cryptography", "no-std"] rust-version = "1.65" [dependencies] -crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint", branch = "master", features = ["alloc", "rand_core" ] } +crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint.git", default-features = false, features = ["rand_core", "alloc"], rev = "4bf6932cee08af70d0a05625b540242522cc77f3" } 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/src/uint_traits.rs b/src/uint_traits.rs index 6f15784..9c637f2 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -7,8 +7,7 @@ use core::ops::{Add, Mul, Neg, Sub}; use crypto_bigint::{ modular::{DynResidue, DynResidueParams}, subtle::CtOption, - ConstChoice, Integer, Limb, NonZero, PowBoundedExp, RandomMod, Reciprocal, Uint, Word, - Random + ConstChoice, Integer, Limb, NonZero, PowBoundedExp, Random, RandomMod, Reciprocal, Uint, Word, }; use rand_core::CryptoRngCore; @@ -115,7 +114,9 @@ impl UintLike for Uint { } fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { - Self::random(rng).shl(Self::BITS - bit_length).0 + let random = Self::random(rng) & Self::MAX >> (Self::BITS - bit_length); + let random = random | Self::ONE << (bit_length - 1); + return random; } fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb) { From 5471920e9ed2174b7b4a051fea5ddc6bdc80afb7 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Thu, 14 Dec 2023 00:32:09 -0500 Subject: [PATCH 20/39] passed more prime generation tests --- benches/bench.rs | 74 +++++++++++++++++++------------------- src/hazmat/lucas.rs | 3 +- src/hazmat/miller_rabin.rs | 2 +- src/hazmat/sieve.rs | 21 +++++++---- src/main.rs | 9 +++++ src/presets.rs | 38 ++++++++++++-------- src/traits.rs | 33 ++++++++++++----- 7 files changed, 110 insertions(+), 70 deletions(-) create mode 100644 src/main.rs diff --git a/benches/bench.rs b/benches/bench.rs index a8e71c6..4653aef 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -23,23 +23,21 @@ fn make_rng() -> ChaCha8Rng { ChaCha8Rng::from_seed(*b"01234567890123456789012345678901") } -// fn make_sieve_(rng: &mut impl CryptoRngCore) -> Sieve { -// let start: Uint = random_odd_uint(rng, Uint::::BITS); -// Sieve::new(&start, Uint::::BITS, false) -// } - -fn make_sieve(rng: &mut impl CryptoRngCore, bit_length: u32) -> Sieve { - let start: T = random_odd_uint(rng, bit_length); +fn make_sieve( + rng: &mut impl CryptoRngCore, + bit_length: u32, + bits_precision: u32, +) -> Sieve { + let start: T = random_odd_uint(rng, bit_length, bits_precision); return Sieve::new(&start, bit_length, false); } -// fn make_presieved_num_(rng: &mut impl CryptoRngCore) -> Uint { -// let mut sieve = make_sieve_(rng); -// sieve.next().unwrap() -// } - -fn make_presieved_num(rng: &mut impl CryptoRngCore, bit_length: u32) -> T { - let mut sieve = make_sieve::(rng, bit_length); +fn make_presieved_num( + rng: &mut impl CryptoRngCore, + bit_length: u32, + bits_precision: u32, +) -> T { + let mut sieve = make_sieve::(rng, bit_length, bits_precision); return sieve.next().unwrap(); } @@ -47,12 +45,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, U128::BITS)) }); group.bench_function("(U128) creation", |b| { b.iter_batched( - || random_odd_uint(&mut OsRng, 128), + || random_odd_uint(&mut OsRng, 128, U128::BITS), |start| Sieve::::new(&start, 128, false), BatchSize::SmallInput, ) @@ -61,19 +59,19 @@ fn bench_sieve(c: &mut Criterion) { // 5 is the average number of pre-sieved samples we need to take before we encounter a prime group.bench_function("(U128) 5 samples", |b| { b.iter_batched( - || make_sieve::(&mut OsRng, 128), + || make_sieve::(&mut OsRng, 128, U128::BITS), |sieve| sieve.take(5).for_each(drop), BatchSize::SmallInput, ) }); group.bench_function("(U1024) random start", |b| { - b.iter(|| random_odd_uint::(&mut OsRng, 1024)) + b.iter(|| random_odd_uint::(&mut OsRng, 1024, U1024::BITS)) }); group.bench_function("(U1024) creation", |b| { b.iter_batched( - || random_odd_uint(&mut OsRng, 1024), + || random_odd_uint(&mut OsRng, 1024, U1024::BITS), |start| Sieve::::new(&start, 1024, false), BatchSize::SmallInput, ) @@ -81,7 +79,7 @@ fn bench_sieve(c: &mut Criterion) { group.bench_function("(U1024) 5 samples", |b| { b.iter_batched( - || make_sieve::(&mut OsRng, 1024), + || make_sieve::(&mut OsRng, 1024, U1024::BITS), |sieve| sieve.take(5).for_each(drop), BatchSize::SmallInput, ) @@ -95,7 +93,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, U128::BITS), |start| MillerRabin::new(&start), BatchSize::SmallInput, ) @@ -103,7 +101,7 @@ fn bench_miller_rabin(c: &mut Criterion) { group.bench_function("(U128) random base test (pre-sieved)", |b| { b.iter_batched( - || MillerRabin::new(&make_presieved_num::(&mut OsRng, 128)), + || MillerRabin::new(&make_presieved_num::(&mut OsRng, 128, U128::BITS)), |mr| mr.test_random_base(&mut OsRng), BatchSize::SmallInput, ) @@ -111,7 +109,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, U1024::BITS), |start| MillerRabin::new(&start), BatchSize::SmallInput, ) @@ -119,7 +117,7 @@ fn bench_miller_rabin(c: &mut Criterion) { group.bench_function("(U1024) random base test (pre-sieved)", |b| { b.iter_batched( - || MillerRabin::new(&make_presieved_num::(&mut OsRng, 1024)), + || MillerRabin::new(&make_presieved_num::(&mut OsRng, 1024, U1024::BITS)), |mr| mr.test_random_base(&mut OsRng), BatchSize::SmallInput, ) @@ -132,7 +130,7 @@ fn bench_lucas(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U128) Selfridge base, strong check (pre-sieved)", |b| { b.iter_batched( - || make_presieved_num::(&mut rng, 128), + || make_presieved_num::(&mut rng, 128, U128::BITS), |n| lucas_test(&n, SelfridgeBase, LucasCheck::Strong), BatchSize::SmallInput, ) @@ -141,7 +139,7 @@ fn bench_lucas(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U1024) Selfridge base, strong check (pre-sieved)", |b| { b.iter_batched( - || make_presieved_num::(&mut rng, 1024), + || make_presieved_num::(&mut rng, 1024, U1024::BITS), |n| lucas_test(&n, SelfridgeBase, LucasCheck::Strong), BatchSize::SmallInput, ) @@ -150,7 +148,7 @@ fn bench_lucas(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U1024) A* base, Lucas-V check (pre-sieved)", |b| { b.iter_batched( - || make_presieved_num::(&mut rng, 1024), + || make_presieved_num::(&mut rng, 1024, U1024::BITS), |n| lucas_test(&n, AStarBase, LucasCheck::LucasV), BatchSize::SmallInput, ) @@ -161,7 +159,7 @@ fn bench_lucas(c: &mut Criterion) { "(U1024) brute force base, almost extra strong (pre-sieved)", |b| { b.iter_batched( - || make_presieved_num::(&mut rng, 1024), + || make_presieved_num::(&mut rng, 1024, U1024::BITS), |n| lucas_test(&n, BruteForceBase, LucasCheck::AlmostExtraStrong), BatchSize::SmallInput, ) @@ -171,7 +169,7 @@ fn bench_lucas(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U1024) brute force base, extra strong (pre-sieved)", |b| { b.iter_batched( - || make_presieved_num::(&mut rng, 1024), + || make_presieved_num::(&mut rng, 1024, U1024::BITS), |n| lucas_test(&n, BruteForceBase, LucasCheck::ExtraStrong), BatchSize::SmallInput, ) @@ -203,7 +201,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, U128::BITS), |num| is_prime_with_rng(&mut OsRng, &num), BatchSize::SmallInput, ) @@ -211,7 +209,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, U128::BITS), |num| is_safe_prime_with_rng(&mut OsRng, &num), BatchSize::SmallInput, ) @@ -219,23 +217,23 @@ 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, U128::BITS)) }); 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, U128::BITS)) }); 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, U128::BITS)) }); 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, U1024::BITS)) }); group.finish(); @@ -245,19 +243,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, U128::BITS)) }); // 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, U256::BITS)) }); // 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, U256::BITS)) }); group.finish(); diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 51d41ce..38826ea 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -549,8 +549,9 @@ 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::ONE), (1, U128::ONE)); // OK assert_eq!(decompose(&U128::from(7766015u32)), (15, U128::from(237u32))); + // OK } fn is_slpsp(num: u32) -> bool { diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 43f01e0..4d495d1 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -195,7 +195,7 @@ mod tests { #[test] fn trivial() { let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); - let start: U1024 = random_odd_uint(&mut rng, 1024); + let start: U1024 = random_odd_uint(&mut rng, 1024, U1024::BITS); for num in Sieve::new(&start, 1024, false).take(10) { let mr = MillerRabin::new(&num); diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 79df651..95912dd 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -13,20 +13,27 @@ use crate::{ /// Returns a random odd integer with given bit length /// (that is, with both `0` and `bit_length-1` bits set). /// +/// Where T is stack allocated (Uint), `bits_precision` must be exactly `T::BITS`. Where T is +/// heap-allocated (BoxedUint), `bits_precision` will be passed to the appropriate type. +/// /// 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: u32) -> T { +pub fn random_odd_uint( + rng: &mut impl CryptoRngCore, + bit_length: u32, + bits_precision: u32, +) -> T { if bit_length == 0 { panic!("Bit length must be non-zero"); } // TODO: what do we do here if `bit_length` is greater than Uint::BITS? // assume that the user knows what he's doing since it is a hazmat function? - /*if bit_length > Uint::::BITS { + if bit_length > bits_precision { panic!( "The requested bit length ({}) is larger than the chosen Uint size", bit_length ); - }*/ + } // TODO: not particularly efficient, can be improved by zeroing high bits instead of shifting let random = T::random_bits(rng, bit_length); @@ -286,7 +293,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: U64 = random_odd_uint(&mut rng, 32, U64::BITS); for num in Sieve::new(&start, 32, false).take(100) { let num_u64: u64 = num.into(); assert!(num_u64.leading_zeros() == 32); @@ -366,7 +373,7 @@ mod tests { #[test] fn random_below_max_length() { for _ in 0..10 { - let r: U64 = random_odd_uint(&mut OsRng, 50); + let r: U64 = random_odd_uint(&mut OsRng, 50, U64::BITS); assert_eq!(r.bits(), 50); } } @@ -374,13 +381,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: U64 = random_odd_uint(&mut OsRng, 0, U64::BITS); } #[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: U64 = random_odd_uint(&mut OsRng, 65, U64::BITS); } #[test] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f23ced9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,9 @@ +use crypto_bigint::{Integer, Random, U128}; +use rand_core::OsRng; + +fn main() { + let max = U128::MAX; + let s = max.trailing_ones(); + println!("{}, {}", max, s); + println!("{}", max.clone().shr(s).0); +} diff --git a/src/presets.rs b/src/presets.rs index 2f73522..afb5fe9 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -10,21 +10,23 @@ use crate::UintLike; /// Returns a random prime of size `bit_length` using [`OsRng`] as the RNG. /// If `bit_length` is `None`, the full size of `Uint` is used. +/// TODO: bits_precision? /// /// 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) /// of size `bit_length` using [`OsRng`] as the RNG. /// If `bit_length` is `None`, the full size of `Uint` is used. +/// TODO: bits_precision? /// /// 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. @@ -47,16 +49,21 @@ pub fn is_safe_prime(num: &T) -> bool { /// Returns a random prime of size `bit_length` using the provided RNG. /// If `bit_length` is `None`, the full size of `Uint` is used. +/// TODO: bits_precision? /// /// 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. -pub fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_length: u32) -> T { +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."); } loop { - let start = random_odd_uint::(rng, bit_length); + let start = random_odd_uint::(rng, bit_length, bits_precision); let sieve = Sieve::new(&start, bit_length, false); for num in sieve { if is_prime_with_rng(rng, &num) { @@ -76,12 +83,13 @@ pub fn generate_prime_with_rng(rng: &mut impl CryptoRngCore, bit_le 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."); } loop { - let start = random_odd_uint::(rng, bit_length); + let start = random_odd_uint::(rng, bit_length, bits_precision); let sieve = Sieve::new(&start, bit_length, true); for num in sieve { if is_safe_prime_with_rng(rng, &num) { @@ -247,7 +255,7 @@ mod tests { #[test] fn prime_generation() { for bit_length in (28u32..=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)); } @@ -256,7 +264,7 @@ 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)); } @@ -289,25 +297,25 @@ 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 = "The requested bit length (65) is larger than the chosen Uint size")] 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 = "The requested bit length (65) is larger than the chosen Uint size")] 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 { @@ -318,7 +326,7 @@ mod tests { fn corner_cases_generate_prime() { for bits in 2u32..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)); } @@ -329,7 +337,7 @@ mod tests { fn corner_cases_generate_safe_prime() { for bits in 3u32..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)); } diff --git a/src/traits.rs b/src/traits.rs index 458d85c..bed5970 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 Uint { - 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)); } } From 2449f8f26611dfdb6c34ef005915d8af62c4c599 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Thu, 14 Dec 2023 01:01:19 -0500 Subject: [PATCH 21/39] All tests pass now --- benches/bench.rs | 6 +++--- src/hazmat/lucas.rs | 9 ++++++--- src/hazmat/miller_rabin.rs | 5 +++-- src/hazmat/sieve.rs | 17 +++++++++-------- src/main.rs | 9 --------- src/presets.rs | 4 ++-- 6 files changed, 23 insertions(+), 27 deletions(-) delete mode 100644 src/main.rs diff --git a/benches/bench.rs b/benches/bench.rs index 4653aef..f472486 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -29,7 +29,7 @@ fn make_sieve( bits_precision: u32, ) -> Sieve { let start: T = random_odd_uint(rng, bit_length, bits_precision); - return Sieve::new(&start, bit_length, false); + return Sieve::new(&start, bit_length, false, bits_precision); } fn make_presieved_num( @@ -51,7 +51,7 @@ fn bench_sieve(c: &mut Criterion) { group.bench_function("(U128) creation", |b| { b.iter_batched( || random_odd_uint(&mut OsRng, 128, U128::BITS), - |start| Sieve::::new(&start, 128, false), + |start| Sieve::::new(&start, 128, false, U128::BITS), BatchSize::SmallInput, ) }); @@ -72,7 +72,7 @@ fn bench_sieve(c: &mut Criterion) { group.bench_function("(U1024) creation", |b| { b.iter_batched( || random_odd_uint(&mut OsRng, 1024, U1024::BITS), - |start| Sieve::::new(&start, 1024, false), + |start| Sieve::::new(&start, 1024, false, U1024::BITS), BatchSize::SmallInput, ) }); diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 38826ea..7f3b402 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -172,7 +172,10 @@ fn decompose(n: &T) -> (u32, T) { 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.clone() >> s).checked_add(&T::one())).expect("Integer overflow"); + // TODO: shr(s-1).shr(1) is a hack around the fact that a full right shift will panic + // see https://github.com/RustCrypto/crypto-bigint/commit/55312b6aa71#r134960147 + let d = Option::from((n.clone().shr(s - 1).shr(1)).checked_add(&T::one())) + .expect("Integer overflow"); (s, d) } @@ -548,10 +551,10 @@ mod tests { #[test] fn decomposition() { + // TODO: the first one overflows assert_eq!(decompose(&U128::MAX), (128, U128::ONE)); - assert_eq!(decompose(&U128::ONE), (1, U128::ONE)); // OK + assert_eq!(decompose(&U128::ONE), (1, U128::ONE)); assert_eq!(decompose(&U128::from(7766015u32)), (15, U128::from(237u32))); - // OK } fn is_slpsp(num: u32) -> bool { diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 4d495d1..cc12448 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -43,7 +43,8 @@ impl MillerRabin { // Find `s` and odd `d` such that `candidate - 1 == 2^s * d`. let candidate_minus_one = candidate.wrapping_sub(&T::one()); let s = candidate_minus_one.trailing_zeros(); - let d = candidate_minus_one.shr(s); + // TODO: https://github.com/RustCrypto/crypto-bigint/commit/55312b6aa71#r134960147 + let d = candidate_minus_one.shr(s - 1).shr(1); Self { candidate: candidate.clone(), @@ -196,7 +197,7 @@ mod tests { fn trivial() { let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); let start: U1024 = random_odd_uint(&mut rng, 1024, U1024::BITS); - for num in Sieve::new(&start, 1024, false).take(10) { + for num in Sieve::new(&start, 1024, false, U1024::BITS).take(10) { let mr = MillerRabin::new(&num); // Trivial tests, must always be true. diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 95912dd..97211f1 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -86,19 +86,19 @@ 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: &T, max_bit_length: u32, safe_primes: bool) -> Self { + pub fn new(start: &T, max_bit_length: u32, safe_primes: bool, bits_precision: u32) -> Self { if max_bit_length == 0 { panic!("The requested bit length cannot be zero"); } // TODO: what do we do here if `bit_length` is greater than Uint::BITS? // assume that the user knows what he's doing since it is a hazmat function? - /*if max_bit_length > Uint::::BITS { + if max_bit_length > bits_precision { panic!( "The requested bit length ({}) is larger than the chosen Uint size", max_bit_length ); - }*/ + } // If we are targeting safe primes, iterate over the corresponding // possible Germain primes (`n/2`), reducing the task to that with `safe_primes = false`. @@ -294,7 +294,7 @@ mod tests { let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901"); let start: U64 = random_odd_uint(&mut rng, 32, U64::BITS); - for num in Sieve::new(&start, 32, false).take(100) { + for num in Sieve::new(&start, 32, false, U64::BITS).take(100) { let num_u64: u64 = num.into(); assert!(num_u64.leading_zeros() == 32); @@ -306,7 +306,8 @@ mod tests { } 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::>(); + let test = + Sieve::new(&U64::from(start), bit_length, safe_prime, U64::BITS).collect::>(); assert_eq!(test.len(), reference.len()); for (x, y) in test.iter().zip(reference.iter()) { assert_eq!(x, &U64::from(*y)); @@ -361,13 +362,13 @@ mod tests { #[test] #[should_panic(expected = "The requested bit length cannot be zero")] fn sieve_zero_bits() { - let _sieve = Sieve::new(&U64::ONE, 0, false); + let _sieve = Sieve::new(&U64::ONE, 0, false, U64::BITS); } #[test] #[should_panic(expected = "The requested bit length (65) is larger than the chosen Uint size")] fn sieve_too_many_bits() { - let _sieve = Sieve::new(&U64::ONE, 65, false); + let _sieve = Sieve::new(&U64::ONE, 65, false, U64::BITS); } #[test] @@ -392,7 +393,7 @@ mod tests { #[test] fn sieve_derived_traits() { - let s = Sieve::new(&U64::ONE, 10, false); + let s = Sieve::new(&U64::ONE, 10, false, U64::BITS); assert!(format!("{s:?}").starts_with("Sieve")); assert_eq!(s.clone(), s); } diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index f23ced9..0000000 --- a/src/main.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crypto_bigint::{Integer, Random, U128}; -use rand_core::OsRng; - -fn main() { - let max = U128::MAX; - let s = max.trailing_ones(); - println!("{}, {}", max, s); - println!("{}", max.clone().shr(s).0); -} diff --git a/src/presets.rs b/src/presets.rs index afb5fe9..63e5ee6 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -64,7 +64,7 @@ pub fn generate_prime_with_rng( } loop { let start = random_odd_uint::(rng, bit_length, bits_precision); - let sieve = Sieve::new(&start, bit_length, false); + let sieve = Sieve::new(&start, bit_length, false, bits_precision); for num in sieve { if is_prime_with_rng(rng, &num) { return num; @@ -90,7 +90,7 @@ pub fn generate_safe_prime_with_rng( } loop { let start = random_odd_uint::(rng, bit_length, bits_precision); - let sieve = Sieve::new(&start, bit_length, true); + let sieve = Sieve::new(&start, bit_length, true, bits_precision); for num in sieve { if is_safe_prime_with_rng(rng, &num) { return num; From 6b734002634fdd3354c0fd88ad0c628a949ab0bd Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Thu, 14 Dec 2023 01:13:29 -0500 Subject: [PATCH 22/39] Typo --- benches/bench.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/bench.rs b/benches/bench.rs index f472486..082c3df 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -222,7 +222,7 @@ fn bench_presets(c: &mut Criterion) { let mut rng = make_rng(); group.bench_function("(U1024) Random prime", |b| { - b.iter(|| generate_prime_with_rng::(&mut rng, 1024, U128::BITS)) + b.iter(|| generate_prime_with_rng::(&mut rng, 1024, U1024::BITS)) }); let mut rng = make_rng(); From 72c4eeec57b3c0a250c781addca6da2bc4cf0f23 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Thu, 14 Dec 2023 11:43:53 -0500 Subject: [PATCH 23/39] Updated documentation for bits_precision --- src/hazmat/jacobi.rs | 5 +++-- src/hazmat/lucas.rs | 5 +---- src/hazmat/miller_rabin.rs | 3 +-- src/hazmat/sieve.rs | 9 +++------ src/presets.rs | 12 +++++++++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index 192877f..e680eab 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -125,8 +125,9 @@ pub(crate) fn jacobi_symbol(a: i32, p_long: &T if a == 1 { return result; } - // TODO: used to be *p_long because Uint implements Copy - // However, BoxedUint does not implement Copy. Is clone the best idea? + // NOTE: prior to the UintLike generics it was *p_long, which works because Uint implements + // Copy. However, BoxedUint does not implement Copy (which it should not anyways since it + // is heap-allocated), so explicit cloning is the next best thing let (result, a_long, p) = swap(result, a, p_long.clone()); // Can unwrap here, since `p` is swapped with `a`, // and `a` would be odd after `reduce_numerator()`. diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 7f3b402..86c9e27 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -172,9 +172,7 @@ fn decompose(n: &T) -> (u32, T) { let s = n.trailing_ones(); // This won't overflow since the original `n` was odd, so we right-shifted at least once. - // TODO: shr(s-1).shr(1) is a hack around the fact that a full right shift will panic - // see https://github.com/RustCrypto/crypto-bigint/commit/55312b6aa71#r134960147 - let d = Option::from((n.clone().shr(s - 1).shr(1)).checked_add(&T::one())) + let d = Option::from((n.clone().shr_vartime(s).0).checked_add(&T::one())) .expect("Integer overflow"); (s, d) @@ -551,7 +549,6 @@ mod tests { #[test] fn decomposition() { - // TODO: the first one overflows 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))); diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index cc12448..16afeee 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -43,8 +43,7 @@ impl MillerRabin { // Find `s` and odd `d` such that `candidate - 1 == 2^s * d`. let candidate_minus_one = candidate.wrapping_sub(&T::one()); let s = candidate_minus_one.trailing_zeros(); - // TODO: https://github.com/RustCrypto/crypto-bigint/commit/55312b6aa71#r134960147 - let d = candidate_minus_one.shr(s - 1).shr(1); + let d = candidate_minus_one.shr_vartime(s).0; Self { candidate: candidate.clone(), diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 97211f1..6f419a9 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -16,7 +16,8 @@ use crate::{ /// Where T is stack allocated (Uint), `bits_precision` must be exactly `T::BITS`. Where T is /// heap-allocated (BoxedUint), `bits_precision` will be passed to the appropriate type. /// -/// Panics if `bit_length` is 0 or is greater than the bit size of the target `Uint`. +/// If `bit_length` is greater than `bits_precision`, or if `bit_length` is 0, this function will +/// panic pub fn random_odd_uint( rng: &mut impl CryptoRngCore, bit_length: u32, @@ -26,8 +27,6 @@ pub fn random_odd_uint( panic!("Bit length must be non-zero"); } - // TODO: what do we do here if `bit_length` is greater than Uint::BITS? - // assume that the user knows what he's doing since it is a hazmat function? if bit_length > bits_precision { panic!( "The requested bit length ({}) is larger than the chosen Uint size", @@ -83,7 +82,7 @@ impl Sieve { /// Note that `start` is adjusted to `2`, or the next `1 mod 2` number (`safe_primes = false`); /// and `5`, or `3 mod 4` number (`safe_primes = true`). /// - /// Panics if `max_bit_length` is zero or greater than the size of the target `Uint`. + /// Panics if `max_bit_length` is zero or greater than `bits_precision` /// /// If `safe_primes` is `true`, both the returned `n` and `n/2` are sieved. pub fn new(start: &T, max_bit_length: u32, safe_primes: bool, bits_precision: u32) -> Self { @@ -91,8 +90,6 @@ impl Sieve { panic!("The requested bit length cannot be zero"); } - // TODO: what do we do here if `bit_length` is greater than Uint::BITS? - // assume that the user knows what he's doing since it is a hazmat function? if max_bit_length > bits_precision { panic!( "The requested bit length ({}) is larger than the chosen Uint size", diff --git a/src/presets.rs b/src/presets.rs index 63e5ee6..43ce827 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -10,7 +10,9 @@ use crate::UintLike; /// Returns a random prime of size `bit_length` using [`OsRng`] as the RNG. /// If `bit_length` is `None`, the full size of `Uint` is used. -/// TODO: bits_precision? +/// +/// Where T is stack allocated (Uint), `bits_precision` must be exactly `T::BITS`. Where T is +/// heap-allocated (BoxedUint), `bits_precision` will be passed to the appropriate type. /// /// See [`is_prime_with_rng`] for details about the performed checks. #[cfg(feature = "default-rng")] @@ -21,7 +23,9 @@ pub fn generate_prime(bit_length: u32, bits_precision: u32) -> T { /// Returns a random safe prime (that is, such that `(n - 1) / 2` is also prime) /// of size `bit_length` using [`OsRng`] as the RNG. /// If `bit_length` is `None`, the full size of `Uint` is used. -/// TODO: bits_precision? +/// +/// Where T is stack allocated (Uint), `bits_precision` must be exactly `T::BITS`. Where T is +/// heap-allocated (BoxedUint), `bits_precision` will be passed to the appropriate type. /// /// See [`is_prime_with_rng`] for details about the performed checks. #[cfg(feature = "default-rng")] @@ -49,7 +53,9 @@ pub fn is_safe_prime(num: &T) -> bool { /// Returns a random prime of size `bit_length` using the provided RNG. /// If `bit_length` is `None`, the full size of `Uint` is used. -/// TODO: bits_precision? +/// +/// Where T is stack allocated (Uint), `bits_precision` must be exactly `T::BITS`. Where T is +/// heap-allocated (BoxedUint), `bits_precision` will be passed to the appropriate type. /// /// Panics if `bit_length` is less than 2, or greater than the bit size of the target `Uint`. /// From dcfedd1e502de9eccf87d8b83a14586bf122d859 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Thu, 14 Dec 2023 11:50:09 -0500 Subject: [PATCH 24/39] listed things to implement for BoxedUint and BoxedResidue --- src/uint_traits.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 9c637f2..9164f3d 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -5,9 +5,10 @@ use crate::hazmat::{ use core::ops::{Add, Mul, Neg, Sub}; use crypto_bigint::{ - modular::{DynResidue, DynResidueParams}, + modular::{BoxedResidue, BoxedResidueParams, DynResidue, DynResidueParams}, subtle::CtOption, - ConstChoice, Integer, Limb, NonZero, PowBoundedExp, Random, RandomMod, Reciprocal, Uint, Word, + BoxedUint, ConstChoice, Integer, Limb, NonZero, PowBoundedExp, Random, RandomMod, Reciprocal, + Uint, Word, }; use rand_core::CryptoRngCore; @@ -160,3 +161,100 @@ impl UintModLike for DynResidue { Self::div_by_2(self) } } + +impl UintLike for BoxedUint { + type Modular = BoxedResidue; + + fn jacobi_symbol_small(lhs: i32, rhs: &Self) -> JacobiSymbol { + todo!(); + } + + fn gcd_small(&self, rhs: u32) -> u32 { + todo!(); + } + + fn bit_vartime(&self, index: u32) -> bool { + todo!(); + } + + fn trailing_zeros(&self) -> u32 { + todo!(); + } + + fn trailing_ones(&self) -> u32 { + todo!(); + } + + fn wrapping_sub(&self, rhs: &Self) -> Self { + todo!(); + } + + fn wrapping_mul(&self, rhs: &Self) -> Self { + todo!(); + } + + fn sqrt_vartime(&self) -> Self { + todo!(); + } + + fn shr_vartime(&self, shift: u32) -> (Self, ConstChoice) { + todo!(); + } + + fn shl_vartime(&self, shift: u32) -> (Self, ConstChoice) { + todo!(); + } + + fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { + todo!(); + } + + fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb) { + todo!(); + } + + fn try_into_u32(&self) -> Option { + todo!(); + } + + fn as_limbs(&self) -> &[Limb] { + todo!(); + } + + fn as_words(&self) -> &[Word] { + todo!(); + } + + fn div_rem_limb(&self, rhs: NonZero) -> (Self, Limb) { + todo!(); + } +} + +impl UintModLike for BoxedResidue { + type Raw = BoxedUint; + type Params = BoxedResidueParams; + + fn new_params(modulus: &Self::Raw) -> CtOption { + todo!(); + } + + fn new(raw: &Self::Raw, params: &Self::Params) -> Self { + todo!(); + } + + fn zero(params: &Self::Params) -> Self { + todo!(); + } + + fn one(params: &Self::Params) -> Self { + todo!(); + } + + fn square(&self) -> Self { + todo!(); + } + + fn div_by_2(&self) -> Self { + todo!(); + } +} From c2ea4c19306831f54178d00ff5d0193601962531 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Thu, 14 Dec 2023 11:54:25 -0500 Subject: [PATCH 25/39] Small number of tests for BoxedUint prime generation --- src/presets.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/presets.rs b/src/presets.rs index 43ce827..3770e9f 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -183,7 +183,7 @@ fn _is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &T) -> boo #[cfg(test)] mod tests { - use crypto_bigint::{CheckedAdd, Uint, Word, U128, U64}; + use crypto_bigint::{CheckedAdd, Uint, Word, U128, U64, BoxedUint}; use num_prime::nt_funcs::is_prime64; use rand_core::OsRng; @@ -265,6 +265,12 @@ mod tests { assert!(p.bits_vartime() == bit_length); assert!(is_prime(&p)); } + + for bit_length in (28u32..=128).step_by(10) { + let p: BoxedUint = generate_prime(bit_length, 128); + assert!(p.bits_vartime() == bit_length); + assert!(is_prime(&p)); + } } #[test] @@ -274,6 +280,12 @@ mod tests { assert!(p.bits_vartime() == bit_length); assert!(is_safe_prime(&p)); } + + for bit_length in (28u32..=128).step_by(10) { + let p: BoxedUint = generate_safe_prime(bit_length, 128); + assert!(p.bits_vartime() == bit_length); + assert!(is_safe_prime(&p)); + } } #[test] From a958b3028b06c07f5978ac726bf14e83fe90c18b Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Thu, 14 Dec 2023 12:36:48 -0500 Subject: [PATCH 26/39] Attempted to implement UintLike for BoxedUint; identified missing implementation in crypto-bigint --- src/hazmat/sieve.rs | 2 +- src/presets.rs | 2 +- src/uint_traits.rs | 109 +++++++++++++++++++++++++++++++++----------- 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 6f419a9..1889dca 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -35,7 +35,7 @@ pub fn random_odd_uint( } // TODO: not particularly efficient, can be improved by zeroing high bits instead of shifting - let random = T::random_bits(rng, bit_length); + let random = T::random_bits(rng, bit_length, bits_precision); // Make it odd let random = random | T::one(); diff --git a/src/presets.rs b/src/presets.rs index 3770e9f..66d31d1 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -183,7 +183,7 @@ fn _is_prime_with_rng(rng: &mut impl CryptoRngCore, num: &T) -> boo #[cfg(test)] mod tests { - use crypto_bigint::{CheckedAdd, Uint, Word, U128, U64, BoxedUint}; + use crypto_bigint::{BoxedUint, CheckedAdd, Uint, Word, U128, U64}; use num_prime::nt_funcs::is_prime64; use rand_core::OsRng; diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 9164f3d..5ec3582 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -1,5 +1,5 @@ use crate::hazmat::{ - gcd::gcd, + gcd, jacobi::{self, JacobiSymbol}, }; use core::ops::{Add, Mul, Neg, Sub}; @@ -29,7 +29,7 @@ pub trait UintLike: Integer + RandomMod { fn sqrt_vartime(&self) -> Self; fn shr_vartime(&self, shift: u32) -> (Self, ConstChoice); fn shl_vartime(&self, shift: u32) -> (Self, ConstChoice); - fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self; + fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32, bits_precision: u32) -> Self; fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb); fn try_into_u32(&self) -> Option; // Will have to be implemented at Uint level if we want to use TryFrom trait @@ -75,7 +75,7 @@ impl UintLike for Uint { } fn gcd_small(&self, rhs: u32) -> u32 { - gcd(self, rhs) + gcd::gcd(self, rhs) } fn trailing_zeros(&self) -> u32 { @@ -114,7 +114,7 @@ impl UintLike for Uint { self.as_limbs() } - fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { + fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32, bits_precision: u32) -> Self { let random = Self::random(rng) & Self::MAX >> (Self::BITS - bit_length); let random = random | Self::ONE << (bit_length - 1); return random; @@ -166,65 +166,119 @@ impl UintLike for BoxedUint { type Modular = BoxedResidue; fn jacobi_symbol_small(lhs: i32, rhs: &Self) -> JacobiSymbol { - todo!(); + jacobi::jacobi_symbol(lhs, rhs) } fn gcd_small(&self, rhs: u32) -> u32 { - todo!(); + gcd::gcd(self, rhs) } + /// TODO: BoxedUint does not implement bit_vartime + /// TODO: this needs to be tested fn bit_vartime(&self, index: u32) -> bool { - todo!(); + if index >= self.bits_precision() { + return false; + } + (self.as_limbs()[(index / Limb::BITS) as usize].0 >> (index % Limb::BITS)) & 1 == 1 } fn trailing_zeros(&self) -> u32 { - todo!(); + self.trailing_zeros() } + /// TODO: BoxedUint does not implement trailing_ones, but it should be doable + /// TODO: this needs to be testesd, but this is not the implementation I would go with in + /// crypto-bigint fn trailing_ones(&self) -> u32 { - todo!(); + let limbs = self.as_limbs(); + + let mut count = 0; + let mut i = 0; + let mut nonmax_limb_not_encountered = ConstChoice::TRUE; + while i < limbs.len() { + let l = limbs[i]; + let z = l.trailing_ones(); + let should_count: bool = nonmax_limb_not_encountered.into(); + if should_count { + count += z; + } + let is_max = l.0 == Limb::MAX.0; + if should_count && is_max { + nonmax_limb_not_encountered = ConstChoice::TRUE; + } else { + nonmax_limb_not_encountered = ConstChoice::FALSE; + } + i += 1; + } + + count } fn wrapping_sub(&self, rhs: &Self) -> Self { - todo!(); + self.wrapping_sub(rhs) } fn wrapping_mul(&self, rhs: &Self) -> Self { - todo!(); + self.wrapping_mul(rhs) } + /// BoxedUint does not implement sqrt_vartime fn sqrt_vartime(&self) -> Self { todo!(); } + /// TODO: BoxedUint::shr_vartime should behave similarly as Uint::shr_vartime; instead of + /// returning Option, return (val, choice) fn shr_vartime(&self, shift: u32) -> (Self, ConstChoice) { - todo!(); - } - + if let Some(shifted) = self.shr_vartime(shift) { + return (shifted, ConstChoice::FALSE); + } else { + return ( + Self::zero_with_precision(self.bits_precision()), + ConstChoice::TRUE, + ); + } + } + + /// TODO: BoxedUint::shl_vartime should behave similarly as Uint::shr_vartime; instead of + /// returning Option, return (val, choice) fn shl_vartime(&self, shift: u32) -> (Self, ConstChoice) { - todo!(); - } - - fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32) -> Self { - todo!(); + if let Some(shifted) = self.shl_vartime(shift) { + return (shifted, ConstChoice::FALSE); + } else { + return ( + Self::zero_with_precision(self.bits_precision()), + ConstChoice::TRUE, + ); + } + } + + fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32, bits_precision: u32) -> Self { + let random = Self::random(rng, bits_precision) + & Self::max(bits_precision) >> (bits_precision - bit_length); + let random = random | Self::one_with_precision(bits_precision) << (bit_length - 1); + return random; } + /// TODO: BoxedUint does not implement div_rem_limb_with_reciprocal + /// TODO: BoxedUint does not implement shl_limb fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb) { todo!(); } fn try_into_u32(&self) -> Option { - todo!(); + self.as_words()[0].try_into().ok() } fn as_limbs(&self) -> &[Limb] { - todo!(); + self.as_limbs() } fn as_words(&self) -> &[Word] { - todo!(); + self.as_words() } + /// TODO: BoxedUint does not implement div_rem_limb fn div_rem_limb(&self, rhs: NonZero) -> (Self, Limb) { todo!(); } @@ -235,25 +289,26 @@ impl UintModLike for BoxedResidue { type Params = BoxedResidueParams; fn new_params(modulus: &Self::Raw) -> CtOption { - todo!(); + Self::Params::new(modulus.clone()) } fn new(raw: &Self::Raw, params: &Self::Params) -> Self { - todo!(); + Self::new(raw.clone(), params.clone()) } fn zero(params: &Self::Params) -> Self { - todo!(); + Self::zero(params.clone()) } fn one(params: &Self::Params) -> Self { - todo!(); + Self::one(params.clone()) } fn square(&self) -> Self { - todo!(); + self.square() } + /// TODO: BoxedUint does not implement div_by_2 fn div_by_2(&self) -> Self { todo!(); } From 2ed2bda2ccaae0f81af0e9272e5e7d63f92a9493 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Fri, 15 Dec 2023 00:57:46 -0500 Subject: [PATCH 27/39] This thing compiles but does not pass tests --- Cargo.toml | 3 ++- src/uint_traits.rs | 26 ++++++-------------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 74b06f6..520ea65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,8 @@ categories = ["cryptography", "no-std"] rust-version = "1.65" [dependencies] -crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint.git", default-features = false, features = ["rand_core", "alloc"], rev = "4bf6932cee08af70d0a05625b540242522cc77f3" } +# crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint.git", default-features = false, features = ["rand_core", "alloc"], rev = "4bf6932cee08af70d0a05625b540242522cc77f3" } +crypto-bigint = { path = "../crypto-bigint", default-features = false, features = ["rand_core", "alloc"] } 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/src/uint_traits.rs b/src/uint_traits.rs index 5ec3582..db135ec 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -114,7 +114,7 @@ impl UintLike for Uint { self.as_limbs() } - fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32, bits_precision: u32) -> Self { + fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32, _bits_precision: u32) -> Self { let random = Self::random(rng) & Self::MAX >> (Self::BITS - bit_length); let random = random | Self::ONE << (bit_length - 1); return random; @@ -224,33 +224,19 @@ impl UintLike for BoxedUint { /// BoxedUint does not implement sqrt_vartime fn sqrt_vartime(&self) -> Self { - todo!(); + self.sqrt_vartime() } /// TODO: BoxedUint::shr_vartime should behave similarly as Uint::shr_vartime; instead of /// returning Option, return (val, choice) fn shr_vartime(&self, shift: u32) -> (Self, ConstChoice) { - if let Some(shifted) = self.shr_vartime(shift) { - return (shifted, ConstChoice::FALSE); - } else { - return ( - Self::zero_with_precision(self.bits_precision()), - ConstChoice::TRUE, - ); - } + self.shr_vartime(shift) } /// TODO: BoxedUint::shl_vartime should behave similarly as Uint::shr_vartime; instead of /// returning Option, return (val, choice) fn shl_vartime(&self, shift: u32) -> (Self, ConstChoice) { - if let Some(shifted) = self.shl_vartime(shift) { - return (shifted, ConstChoice::FALSE); - } else { - return ( - Self::zero_with_precision(self.bits_precision()), - ConstChoice::TRUE, - ); - } + self.shl_vartime(shift) } fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32, bits_precision: u32) -> Self { @@ -263,7 +249,7 @@ impl UintLike for BoxedUint { /// TODO: BoxedUint does not implement div_rem_limb_with_reciprocal /// TODO: BoxedUint does not implement shl_limb fn ct_div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb) { - todo!(); + self.div_rem_limb_with_reciprocal(reciprocal) } fn try_into_u32(&self) -> Option { @@ -280,7 +266,7 @@ impl UintLike for BoxedUint { /// TODO: BoxedUint does not implement div_rem_limb fn div_rem_limb(&self, rhs: NonZero) -> (Self, Limb) { - todo!(); + self.div_rem_limb(rhs) } } From 3674e878ff0a03cafa5c0d12400b2382b8185377 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Fri, 15 Dec 2023 16:57:35 -0500 Subject: [PATCH 28/39] Separate out failed prime generation test --- src/hazmat/lucas.rs | 4 ++-- src/hazmat/miller_rabin.rs | 4 ++-- src/hazmat/sieve.rs | 8 +++----- src/presets.rs | 12 ++++++++++-- src/uint_traits.rs | 4 ++-- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 86c9e27..37a7078 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -53,7 +53,7 @@ impl LucasBase for SelfridgeBase { if attempts >= ATTEMPTS_BEFORE_SQRT { let sqrt_n = n.sqrt_vartime(); - if &sqrt_n.wrapping_mul(&sqrt_n) == n { + if &::wrapping_mul(&sqrt_n, &sqrt_n) == n { return Err(Primality::Composite); } } @@ -130,7 +130,7 @@ impl LucasBase for BruteForceBase { if attempts >= ATTEMPTS_BEFORE_SQRT { let sqrt_n = n.sqrt_vartime(); - if &sqrt_n.wrapping_mul(&sqrt_n) == n { + if &::wrapping_mul(&sqrt_n, &sqrt_n) == n { return Err(Primality::Composite); } } diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 16afeee..0680e90 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -41,7 +41,7 @@ impl MillerRabin { let minus_one = -one.clone(); // Find `s` and odd `d` such that `candidate - 1 == 2^s * d`. - let candidate_minus_one = candidate.wrapping_sub(&T::one()); + let candidate_minus_one = ::wrapping_sub(candidate, &T::one()); let s = candidate_minus_one.trailing_zeros(); let d = candidate_minus_one.shr_vartime(s).0; @@ -101,7 +101,7 @@ impl MillerRabin { panic!("No suitable random base possible when `candidate == 3`; use the base 2 test.") } - let range = self.candidate.wrapping_sub(&T::from(4u32)); + let range = ::wrapping_sub(&self.candidate, &T::from(4u32)); let range_nonzero = NonZero::new(range).unwrap(); // This should not overflow as long as `random_mod()` behaves according to the contract // (that is, returns a number within the given range). diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 1889dca..8892cba 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -174,11 +174,9 @@ impl Sieve { } // Find the increment limit. - let max_value = T::one() - .shl_vartime(self.max_bit_length) - .0 - .wrapping_sub(&T::one()); - let incr_limit = max_value.wrapping_sub(&self.base); + let max_value = + ::wrapping_sub(&T::one().shl_vartime(self.max_bit_length).0, &T::one()); + let incr_limit = ::wrapping_sub(&max_value, &self.base); self.incr_limit = if incr_limit > INCR_LIMIT.into() { INCR_LIMIT } else { diff --git a/src/presets.rs b/src/presets.rs index 66d31d1..001d74f 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -232,7 +232,7 @@ mod tests { } fn test_cunningham_chain(length: usize, num: &Uint) { - let mut next = *num; + let mut next: Uint = *num; for i in 0..length { assert!(is_prime(&next)); @@ -241,7 +241,7 @@ mod tests { assert!(is_safe_prime(&next)); } - next = (next << 1).checked_add(&Uint::::ONE).unwrap(); + next = (next.shl(1)).checked_add(&Uint::::ONE).unwrap(); } // The chain ended. @@ -265,7 +265,11 @@ mod tests { assert!(p.bits_vartime() == bit_length); assert!(is_prime(&p)); } + } + #[test] + #[ignore = "Boxed prime generation is not ready"] + fn boxed_prime_generation() { for bit_length in (28u32..=128).step_by(10) { let p: BoxedUint = generate_prime(bit_length, 128); assert!(p.bits_vartime() == bit_length); @@ -280,7 +284,11 @@ mod tests { assert!(p.bits_vartime() == bit_length); assert!(is_safe_prime(&p)); } + } + #[test] + #[ignore = "Boxed prime generation is not ready"] + fn safe_boxed_prime_generation() { for bit_length in (28u32..=128).step_by(10) { let p: BoxedUint = generate_safe_prime(bit_length, 128); assert!(p.bits_vartime() == bit_length); diff --git a/src/uint_traits.rs b/src/uint_traits.rs index db135ec..59cae41 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -103,11 +103,11 @@ impl UintLike for Uint { } fn shr_vartime(&self, shift: u32) -> (Self, ConstChoice) { - Self::shr_vartime(self, shift) + Self::overflowing_shr_vartime(self, shift) } fn shl_vartime(&self, shift: u32) -> (Self, ConstChoice) { - Self::shl_vartime(self, shift) + Self::overflowing_shl_vartime(self, shift) } fn as_limbs(&self) -> &[Limb] { From fa8638301952604a13aaf2044e0f16f39da462bc Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Fri, 15 Dec 2023 23:27:58 -0500 Subject: [PATCH 29/39] Use overflowing_shift to allow overflow --- src/hazmat/sieve.rs | 2 +- src/lib.rs | 2 +- src/uint_traits.rs | 23 +++++++++++++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index 8892cba..a311e05 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -41,7 +41,7 @@ pub fn random_odd_uint( let random = random | T::one(); // Make sure it's the correct bit size - let random = random | T::one().shr(bit_length - 1); + let random = random | T::one().shl_vartime(bit_length - 1).0; random } diff --git a/src/lib.rs b/src/lib.rs index 4f7e8e3..f215c74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +// #![no_std] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![doc = include_str!("../README.md")] #![deny(unsafe_code)] diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 59cae41..19b2139 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -230,19 +230,34 @@ impl UintLike for BoxedUint { /// TODO: BoxedUint::shr_vartime should behave similarly as Uint::shr_vartime; instead of /// returning Option, return (val, choice) fn shr_vartime(&self, shift: u32) -> (Self, ConstChoice) { - self.shr_vartime(shift) + let (val, overflow) = self.overflowing_shr(shift); + if overflow.into() { + (val, ConstChoice::TRUE) + } else { + (val, ConstChoice::FALSE) + } } /// TODO: BoxedUint::shl_vartime should behave similarly as Uint::shr_vartime; instead of /// returning Option, return (val, choice) fn shl_vartime(&self, shift: u32) -> (Self, ConstChoice) { - self.shl_vartime(shift) + let (val, overflow) = self.overflowing_shl(shift); + if overflow.into() { + (val, ConstChoice::TRUE) + } else { + (val, ConstChoice::FALSE) + } } fn random_bits(rng: &mut impl CryptoRngCore, bit_length: u32, bits_precision: u32) -> Self { let random = Self::random(rng, bits_precision) - & Self::max(bits_precision) >> (bits_precision - bit_length); - let random = random | Self::one_with_precision(bits_precision) << (bit_length - 1); + & Self::max(bits_precision) + .overflowing_shr(bits_precision - bit_length) + .0; + let random = random + | Self::one_with_precision(bits_precision) + .overflowing_shl(bit_length - 1) + .0; return random; } From 8edacf82c0ebc0092623888bc10727d473b07934 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Fri, 15 Dec 2023 23:36:08 -0500 Subject: [PATCH 30/39] instantiate one using precision so shifting works --- src/hazmat/jacobi.rs | 2 +- src/hazmat/lucas.rs | 6 ++++-- src/hazmat/miller_rabin.rs | 5 ++++- src/hazmat/sieve.rs | 20 ++++++++++++++------ src/uint_traits.rs | 18 ++++++++++++++++++ 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/hazmat/jacobi.rs b/src/hazmat/jacobi.rs index e680eab..113e6ac 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -109,7 +109,7 @@ pub(crate) fn jacobi_symbol(a: i32, p_long: &T }; // A degenerate case. - if a_pos == 1 || p_long == &T::one() { + if a_pos == 1 || p_long == &T::one_with_precision(p_long.bits_precision()) { return result; } diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 37a7078..e10dd10 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -172,8 +172,10 @@ fn decompose(n: &T) -> (u32, T) { 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.clone().shr_vartime(s).0).checked_add(&T::one())) - .expect("Integer overflow"); + let d = Option::from( + (n.clone().shr_vartime(s).0).checked_add(&T::one_with_precision(n.bits_precision())), + ) + .expect("Integer overflow"); (s, d) } diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index 0680e90..bb9ceb9 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -41,7 +41,10 @@ impl MillerRabin { let minus_one = -one.clone(); // Find `s` and odd `d` such that `candidate - 1 == 2^s * d`. - let candidate_minus_one = ::wrapping_sub(candidate, &T::one()); + let candidate_minus_one = ::wrapping_sub( + candidate, + &T::one_with_precision(candidate.bits_precision()), + ); let s = candidate_minus_one.trailing_zeros(); let d = candidate_minus_one.shr_vartime(s).0; diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index a311e05..d50424f 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -38,10 +38,13 @@ pub fn random_odd_uint( let random = T::random_bits(rng, bit_length, bits_precision); // Make it odd - let random = random | T::one(); + let random = random | T::one_with_precision(bits_precision); // Make sure it's the correct bit size - let random = random | T::one().shl_vartime(bit_length - 1).0; + let random = random + | T::one_with_precision(bits_precision) + .shl_vartime(bit_length - 1) + .0; random } @@ -106,6 +109,7 @@ impl Sieve { }; let mut base = base; + let base_bits_precision = base.bits_precision(); // This is easier than making all the methods generic enough to handle these corner cases. let produces_nothing = max_bit_length < base.bits_vartime() || max_bit_length < 2; @@ -118,7 +122,7 @@ impl Sieve { base = T::from(3u32); } else { // Adjust the base so that we hit odd numbers when incrementing it by 2. - base = base | T::one(); + base = base | T::one_with_precision(base_bits_precision); } // Only calculate residues by primes up to and not including `base`, @@ -174,8 +178,12 @@ impl Sieve { } // Find the increment limit. - let max_value = - ::wrapping_sub(&T::one().shl_vartime(self.max_bit_length).0, &T::one()); + let max_value = ::wrapping_sub( + &T::one_with_precision(self.base.bits_precision()) + .shl_vartime(self.max_bit_length) + .0, + &T::one_with_precision(self.base.bits_precision()), + ); let incr_limit = ::wrapping_sub(&max_value, &self.base); self.incr_limit = if incr_limit > INCR_LIMIT.into() { INCR_LIMIT @@ -228,7 +236,7 @@ impl Sieve { let mut num: T = Option::from(self.base.checked_add(&self.incr.into())).expect("Integer overflow"); if self.safe_primes { - num = num.shl_vartime(1).0 | T::one(); + num = num.shl_vartime(1).0 | T::one_with_precision(self.base.bits_precision()); } Some(num) }; diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 19b2139..e2ba6ff 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -36,6 +36,8 @@ pub trait UintLike: Integer + RandomMod { fn as_limbs(&self) -> &[Limb]; fn as_words(&self) -> &[Word]; fn div_rem_limb(&self, rhs: NonZero) -> (Self, Limb); + fn one_with_precision(bits_precision: u32) -> Self; + fn zero_with_precision(bits_precision: u32) -> Self; } #[allow(missing_docs)] @@ -131,6 +133,14 @@ impl UintLike for Uint { fn div_rem_limb(&self, rhs: NonZero) -> (Self, Limb) { self.div_rem_limb(rhs) } + + fn one_with_precision(_bits_precision: u32) -> Self { + Self::ONE + } + + fn zero_with_precision(_bits_precision: u32) -> Self { + Self::ZERO + } } impl UintModLike for DynResidue { @@ -283,6 +293,14 @@ impl UintLike for BoxedUint { fn div_rem_limb(&self, rhs: NonZero) -> (Self, Limb) { self.div_rem_limb(rhs) } + + fn one_with_precision(bits_precision: u32) -> Self { + Self::one_with_precision(bits_precision) + } + + fn zero_with_precision(bits_precision: u32) -> Self { + Self::zero_with_precision(bits_precision) + } } impl UintModLike for BoxedResidue { From 4c800d8f5617de7bc2823d80c5f33f5a2e430bfb Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Fri, 15 Dec 2023 23:37:10 -0500 Subject: [PATCH 31/39] Widen raw before instantiating BoxedResidue --- src/uint_traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uint_traits.rs b/src/uint_traits.rs index e2ba6ff..3948d98 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -312,7 +312,7 @@ impl UintModLike for BoxedResidue { } fn new(raw: &Self::Raw, params: &Self::Params) -> Self { - Self::new(raw.clone(), params.clone()) + Self::new(raw.widen(params.bits_precision()).clone(), params.clone()) } fn zero(params: &Self::Params) -> Self { From 83d34e7c2051b0a3419af1be810da638fe8a4b41 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Fri, 15 Dec 2023 23:37:50 -0500 Subject: [PATCH 32/39] div_by_2 has been implemented --- src/uint_traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 3948d98..aa4a373 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -329,6 +329,6 @@ impl UintModLike for BoxedResidue { /// TODO: BoxedUint does not implement div_by_2 fn div_by_2(&self) -> Self { - todo!(); + self.div_by_2() } } From acc1f460cb88f455ad4a6e7d742ca49673fc6480 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Sat, 16 Dec 2023 00:12:59 -0500 Subject: [PATCH 33/39] Widen after calling from --- src/hazmat/lucas.rs | 13 ++++++++++--- src/hazmat/miller_rabin.rs | 14 ++++++++++---- src/hazmat/sieve.rs | 32 ++++++++++++++++++-------------- src/lib.rs | 2 +- src/presets.rs | 8 +++++--- src/uint_traits.rs | 12 ++++++++++++ 6 files changed, 56 insertions(+), 25 deletions(-) diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index e10dd10..9c3050b 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -70,7 +70,7 @@ 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 != &T::from(d.abs_diff(0)) { + if n != &T::from(d.abs_diff(0)).widen(n.bits_precision()) { return Err(Primality::Composite); } } @@ -147,7 +147,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 == &T::from(p + 2) { + let primality = if n == &T::from(p + 2).widen(n.bits_precision()) { Primality::Prime } else { Primality::Composite @@ -300,7 +300,10 @@ pub fn lucas_test( // 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 && candidate.gcd_small(abs_q) != 1 && candidate > &T::from(abs_q) { + if abs_q != 1 + && candidate.gcd_small(abs_q) != 1 + && candidate > &T::from(abs_q).widen(candidate.bits_precision()) + { return Primality::Composite; } @@ -324,6 +327,7 @@ pub fn lucas_test( let q = if q_is_one { one.clone() } else { + // No need to widen; widen happens at T::Modular::new let abs_q = ::Modular::new(&T::from(q.abs_diff(0)), ¶ms); if q < 0 { -abs_q @@ -368,6 +372,9 @@ pub fn lucas_test( let q_2k = qk.square(); let u_2k = uk * &vk; + + dbg!(i, &qk, &vk); + let v_2k = vk.square() - &(qk.clone() + &qk); uk = u_2k; diff --git a/src/hazmat/miller_rabin.rs b/src/hazmat/miller_rabin.rs index bb9ceb9..64a16fd 100644 --- a/src/hazmat/miller_rabin.rs +++ b/src/hazmat/miller_rabin.rs @@ -89,7 +89,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(2u32).widen(self.candidate.bits_precision())) } /// Perform a Miller-Rabin check with a random base (in the range `[3, candidate-2]`) @@ -104,12 +104,18 @@ impl MillerRabin { panic!("No suitable random base possible when `candidate == 3`; use the base 2 test.") } - let range = ::wrapping_sub(&self.candidate, &T::from(4u32)); + let range = ::wrapping_sub( + &self.candidate, + &T::from(4u32).widen(self.candidate.bits_precision()), + ); let range_nonzero = NonZero::new(range).unwrap(); // 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(T::random_mod(rng, &range_nonzero).checked_add(&T::from(3u32))) - .expect("Integer overflow"); + let random = Option::from( + T::random_mod(rng, &range_nonzero) + .checked_add(&T::from(3u32).widen(self.candidate.bits_precision())), + ) + .expect("Integer overflow"); self.test(&random) } } diff --git a/src/hazmat/sieve.rs b/src/hazmat/sieve.rs index d50424f..bc3d73d 100644 --- a/src/hazmat/sieve.rs +++ b/src/hazmat/sieve.rs @@ -117,9 +117,9 @@ impl Sieve { // Add the exception to the produced candidates - the only one that doesn't fit // the general pattern of incrementing the base by 2. let mut starts_from_exception = false; - if base <= T::from(2u32) { + if base <= T::from(2u32).widen(base.bits_precision()) { starts_from_exception = true; - base = T::from(3u32); + base = T::from(3u32).widen(base.bits_precision()); } else { // Adjust the base so that we hit odd numbers when incrementing it by 2. base = base | T::one_with_precision(base_bits_precision); @@ -128,17 +128,18 @@ impl Sieve { // Only calculate residues by primes up to and not including `base`, // because when we only have the resiude, // we cannot distinguish between a prime itself and a multiple of that prime. - let residues_len = if T::from(SMALL_PRIMES[SMALL_PRIMES.len() - 1]) >= base { - SMALL_PRIMES - .iter() - .enumerate() - .find(|(_i, p)| T::from(**p) >= base) - .map(|(i, _p)| i) - .unwrap_or(SMALL_PRIMES.len()) - } else { - // This will be the majority of use cases - SMALL_PRIMES.len() - }; + let residues_len = + if T::from(SMALL_PRIMES[SMALL_PRIMES.len() - 1]).widen(base.bits_precision()) >= base { + SMALL_PRIMES + .iter() + .enumerate() + .find(|(_i, p)| T::from(**p).widen(base.bits_precision()) >= base) + .map(|(i, _p)| i) + .unwrap_or(SMALL_PRIMES.len()) + } else { + // This will be the majority of use cases + SMALL_PRIMES.len() + }; Self { base, @@ -254,7 +255,10 @@ impl Sieve { if self.starts_from_exception { self.starts_from_exception = false; - return Some(T::from(if self.safe_primes { 5u32 } else { 2u32 })); + return Some( + T::from(if self.safe_primes { 5u32 } else { 2u32 }) + .widen(self.base.bits_precision()), + ); } // Main loop diff --git a/src/lib.rs b/src/lib.rs index f215c74..8fec413 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// #![no_std] +// #![no_std] // TODO: uncomment this after finishing debugging #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![doc = include_str!("../README.md")] #![deny(unsafe_code)] diff --git a/src/presets.rs b/src/presets.rs index 001d74f..33ad4c5 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -131,7 +131,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(2u32).widen(num.bits_precision()) { return true; } if num.is_even().into() { @@ -148,10 +148,12 @@ pub fn is_safe_prime_with_rng(rng: &mut impl CryptoRngCore, num: &T // 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(5u32).widen(num.bits_precision()) { return true; } - if T::from(3u32) & num.clone() != T::from(3u32) { + if T::from(3u32).widen(num.bits_precision()) & num.clone() + != T::from(3u32).widen(num.bits_precision()) + { return false; } diff --git a/src/uint_traits.rs b/src/uint_traits.rs index aa4a373..659e8be 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -36,8 +36,12 @@ pub trait UintLike: Integer + RandomMod { fn as_limbs(&self) -> &[Limb]; fn as_words(&self) -> &[Word]; fn div_rem_limb(&self, rhs: NonZero) -> (Self, Limb); + + // This is necessary for making sure that every BoxedUint has the same bits_precision + // TODO: remove them after BoxedUint can work with different bits_precision fn one_with_precision(bits_precision: u32) -> Self; fn zero_with_precision(bits_precision: u32) -> Self; + fn widen(&self, bits_precision: u32) -> Self; } #[allow(missing_docs)] @@ -141,6 +145,10 @@ impl UintLike for Uint { fn zero_with_precision(_bits_precision: u32) -> Self { Self::ZERO } + + fn widen(&self, _bits_precision: u32) -> Self { + *self + } } impl UintModLike for DynResidue { @@ -301,6 +309,10 @@ impl UintLike for BoxedUint { fn zero_with_precision(bits_precision: u32) -> Self { Self::zero_with_precision(bits_precision) } + + fn widen(&self, bits_precision: u32) -> Self { + self.widen(bits_precision) + } } impl UintModLike for BoxedResidue { From df0ec9b7fd959299a8e1142e7d0d1bc1a1aa40f0 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Sat, 16 Dec 2023 01:31:02 -0500 Subject: [PATCH 34/39] Refresh seems to address the problem with BoxedResidue::square; now generate_prime and generate_safe_prime works with BoxedUint --- Cargo.toml | 5 ++--- src/hazmat/lucas.rs | 20 ++++++++++---------- src/presets.rs | 2 -- src/uint_traits.rs | 13 +++++++++++++ 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 520ea65..9161687 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,13 @@ categories = ["cryptography", "no-std"] rust-version = "1.65" [dependencies] -# crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint.git", default-features = false, features = ["rand_core", "alloc"], rev = "4bf6932cee08af70d0a05625b540242522cc77f3" } -crypto-bigint = { path = "../crypto-bigint", default-features = false, features = ["rand_core", "alloc"] } +crypto-bigint = { git = "https://github.com/xuganyu96/crypto-bigint.git", default-features = false, features = ["rand_core", "alloc"], rev = "9d730ab" } 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 } +rand_chacha = "0.3" [dev-dependencies] -rand_chacha = "0.3" criterion = { version = "0.4", features = ["html_reports"] } num-modular = { version = "0.5", features = ["num-bigint"] } num-bigint = "0.4" diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 9c3050b..8426138 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -370,12 +370,12 @@ pub fn lucas_test( for i in (0..d.bits_vartime()).rev() { // k' = k * 2 - let q_2k = qk.square(); - let u_2k = uk * &vk; + let q_2k = qk.square().refresh(); + let u_2k = (uk * &vk).refresh(); dbg!(i, &qk, &vk); - let v_2k = vk.square() - &(qk.clone() + &qk); + let v_2k = vk.square().refresh() - &(qk.clone() + &qk); uk = u_2k; vk = v_2k; @@ -387,16 +387,16 @@ pub fn lucas_test( let (p_uk, p_vk) = if p_is_one { (uk.clone(), vk.clone()) } else { - (p.clone() * &uk, p.clone() * &vk) + ((p.clone() * &uk).refresh(), (p.clone() * &vk).refresh()) }; - let u_k1 = (p_uk + &vk).div_by_2(); - let v_k1 = (d_m.clone() * &uk + &p_vk).div_by_2(); + let u_k1 = (p_uk + &vk).div_by_2().refresh(); + let v_k1 = ((d_m.clone() * &uk).refresh() + &p_vk).div_by_2().refresh(); let q_k1 = qk * &q; uk = u_k1; vk = v_k1; - qk = q_k1; + qk = q_k1.refresh(); } } @@ -450,21 +450,21 @@ pub fn lucas_test( // k' = 2k // V_{k'} = V_k^2 - 2 Q^k - vk = vk.square() - &qk - &qk; + vk = vk.square().refresh() - &qk - &qk; if check != LucasCheck::LucasV && vk == zero { return Primality::ProbablyPrime; } if !q_is_one { - qk = qk.square(); + qk = qk.square().refresh(); } } if check == LucasCheck::LucasV { // At this point vk = V_{d * 2^(s-1)}. // Double the index again: - vk = vk.square() - &qk - &qk; // now vk = V_{d * 2^s} = V_{n+1} + vk = vk.square().refresh() - &qk - &qk; // now vk = V_{d * 2^s} = V_{n+1} // Lucas-V check[^Baillie2021]: if V_{n+1} == 2 Q, report `n` as prime. if vk == q.clone() + &q { diff --git a/src/presets.rs b/src/presets.rs index 33ad4c5..117e1b8 100644 --- a/src/presets.rs +++ b/src/presets.rs @@ -270,7 +270,6 @@ mod tests { } #[test] - #[ignore = "Boxed prime generation is not ready"] fn boxed_prime_generation() { for bit_length in (28u32..=128).step_by(10) { let p: BoxedUint = generate_prime(bit_length, 128); @@ -289,7 +288,6 @@ mod tests { } #[test] - #[ignore = "Boxed prime generation is not ready"] fn safe_boxed_prime_generation() { for bit_length in (28u32..=128).step_by(10) { let p: BoxedUint = generate_safe_prime(bit_length, 128); diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 659e8be..626b949 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -66,6 +66,8 @@ pub trait UintModLike: fn one(params: &Self::Params) -> Self; fn square(&self) -> Self; fn div_by_2(&self) -> Self; + + fn refresh(&self) -> Self; } /// Uint impls @@ -178,6 +180,10 @@ impl UintModLike for DynResidue { fn div_by_2(&self) -> Self { Self::div_by_2(self) } + + fn refresh(&self) -> Self { + *self + } } impl UintLike for BoxedUint { @@ -343,4 +349,11 @@ impl UintModLike for BoxedResidue { fn div_by_2(&self) -> Self { self.div_by_2() } + + // TODO: BoxedResidue::square might be buggy + // Check https://github.com/RustCrypto/crypto-bigint/issues/441 for details + // Calling "retrieve" and reinstantiate seems to help + fn refresh(&self) -> Self { + Self::new(self.retrieve(), self.params().clone()) + } } From 49d34870a4644b0e6969af4a835336548689e274 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Sat, 16 Dec 2023 01:35:52 -0500 Subject: [PATCH 35/39] Removed unnecessary dbg! --- src/hazmat/lucas.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 8426138..5ba6bae 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -373,8 +373,6 @@ pub fn lucas_test( let q_2k = qk.square().refresh(); let u_2k = (uk * &vk).refresh(); - dbg!(i, &qk, &vk); - let v_2k = vk.square().refresh() - &(qk.clone() + &qk); uk = u_2k; From 24e9dde1ff032a1f5ceae3613343d28e4c421f75 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Sat, 16 Dec 2023 18:11:32 -0500 Subject: [PATCH 36/39] Removed refresh as BoxedUint::{square, mul} will return reduced form --- Cargo.toml | 2 +- src/hazmat/lucas.rs | 20 ++++++++++---------- src/uint_traits.rs | 13 ------------- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9161687..e43a88b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ categories = ["cryptography", "no-std"] rust-version = "1.65" [dependencies] -crypto-bigint = { git = "https://github.com/xuganyu96/crypto-bigint.git", default-features = false, features = ["rand_core", "alloc"], rev = "9d730ab" } +crypto-bigint = { git = "https://github.com/xuganyu96/crypto-bigint.git", default-features = false, features = ["rand_core", "alloc"], rev = "f433d38" } 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/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 5ba6bae..877d1e5 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -370,10 +370,10 @@ pub fn lucas_test( for i in (0..d.bits_vartime()).rev() { // k' = k * 2 - let q_2k = qk.square().refresh(); - let u_2k = (uk * &vk).refresh(); + let q_2k = qk.square(); + let u_2k = uk * &vk; - let v_2k = vk.square().refresh() - &(qk.clone() + &qk); + let v_2k = vk.square() - &(qk.clone() + &qk); uk = u_2k; vk = v_2k; @@ -385,16 +385,16 @@ pub fn lucas_test( let (p_uk, p_vk) = if p_is_one { (uk.clone(), vk.clone()) } else { - ((p.clone() * &uk).refresh(), (p.clone() * &vk).refresh()) + ((p.clone() * &uk), (p.clone() * &vk)) }; - let u_k1 = (p_uk + &vk).div_by_2().refresh(); - let v_k1 = ((d_m.clone() * &uk).refresh() + &p_vk).div_by_2().refresh(); + let u_k1 = (p_uk + &vk).div_by_2(); + let v_k1 = ((d_m.clone() * &uk) + &p_vk).div_by_2(); let q_k1 = qk * &q; uk = u_k1; vk = v_k1; - qk = q_k1.refresh(); + qk = q_k1; } } @@ -448,21 +448,21 @@ pub fn lucas_test( // k' = 2k // V_{k'} = V_k^2 - 2 Q^k - vk = vk.square().refresh() - &qk - &qk; + vk = vk.square() - &qk - &qk; if check != LucasCheck::LucasV && vk == zero { return Primality::ProbablyPrime; } if !q_is_one { - qk = qk.square().refresh(); + qk = qk.square(); } } if check == LucasCheck::LucasV { // At this point vk = V_{d * 2^(s-1)}. // Double the index again: - vk = vk.square().refresh() - &qk - &qk; // now vk = V_{d * 2^s} = V_{n+1} + vk = vk.square() - &qk - &qk; // now vk = V_{d * 2^s} = V_{n+1} // Lucas-V check[^Baillie2021]: if V_{n+1} == 2 Q, report `n` as prime. if vk == q.clone() + &q { diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 626b949..659e8be 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -66,8 +66,6 @@ pub trait UintModLike: fn one(params: &Self::Params) -> Self; fn square(&self) -> Self; fn div_by_2(&self) -> Self; - - fn refresh(&self) -> Self; } /// Uint impls @@ -180,10 +178,6 @@ impl UintModLike for DynResidue { fn div_by_2(&self) -> Self { Self::div_by_2(self) } - - fn refresh(&self) -> Self { - *self - } } impl UintLike for BoxedUint { @@ -349,11 +343,4 @@ impl UintModLike for BoxedResidue { fn div_by_2(&self) -> Self { self.div_by_2() } - - // TODO: BoxedResidue::square might be buggy - // Check https://github.com/RustCrypto/crypto-bigint/issues/441 for details - // Calling "retrieve" and reinstantiate seems to help - fn refresh(&self) -> Self { - Self::new(self.retrieve(), self.params().clone()) - } } From bf76fe5d3a5c32215c86e2e4c5fe34e98a482827 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Sat, 16 Dec 2023 18:12:51 -0500 Subject: [PATCH 37/39] Better clarity --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e43a88b..48a89ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ categories = ["cryptography", "no-std"] rust-version = "1.65" [dependencies] -crypto-bigint = { git = "https://github.com/xuganyu96/crypto-bigint.git", default-features = false, features = ["rand_core", "alloc"], rev = "f433d38" } +crypto-bigint = { git = "https://github.com/xuganyu96/crypto-bigint.git", default-features = false, features = ["rand_core", "alloc"], branch = "boxed-uintlike-coverage" } 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 } From 459089d04d7abd5e938ac03dfcc6df140bf823f0 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Sat, 16 Dec 2023 18:20:35 -0500 Subject: [PATCH 38/39] BoxedUint test for lucas --- src/hazmat/lucas.rs | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/hazmat/lucas.rs b/src/hazmat/lucas.rs index 877d1e5..42c0bdb 100644 --- a/src/hazmat/lucas.rs +++ b/src/hazmat/lucas.rs @@ -478,7 +478,7 @@ mod tests { use alloc::format; - use crypto_bigint::{Uint, U128, U64}; + use crypto_bigint::{BoxedUint, Uint, U128, U64}; #[cfg(feature = "tests-exhaustive")] use num_prime::nt_funcs::is_prime64; @@ -512,6 +512,12 @@ mod tests { assert_eq!(SelfridgeBase.generate(&num), Err(Primality::Composite)); assert_eq!(AStarBase.generate(&num), Err(Primality::Composite)); assert_eq!(BruteForceBase.generate(&num), Err(Primality::Composite)); + + // The same test, but with BoxedUint + let num = BoxedUint::from(131u32).widen(64).square(); + assert_eq!(SelfridgeBase.generate(&num), Err(Primality::Composite)); + assert_eq!(AStarBase.generate(&num), Err(Primality::Composite)); + assert_eq!(BruteForceBase.generate(&num), Err(Primality::Composite)); } #[test] @@ -520,7 +526,11 @@ mod tests { assert_eq!( BruteForceBase.generate(&U64::from(5u32)), Err(Primality::Prime) - ) + ); + assert_eq!( + BruteForceBase.generate(&BoxedUint::from(5u32).widen(64)), + Err(Primality::Prime) + ); } #[test] @@ -530,6 +540,10 @@ mod tests { lucas_test(&U64::from(6u32), SelfridgeBase, LucasCheck::Strong), Primality::Composite ); + assert_eq!( + lucas_test(&BoxedUint::from(6u32), SelfridgeBase, LucasCheck::Strong), + Primality::Composite + ); } #[test] @@ -552,6 +566,14 @@ mod tests { lucas_test(&U64::from(15u32), TestBase, LucasCheck::Strong), Primality::Composite ); + assert_eq!( + lucas_test( + &BoxedUint::from(15u32).widen(64), + TestBase, + LucasCheck::Strong + ), + Primality::Composite + ); } #[test] @@ -559,6 +581,19 @@ mod tests { 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(&BoxedUint::from(u128::MAX)), + (128, BoxedUint::one_with_precision(128)) + ); + assert_eq!( + decompose(&BoxedUint::one_with_precision(128)), + (1, BoxedUint::one_with_precision(128)) + ); + assert_eq!( + decompose(&BoxedUint::from(7766015u32)), + (15, BoxedUint::from(237u32)) + ); } fn is_slpsp(num: u32) -> bool { From 7d7563a1a94ca81117cb95a5df77187db99fead1 Mon Sep 17 00:00:00 2001 From: "Ganyu (Bruce) Xu" Date: Mon, 18 Dec 2023 13:12:02 -0500 Subject: [PATCH 39/39] Adapted to newest crypto-bigint APIs --- src/hazmat/gcd.rs | 2 +- src/hazmat/jacobi.rs | 2 +- src/hazmat/precomputed.rs | 3 ++- src/uint_traits.rs | 22 ++++++++++++++++++++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/hazmat/gcd.rs b/src/hazmat/gcd.rs index d64fd0d..2a793fe 100644 --- a/src/hazmat/gcd.rs +++ b/src/hazmat/gcd.rs @@ -49,7 +49,7 @@ pub(crate) fn gcd(n: &T, 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 113e6ac..2f4d197 100644 --- a/src/hazmat/jacobi.rs +++ b/src/hazmat/jacobi.rs @@ -169,7 +169,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/precomputed.rs b/src/hazmat/precomputed.rs index b9e2206..055dd26 100644 --- a/src/hazmat/precomputed.rs +++ b/src/hazmat/precomputed.rs @@ -151,7 +151,8 @@ 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 { - let limb = NonZero::::const_new(Limb(SMALL_PRIMES[i] as Word)).0; + let limb = + NonZero::::const_new(Limb(SMALL_PRIMES[i] as Word)).expect("Prime is zero"); arr[i] = Reciprocal::new(limb); i += 1; } diff --git a/src/uint_traits.rs b/src/uint_traits.rs index 659e8be..90dd8db 100644 --- a/src/uint_traits.rs +++ b/src/uint_traits.rs @@ -109,11 +109,29 @@ impl UintLike for Uint { } fn shr_vartime(&self, shift: u32) -> (Self, ConstChoice) { - Self::overflowing_shr_vartime(self, shift) + let opt = self.overflowing_shr_vartime(shift); + if opt.is_none().into() { + // opt.is_none() indicates overflow + (Self::ZERO, ConstChoice::TRUE) + } else { + ( + opt.expect("attempt to right shift with overflow"), + ConstChoice::FALSE, + ) + } } fn shl_vartime(&self, shift: u32) -> (Self, ConstChoice) { - Self::overflowing_shl_vartime(self, shift) + let opt = self.overflowing_shl_vartime(shift); + if opt.is_none().into() { + // opt.is_none() indicates overflow + (Self::ZERO, ConstChoice::TRUE) + } else { + ( + opt.expect("attempt to left shift with overflow"), + ConstChoice::FALSE, + ) + } } fn as_limbs(&self) -> &[Limb] {