Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BoxedUint support #40

Merged
merged 5 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Bumped `crypto-bigint` to 0.6.0-pre.6. ([#38])
- Bumped MSRV to 1.73. (#[38])
- `MillerRabin::new()` takes an `Odd`-wrapped integer. `random_odd_uint()` is renamed to `random_odd_integer()`, takes a `NonZeroU32` for `bit_length`, and returns an `Odd`-wrapped integer. `LucasBase::generate()` takes an `Odd`-wrapped integer. `lucas_test` takes an `Odd`-wrapped integer. (#[38])
- All bit length-type parameters take `u32` instead of `usize`. (#[38])
- All the API is based on the `Integer` trait instead of `Uint` specifically. (#[38])
- Bumped `crypto-bigint` to 0.6.0-pre.7. ([#40])
- Bumped MSRV to 1.73. ([#36])
- `MillerRabin::new()` takes an `Odd`-wrapped integer by value. `random_odd_uint()` returns an `Odd`-wrapped integer. `LucasBase::generate()` takes an `Odd`-wrapped integer. `lucas_test` takes an `Odd`-wrapped integer. ([#36])
- `random_odd_uint()` is renamed to `random_odd_integer()`, takes a `NonZeroU32` for `bit_length`. ([#38])
- All bit length-type parameters take `u32` instead of `usize`. ([#36])
- All the API is based on the `Integer` trait instead of `Uint` specifically. ([#38])
- High-level generation/checking functions take an additional `bits_precision` argument. ([#40])


[#36]: https://github.com/entropyxyz/crypto-primes/pull/36
[#38]: https://github.com/entropyxyz/crypto-primes/pull/38
[#40]: https://github.com/entropyxyz/crypto-primes/pull/40


## [0.5.0] - 2023-08-20
Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ categories = ["cryptography", "no-std"]
rust-version = "1.73"

[dependencies]
crypto-bigint = { version = "0.6.0-pre.6", default-features = false, features = ["rand_core"] }
crypto-bigint = { version = "0.6.0-pre.7", default-features = false, features = ["rand_core"] }
rand_core = { version = "0.6.4", default-features = false }
openssl = { version = "0.10.39", optional = true, features = ["vendored"] }
rug = { version = "1.18", default-features = false, features = ["integer"], optional = true }

[dev-dependencies]
# need `crypto-bigint` with `alloc` to test `BoxedUint`
crypto-bigint = { version = "0.6.0-pre.7", default-features = false, features = ["alloc"] }
rand_chacha = "0.3"
criterion = { version = "0.4", features = ["html_reports"] }
num-modular = { version = "0.5", features = ["num-bigint"] }
Expand Down
50 changes: 31 additions & 19 deletions benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::num::NonZeroU32;

use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use crypto_bigint::{nlimbs, Integer, Odd, RandomBits, Uint, U1024, U128, U256};
use crypto_bigint::{nlimbs, BoxedUint, Integer, Odd, RandomBits, Uint, U1024, U128, U256};
use rand_chacha::ChaCha8Rng;
use rand_core::{CryptoRngCore, OsRng, SeedableRng};

Expand All @@ -27,12 +27,13 @@ fn make_rng() -> ChaCha8Rng {
fn random_odd_uint<T: RandomBits + Integer>(
rng: &mut impl CryptoRngCore,
bit_length: u32,
bits_precision: u32,
) -> Odd<T> {
random_odd_integer::<T>(rng, NonZeroU32::new(bit_length).unwrap())
random_odd_integer::<T>(rng, NonZeroU32::new(bit_length).unwrap(), bits_precision)
}

fn make_sieve<const L: usize>(rng: &mut impl CryptoRngCore) -> Sieve<Uint<L>> {
let start = random_odd_uint::<Uint<L>>(rng, Uint::<L>::BITS);
let start = random_odd_uint::<Uint<L>>(rng, Uint::<L>::BITS, Uint::<L>::BITS);
Sieve::new(&start, NonZeroU32::new(Uint::<L>::BITS).unwrap(), false)
}

Expand All @@ -45,12 +46,12 @@ fn bench_sieve(c: &mut Criterion) {
let mut group = c.benchmark_group("Sieve");

group.bench_function("(U128) random start", |b| {
b.iter(|| random_odd_uint::<U128>(&mut OsRng, 128))
b.iter(|| random_odd_uint::<U128>(&mut OsRng, 128, 128))
});

group.bench_function("(U128) creation", |b| {
b.iter_batched(
|| random_odd_uint::<U128>(&mut OsRng, 128),
|| random_odd_uint::<U128>(&mut OsRng, 128, 128),
|start| Sieve::new(start.as_ref(), NonZeroU32::new(128).unwrap(), false),
BatchSize::SmallInput,
)
Expand All @@ -66,12 +67,12 @@ fn bench_sieve(c: &mut Criterion) {
});

group.bench_function("(U1024) random start", |b| {
b.iter(|| random_odd_uint::<U1024>(&mut OsRng, 1024))
b.iter(|| random_odd_uint::<U1024>(&mut OsRng, 1024, 1024))
});

group.bench_function("(U1024) creation", |b| {
b.iter_batched(
|| random_odd_uint::<U1024>(&mut OsRng, 1024),
|| random_odd_uint::<U1024>(&mut OsRng, 1024, 1024),
|start| Sieve::new(start.as_ref(), NonZeroU32::new(1024).unwrap(), false),
BatchSize::SmallInput,
)
Expand All @@ -93,7 +94,7 @@ fn bench_miller_rabin(c: &mut Criterion) {

group.bench_function("(U128) creation", |b| {
b.iter_batched(
|| random_odd_uint::<U128>(&mut OsRng, 128),
|| random_odd_uint::<U128>(&mut OsRng, 128, 128),
|n| MillerRabin::new(&n),
BatchSize::SmallInput,
)
Expand All @@ -109,7 +110,7 @@ fn bench_miller_rabin(c: &mut Criterion) {

group.bench_function("(U1024) creation", |b| {
b.iter_batched(
|| random_odd_uint::<U1024>(&mut OsRng, 1024),
|| random_odd_uint::<U1024>(&mut OsRng, 1024, 1024),
|n| MillerRabin::new(&n),
BatchSize::SmallInput,
)
Expand Down Expand Up @@ -202,39 +203,50 @@ fn bench_presets(c: &mut Criterion) {

group.bench_function("(U128) Prime test", |b| {
b.iter_batched(
|| random_odd_uint::<U128>(&mut OsRng, 128),
|| random_odd_uint::<U128>(&mut OsRng, 128, 128),
|num| is_prime_with_rng(&mut OsRng, num.as_ref()),
BatchSize::SmallInput,
)
});

group.bench_function("(U128) Safe prime test", |b| {
b.iter_batched(
|| random_odd_uint::<U128>(&mut OsRng, 128),
|| random_odd_uint::<U128>(&mut OsRng, 128, 128),
|num| is_safe_prime_with_rng(&mut OsRng, num.as_ref()),
BatchSize::SmallInput,
)
});

let mut rng = make_rng();
group.bench_function("(U128) Random prime", |b| {
b.iter(|| generate_prime_with_rng::<U128>(&mut rng, 128))
b.iter(|| generate_prime_with_rng::<U128>(&mut rng, 128, 128))
});

let mut rng = make_rng();
group.bench_function("(U1024) Random prime", |b| {
b.iter(|| generate_prime_with_rng::<U1024>(&mut rng, 1024))
b.iter(|| generate_prime_with_rng::<U1024>(&mut rng, 1024, 1024))
});

let mut rng = make_rng();
group.bench_function("(U128) Random safe prime", |b| {
b.iter(|| generate_safe_prime_with_rng::<U128>(&mut rng, 128))
b.iter(|| generate_safe_prime_with_rng::<U128>(&mut rng, 128, 128))
});

group.sample_size(20);
let mut rng = make_rng();
group.bench_function("(U1024) Random safe prime", |b| {
b.iter(|| generate_safe_prime_with_rng::<U1024>(&mut rng, 1024))
b.iter(|| generate_safe_prime_with_rng::<U1024>(&mut rng, 1024, 1024))
});

let mut rng = make_rng();
group.bench_function("(Boxed128) Random safe prime", |b| {
b.iter(|| generate_safe_prime_with_rng::<BoxedUint>(&mut rng, 128, 128))
});

group.sample_size(20);
let mut rng = make_rng();
group.bench_function("(Boxed1024) Random safe prime", |b| {
b.iter(|| generate_safe_prime_with_rng::<BoxedUint>(&mut rng, 1024, 1024))
});

group.finish();
Expand All @@ -244,19 +256,19 @@ fn bench_presets(c: &mut Criterion) {

let mut rng = make_rng();
group.bench_function("(U128) Random safe prime", |b| {
b.iter(|| generate_safe_prime_with_rng::<U128>(&mut rng, 128))
b.iter(|| generate_safe_prime_with_rng::<U128>(&mut rng, 128, 128))
});

// The performance should scale with the prime size, not with the Uint size.
// So we should strive for this test's result to be as close as possible
// to that of the previous one and as far away as possible from the next one.
group.bench_function("(U256) Random 128 bit safe prime", |b| {
b.iter(|| generate_safe_prime_with_rng::<U256>(&mut rng, 128))
b.iter(|| generate_safe_prime_with_rng::<U256>(&mut rng, 128, 256))
});

// The upper bound for the previous test.
group.bench_function("(U256) Random 256 bit safe prime", |b| {
b.iter(|| generate_safe_prime_with_rng::<U256>(&mut rng, 256))
b.iter(|| generate_safe_prime_with_rng::<U256>(&mut rng, 256, 256))
});

group.finish();
Expand All @@ -267,7 +279,7 @@ fn bench_gmp(c: &mut Criterion) {
let mut group = c.benchmark_group("GMP");

fn random<const L: usize>(rng: &mut impl CryptoRngCore) -> GmpInteger {
let num = random_odd_uint::<Uint<L>>(rng, Uint::<L>::BITS).get();
let num = random_odd_uint::<Uint<L>>(rng, Uint::<L>::BITS, Uint::<L>::BITS).get();
GmpInteger::from_digits(num.as_words(), Order::Lsf)
}

Expand Down
2 changes: 1 addition & 1 deletion src/hazmat/jacobi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub(crate) fn jacobi_symbol_vartime<T: Integer>(
};

// A degenerate case.
if abs_a == 1 || p_long.as_ref() == &T::one() {
if abs_a == 1 || p_long.as_ref() == &T::one_like(p_long) {
return result;
}

Expand Down
20 changes: 12 additions & 8 deletions src/hazmat/lucas.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Lucas primality test.
use crypto_bigint::{Integer, Monty, Odd, Square, Word};
use crypto_bigint::{Integer, Limb, Monty, Odd, Square, Word};

use super::{
gcd::gcd_vartime,
Expand Down Expand Up @@ -161,7 +161,7 @@ impl LucasBase for BruteForceBase {
// Since the loop proceeds in increasing P and starts with P - 2 == 1,
// the shared prime factor must be P + 2.
// If P + 2 == n, then n is prime; otherwise P + 2 is a proper factor of n.
let primality = if n.as_ref() == &T::from(p + 2) {
let primality = if n.as_ref() == &T::from_limb_like(Limb::from(p + 2), n.as_ref()) {
Primality::Prime
} else {
Primality::Composite
Expand All @@ -182,6 +182,7 @@ fn decompose<T: Integer>(n: &Odd<T>) -> (u32, Odd<T>) {
// Need to be careful here since `n + 1` can overflow.
// Instead of adding 1 and counting trailing 0s, we count trailing ones on the original `n`.

let one = T::one_like(n);
let s = n.trailing_ones_vartime();
let d = if s < n.bits_precision() {
// The shift won't overflow because of the check above.
Expand All @@ -190,10 +191,10 @@ fn decompose<T: Integer>(n: &Odd<T>) -> (u32, Odd<T>) {
n.as_ref()
.overflowing_shr_vartime(s)
.expect("shift should be within range by construction")
.checked_add(&T::one())
.checked_add(&one)
.expect("addition should not overflow by construction")
} else {
T::one()
one
};

(s, Odd::new(d).expect("`d` should be odd by construction"))
Expand Down Expand Up @@ -293,6 +294,9 @@ pub fn lucas_test<T: Integer>(
// R. Crandall, C. Pomerance, "Prime numbers: a computational perspective",
// 2nd ed., Springer (2005) (ISBN: 0-387-25282-7, 978-0387-25282-7)

// A word-to-big integer conversion helper
let to_integer = |x: Word| T::from_limb_like(Limb::from(x), candidate.as_ref());

// Find the base for the Lucas sequence.
let (p, abs_q, q_is_negative) = match base.generate(candidate) {
Ok(pq) => pq,
Expand Down Expand Up @@ -328,7 +332,7 @@ pub fn lucas_test<T: Integer>(
// it does not noticeably affect the performance.
if abs_q != 1
&& gcd_vartime(candidate.as_ref(), abs_q) != 1
&& candidate.as_ref() > &T::from(abs_q)
&& candidate.as_ref() > &to_integer(abs_q)
{
return Primality::Composite;
}
Expand All @@ -351,7 +355,7 @@ pub fn lucas_test<T: Integer>(
let q = if q_is_one {
one.clone()
} else {
let abs_q = <T as Integer>::Monty::new(T::from(abs_q), params.clone());
let abs_q = <T as Integer>::Monty::new(to_integer(abs_q), params.clone());
if q_is_negative {
-abs_q
} else {
Expand All @@ -364,7 +368,7 @@ pub fn lucas_test<T: Integer>(
let p = if p_is_one {
one.clone()
} else {
<T as Integer>::Monty::new(T::from(p), params.clone())
<T as Integer>::Monty::new(to_integer(p), params.clone())
};

// Compute d-th element of Lucas sequence (U_d(P, Q), V_d(P, Q)), where:
Expand All @@ -387,7 +391,7 @@ pub fn lucas_test<T: Integer>(
let mut qk = one.clone(); // keeps Q^k

// D in Montgomery representation - note that it can be negative.
let abs_d = <T as Integer>::Monty::new(T::from(abs_d), params);
let abs_d = <T as Integer>::Monty::new(to_integer(abs_d), params);
let d_m = if d_is_negative { -abs_d } else { abs_d };

for i in (0..d.bits_vartime()).rev() {
Expand Down
23 changes: 13 additions & 10 deletions src/hazmat/miller_rabin.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Miller-Rabin primality test.

use crypto_bigint::{Integer, Monty, NonZero, Odd, PowBoundedExp, RandomMod, Square};
use crypto_bigint::{Integer, Limb, Monty, NonZero, Odd, PowBoundedExp, RandomMod, Square};
use rand_core::CryptoRngCore;

use super::Primality;
Expand Down Expand Up @@ -28,14 +28,16 @@ impl<T: Integer + RandomMod> MillerRabin<T> {
/// Initializes a Miller-Rabin test for `candidate`.
pub fn new(candidate: &Odd<T>) -> Self {
let params = <T as Integer>::Monty::new_params_vartime(candidate.clone());
let one = <T as Integer>::Monty::one(params.clone());
let minus_one = -one.clone();
let m_one = <T as Integer>::Monty::one(params.clone());
let m_minus_one = -m_one.clone();

let one = T::one_like(candidate.as_ref());

// Find `s` and odd `d` such that `candidate - 1 == 2^s * d`.
let (s, d) = if candidate.as_ref() == &T::one() {
(0, T::one())
let (s, d) = if candidate.as_ref() == &one {
(0, one)
} else {
let candidate_minus_one = candidate.wrapping_sub(&T::one());
let candidate_minus_one = candidate.wrapping_sub(&one);
let s = candidate_minus_one.trailing_zeros_vartime();
// Will not overflow because `candidate` is odd and greater than 1.
let d = candidate_minus_one
Expand All @@ -48,8 +50,8 @@ impl<T: Integer + RandomMod> MillerRabin<T> {
candidate: candidate.as_ref().clone(),
bit_length: candidate.bits_vartime(),
montgomery_params: params,
one,
minus_one,
one: m_one,
minus_one: m_minus_one,
s,
d,
}
Expand Down Expand Up @@ -85,7 +87,7 @@ impl<T: Integer + RandomMod> MillerRabin<T> {

/// Perform a Miller-Rabin check with base 2.
pub fn test_base_two(&self) -> Primality {
self.test(&T::from(2u32))
self.test(&T::from_limb_like(Limb::from(2u32), &self.candidate))
}

/// Perform a Miller-Rabin check with a random base (in the range `[3, candidate-2]`)
Expand Down Expand Up @@ -189,7 +191,8 @@ mod tests {
#[test]
fn trivial() {
let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
let start = random_odd_integer::<U1024>(&mut rng, NonZeroU32::new(1024).unwrap());
let start =
random_odd_integer::<U1024>(&mut rng, NonZeroU32::new(1024).unwrap(), U1024::BITS);
for num in Sieve::new(start.as_ref(), NonZeroU32::new(1024).unwrap(), false).take(10) {
let mr = MillerRabin::new(&Odd::new(num).unwrap());

Expand Down
Loading
Loading