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

Further coverage of UintLike #37

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fed515e
Bump crypto-bigint and start transitioning to traits
fjarri Nov 29, 2023
e5ee6be
Convert `sieve`
fjarri Nov 29, 2023
d5fff44
Convert `miller_rabin`
fjarri Nov 29, 2023
0632514
Convert `presets`
fjarri Nov 29, 2023
8041216
Convert `traits`
fjarri Nov 29, 2023
bf44bb5
Updates for the crypto-bigint@bcb41ff42b0eae91440d60d67c9173e8f2280de5
fjarri Nov 30, 2023
6d5e81a
Bit count uses u32 instead of usize
xuganyu96 Dec 13, 2023
1609d00
Removed ambiguous candidates between UintLike and Integer
xuganyu96 Dec 13, 2023
efeab19
Parameterize benchmarks with T: UintLike
xuganyu96 Dec 13, 2023
7c95144
Patch latest version of crypto-bigint
xuganyu96 Dec 14, 2023
1598586
Implemented SMallMod for BoxedUint
xuganyu96 Dec 14, 2023
0a3686a
shift now returns self and ConstChoice
xuganyu96 Dec 14, 2023
d90cb3d
Reciprocal::new is now const fn
xuganyu96 Dec 14, 2023
cd676ba
JacobiSymbol is public under hazmat::JacobiSymbol
xuganyu96 Dec 14, 2023
e62362c
Implemented Jacobi with UintLike
xuganyu96 Dec 14, 2023
cabe201
Implemented gcd with UintLike
xuganyu96 Dec 14, 2023
0fa0582
But ct_div_rem_limb_with_reciprocal is actually not const fn
xuganyu96 Dec 14, 2023
ba9d3c5
Flawed implementation of Random
xuganyu96 Dec 14, 2023
6985061
passed more prime generation tests
xuganyu96 Dec 14, 2023
5471920
passed more prime generation tests
xuganyu96 Dec 14, 2023
2449f8f
All tests pass now
xuganyu96 Dec 14, 2023
6b73400
Typo
xuganyu96 Dec 14, 2023
72c4eee
Updated documentation for bits_precision
xuganyu96 Dec 14, 2023
dcfedd1
listed things to implement for BoxedUint and BoxedResidue
xuganyu96 Dec 14, 2023
c2ea4c1
Small number of tests for BoxedUint prime generation
xuganyu96 Dec 14, 2023
a958b30
Attempted to implement UintLike for BoxedUint; identified missing imp…
xuganyu96 Dec 14, 2023
2ed2bda
This thing compiles but does not pass tests
xuganyu96 Dec 15, 2023
3674e87
Separate out failed prime generation test
xuganyu96 Dec 15, 2023
fa86383
Use overflowing_shift to allow overflow
xuganyu96 Dec 16, 2023
8edacf8
instantiate one using precision so shifting works
xuganyu96 Dec 16, 2023
4c800d8
Widen raw before instantiating BoxedResidue
xuganyu96 Dec 16, 2023
83d34e7
div_by_2 has been implemented
xuganyu96 Dec 16, 2023
acc1f46
Widen after calling from
xuganyu96 Dec 16, 2023
df0ec9b
Refresh seems to address the problem with BoxedResidue::square; now g…
xuganyu96 Dec 16, 2023
49d3487
Removed unnecessary dbg!
xuganyu96 Dec 16, 2023
24e9dde
Removed refresh as BoxedUint::{square, mul} will return reduced form
xuganyu96 Dec 16, 2023
bf76fe5
Better clarity
xuganyu96 Dec 16, 2023
459089d
BoxedUint test for lucas
xuganyu96 Dec 16, 2023
7d7563a
Adapted to newest crypto-bigint APIs
xuganyu96 Dec 18, 2023
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = { 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 }
Expand Down
79 changes: 44 additions & 35 deletions benches/bench.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -22,55 +23,63 @@ fn make_rng() -> ChaCha8Rng {
ChaCha8Rng::from_seed(*b"01234567890123456789012345678901")
}

fn make_sieve<const L: usize>(rng: &mut impl CryptoRngCore) -> Sieve<L> {
let start: Uint<L> = random_odd_uint(rng, Uint::<L>::BITS);
Sieve::new(&start, Uint::<L>::BITS, false)
fn make_sieve<T: UintLike>(
rng: &mut impl CryptoRngCore,
bit_length: u32,
bits_precision: u32,
) -> Sieve<T> {
let start: T = random_odd_uint(rng, bit_length, bits_precision);
return Sieve::new(&start, bit_length, false, bits_precision);
}

fn make_presieved_num<const L: usize>(rng: &mut impl CryptoRngCore) -> Uint<L> {
let mut sieve = make_sieve(rng);
sieve.next().unwrap()
fn make_presieved_num<T: UintLike>(
rng: &mut impl CryptoRngCore,
bit_length: u32,
bits_precision: u32,
) -> T {
let mut sieve = make_sieve::<T>(rng, bit_length, bits_precision);
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::<U128>(&mut OsRng, 128, U128::BITS))
});

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, U128::BITS),
|start| Sieve::<U128>::new(&start, 128, false, U128::BITS),
BatchSize::SmallInput,
)
});

// 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::<U128>(&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::<{ nlimbs!(1024) }>(&mut OsRng, 1024))
b.iter(|| random_odd_uint::<U1024>(&mut OsRng, 1024, U1024::BITS))
});

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, U1024::BITS),
|start| Sieve::<U1024>::new(&start, 1024, false, U1024::BITS),
BatchSize::SmallInput,
)
});

group.bench_function("(U1024) 5 samples", |b| {
b.iter_batched(
|| make_sieve::<{ nlimbs!(1024) }>(&mut OsRng),
|| make_sieve::<U1024>(&mut OsRng, 1024, U1024::BITS),
|sieve| sieve.take(5).for_each(drop),
BatchSize::SmallInput,
)
Expand All @@ -84,31 +93,31 @@ 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::<U128>(&mut OsRng, 128, U128::BITS),
|start| MillerRabin::new(&start),
BatchSize::SmallInput,
)
});

group.bench_function("(U128) random base test (pre-sieved)", |b| {
b.iter_batched(
|| MillerRabin::new(&make_presieved_num::<{ nlimbs!(128) }>(&mut OsRng)),
|| MillerRabin::new(&make_presieved_num::<U128>(&mut OsRng, 128, U128::BITS)),
|mr| mr.test_random_base(&mut OsRng),
BatchSize::SmallInput,
)
});

group.bench_function("(U1024) creation", |b| {
b.iter_batched(
|| random_odd_uint::<{ nlimbs!(1024) }>(&mut OsRng, 1024),
|| random_odd_uint::<U1024>(&mut OsRng, 1024, U1024::BITS),
|start| MillerRabin::new(&start),
BatchSize::SmallInput,
)
});

group.bench_function("(U1024) random base test (pre-sieved)", |b| {
b.iter_batched(
|| MillerRabin::new(&make_presieved_num::<{ nlimbs!(1024) }>(&mut OsRng)),
|| MillerRabin::new(&make_presieved_num::<U1024>(&mut OsRng, 1024, U1024::BITS)),
|mr| mr.test_random_base(&mut OsRng),
BatchSize::SmallInput,
)
Expand All @@ -121,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::<{ nlimbs!(128) }>(&mut rng),
|| make_presieved_num::<U128>(&mut rng, 128, U128::BITS),
|n| lucas_test(&n, SelfridgeBase, LucasCheck::Strong),
BatchSize::SmallInput,
)
Expand All @@ -130,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::<{ nlimbs!(1024) }>(&mut rng),
|| make_presieved_num::<U1024>(&mut rng, 1024, U1024::BITS),
|n| lucas_test(&n, SelfridgeBase, LucasCheck::Strong),
BatchSize::SmallInput,
)
Expand All @@ -139,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::<{ nlimbs!(1024) }>(&mut rng),
|| make_presieved_num::<U1024>(&mut rng, 1024, U1024::BITS),
|n| lucas_test(&n, AStarBase, LucasCheck::LucasV),
BatchSize::SmallInput,
)
Expand All @@ -150,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::<{ nlimbs!(1024) }>(&mut rng),
|| make_presieved_num::<U1024>(&mut rng, 1024, U1024::BITS),
|n| lucas_test(&n, BruteForceBase, LucasCheck::AlmostExtraStrong),
BatchSize::SmallInput,
)
Expand All @@ -160,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::<{ nlimbs!(1024) }>(&mut rng),
|| make_presieved_num::<U1024>(&mut rng, 1024, U1024::BITS),
|n| lucas_test(&n, BruteForceBase, LucasCheck::ExtraStrong),
BatchSize::SmallInput,
)
Expand Down Expand Up @@ -192,39 +201,39 @@ 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::<U128>(&mut OsRng, 128, U128::BITS),
|num| is_prime_with_rng(&mut OsRng, &num),
BatchSize::SmallInput,
)
});

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

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::<U128>(&mut rng, 128, U128::BITS))
});

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::<U1024>(&mut rng, 1024, U1024::BITS))
});

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::<U128>(&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::<{ nlimbs!(1024) }>(&mut rng, None))
b.iter(|| generate_safe_prime_with_rng::<U1024>(&mut rng, 1024, U1024::BITS))
});

group.finish();
Expand All @@ -234,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::<{ nlimbs!(128) }>(&mut rng, None))
b.iter(|| generate_safe_prime_with_rng::<U128>(&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::<{ nlimbs!(256) }>(&mut rng, Some(128)))
b.iter(|| generate_safe_prime_with_rng::<U256>(&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::<{ nlimbs!(256) }>(&mut rng, None))
b.iter(|| generate_safe_prime_with_rng::<U256>(&mut rng, 256, U256::BITS))
});

group.finish();
Expand All @@ -263,15 +272,15 @@ fn bench_gmp(c: &mut Criterion) {

group.bench_function("(U128) Random prime", |b| {
b.iter_batched(
|| random::<{ nlimbs!(128) }>(&mut OsRng),
|| random::<U128>(&mut OsRng),
|n| n.next_prime(),
BatchSize::SmallInput,
)
});

group.bench_function("(U1024) Random prime", |b| {
b.iter_batched(
|| random::<{ nlimbs!(1024) }>(&mut OsRng),
|| random::<U1024>(&mut OsRng),
|n| n.next_prime(),
BatchSize::SmallInput,
)
Expand Down
5 changes: 3 additions & 2 deletions src/hazmat.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Components to build your own primality test.
//! Handle with care.

mod gcd;
mod jacobi;
pub(crate) mod gcd;
pub(crate) mod jacobi;
mod lucas;
mod miller_rabin;
mod precomputed;
Expand All @@ -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};
Expand Down
10 changes: 6 additions & 4 deletions src/hazmat/gcd.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
use crypto_bigint::{Limb, NonZero, Uint};
use crypto_bigint::{Limb, NonZero};

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<const L: usize>(n: &Uint<L>, m: u32) -> u32 {
pub(crate) fn gcd<T: UintLike>(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<L>`
// (since `gcd(n, 0) = n`).
debug_assert!(m != 0);

// 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::<L>::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`.
Expand Down
29 changes: 23 additions & 6 deletions src/hazmat/jacobi.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
//! Jacobi symbol calculation.

use crypto_bigint::{Integer, Limb, NonZero, Uint, Word};
use core::fmt::Display;

use crypto_bigint::{BoxedUint, Limb, NonZero, Uint, Word};

use crate::UintLike;

#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum JacobiSymbol {
pub enum JacobiSymbol {
Zero,
One,
MinusOne,
Expand All @@ -21,7 +26,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;
}
Expand All @@ -44,6 +49,16 @@ impl<const L: usize> SmallMod for Uint<L> {
}
}

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.
Expand Down Expand Up @@ -73,7 +88,7 @@ fn swap<T: SmallMod, V: SmallMod>(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<const L: usize>(a: i32, p_long: &Uint<L>) -> JacobiSymbol {
pub(crate) fn jacobi_symbol<T: UintLike + Display + SmallMod>(a: i32, p_long: &T) -> JacobiSymbol {
if p_long.is_even().into() {
panic!("`p_long` must be an odd integer, but got {}", p_long);
}
Expand All @@ -94,7 +109,7 @@ pub(crate) fn jacobi_symbol<const L: usize>(a: i32, p_long: &Uint<L>) -> JacobiS
};

// A degenerate case.
if a_pos == 1 || p_long == &Uint::<L>::ONE {
if a_pos == 1 || p_long == &T::one() {
return result;
}

Expand All @@ -110,7 +125,9 @@ pub(crate) fn jacobi_symbol<const L: usize>(a: i32, p_long: &Uint<L>) -> JacobiS
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());
Expand Down
Loading