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

Uint traits #39

Closed
wants to merge 5 commits into from
Closed
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
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.73"

[dependencies]
crypto-bigint = { version = "0.6.0-pre.5", default-features = false, features = ["rand_core"] }
crypto-bigint = { version = "0.6.0-pre.5", default-features = false, 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 }
Expand Down
44 changes: 22 additions & 22 deletions benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ fn make_rng() -> ChaCha8Rng {
ChaCha8Rng::from_seed(*b"01234567890123456789012345678901")
}

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

Expand All @@ -36,13 +36,13 @@ 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::<Uint<{ nlimbs!(128) }>>(&mut OsRng, 128, 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::<Uint<{ nlimbs!(128) }>>(&mut OsRng, 128, 128),
|start| Sieve::new(start.as_ref(), 128, false),
BatchSize::SmallInput,
)
});
Expand All @@ -57,13 +57,13 @@ fn bench_sieve(c: &mut Criterion) {
});

group.bench_function("(U1024) random start", |b| {
b.iter(|| random_odd_uint::<{ nlimbs!(1024) }>(&mut OsRng, 1024))
b.iter(|| random_odd_uint::<Uint<{ nlimbs!(1024) }>>(&mut OsRng, 1024, 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::<Uint<{ nlimbs!(1024) }>>(&mut OsRng, 1024, 1024),
|start| Sieve::new(start.as_ref(), 1024, false),
BatchSize::SmallInput,
)
});
Expand All @@ -84,7 +84,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::<Uint<{ nlimbs!(128) }>>(&mut OsRng, 128, 128),
MillerRabin::new,
BatchSize::SmallInput,
)
Expand All @@ -100,7 +100,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::<Uint<{ nlimbs!(1024) }>>(&mut OsRng, 1024, 1024),
MillerRabin::new,
BatchSize::SmallInput,
)
Expand Down Expand Up @@ -193,39 +193,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),
|num| is_prime_with_rng(&mut OsRng, &num),
|| random_odd_uint::<Uint<{ nlimbs!(128) }>>(&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::<{ nlimbs!(128) }>(&mut OsRng, 128),
|num| is_safe_prime_with_rng(&mut OsRng, &num),
|| random_odd_uint::<Uint<{ nlimbs!(128) }>>(&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::<{ nlimbs!(128) }>(&mut rng, None))
b.iter(|| generate_prime_with_rng::<Uint<{ nlimbs!(128) }>>(&mut rng, 128, 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::<Uint<{ nlimbs!(1024) }>>(&mut rng, 1024, 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::<Uint<{ nlimbs!(128) }>>(&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::<{ nlimbs!(1024) }>(&mut rng, None))
b.iter(|| generate_safe_prime_with_rng::<Uint<{ nlimbs!(1024) }>>(&mut rng, 1024, 1024))
});

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

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

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

Expand Down
8 changes: 4 additions & 4 deletions src/hazmat/gcd.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use crypto_bigint::{Limb, NonZero, Uint, Word};
use crypto_bigint::{Integer, Limb, NonZero, Word};

/// Calculates the greatest common divisor of `n` and `m`.
/// By definition, `gcd(0, m) == m`.
/// `n` must be non-zero.
pub(crate) fn gcd_vartime<const L: usize>(n: &Uint<L>, m: Word) -> Word {
pub(crate) fn gcd_vartime<T: Integer>(n: &T, m: Word) -> Word {
// This is an internal function, and it will never be called with `m = 0`.
// Allowing `m = 0` would require us to have the return type of `Uint<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;
}

Expand All @@ -23,7 +23,7 @@ pub(crate) fn gcd_vartime<const L: usize>(n: &Uint<L>, m: Word) -> Word {
} else {
// In this branch `n` is `Word::BITS` bits or shorter,
// so we can safely take the first limb.
let n = n.as_words()[0];
let n = n.as_ref()[0].0;
if n > m {
(n, m)
} else {
Expand Down
71 changes: 32 additions & 39 deletions src/hazmat/jacobi.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Jacobi symbol calculation.

use crypto_bigint::{Limb, NonZero, Odd, Uint, Word};
use crypto_bigint::{Integer, Limb, NonZero, Odd, Word};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum JacobiSymbol {
Expand All @@ -20,37 +20,13 @@ impl core::ops::Neg for JacobiSymbol {
}
}

// A helper trait to generalize some functions over Word and Uint.
trait SmallMod {
fn mod8(&self) -> Word;
fn mod4(&self) -> Word;
}

impl SmallMod for Word {
fn mod8(&self) -> Word {
self & 7
}
fn mod4(&self) -> Word {
self & 3
}
}

impl<const L: usize> SmallMod for Uint<L> {
fn mod8(&self) -> Word {
self.as_limbs()[0].0 & 7
}
fn mod4(&self) -> Word {
self.as_limbs()[0].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.
///
/// Note that the returned `r` is odd.
fn reduce_numerator<V: SmallMod>(j: JacobiSymbol, a: Word, p: &V) -> (JacobiSymbol, Word) {
let p_mod_8 = p.mod8();
fn apply_reduce_numerator(j: JacobiSymbol, a: Word, p: Word) -> (JacobiSymbol, Word) {
let p_mod_8 = p & 7;
let s = a.trailing_zeros();
let j = if (s & 1) == 1 && (p_mod_8 == 3 || p_mod_8 == 5) {
-j
Expand All @@ -60,38 +36,55 @@ fn reduce_numerator<V: SmallMod>(j: JacobiSymbol, a: Word, p: &V) -> (JacobiSymb
(j, a >> s)
}

fn reduce_numerator_long<T: Integer>(j: JacobiSymbol, a: Word, p: &T) -> (JacobiSymbol, Word) {
apply_reduce_numerator(j, a, p.as_ref()[0].0)
}

fn reduce_numerator_short(j: JacobiSymbol, a: Word, p: Word) -> (JacobiSymbol, Word) {
apply_reduce_numerator(j, a, p)
}

/// Transforms `(a/p)` -> `(p/a)` for odd and coprime `a` and `p`.
/// Takes a Jacobi symbol value, and returns the swapped pair and the new Jacobi symbol,
/// negated if the transformation changes parity.
fn swap<T: SmallMod, V: SmallMod>(j: JacobiSymbol, a: T, p: V) -> (JacobiSymbol, V, T) {
let j = if a.mod4() == 1 || p.mod4() == 1 {
fn apply_swap(j: JacobiSymbol, a: Word, p: Word) -> JacobiSymbol {
if a & 3 == 1 || p & 3 == 1 {
j
} else {
-j
};
}
}

fn swap_long<T: Integer>(j: JacobiSymbol, a: Word, p: &Odd<T>) -> (JacobiSymbol, &Odd<T>, Word) {
let j = apply_swap(j, a, p.as_ref().as_ref()[0].0);
(j, p, a)
}

fn swap_short(j: JacobiSymbol, a: Word, p: Word) -> (JacobiSymbol, Word, Word) {
let j = apply_swap(j, a, p);
(j, p, a)
}

/// Returns the Jacobi symbol `(a/p)` given an odd `p`.
pub(crate) fn jacobi_symbol_vartime<const L: usize>(
pub(crate) fn jacobi_symbol_vartime<T: Integer>(
abs_a: Word,
a_is_negative: bool,
p_long: &Odd<Uint<L>>,
p_long: &Odd<T>,
) -> JacobiSymbol {
let result = JacobiSymbol::One; // Keep track of all the sign flips here.

// Deal with a negative `a` first:
// (-a/n) = (-1/n) * (a/n)
// = (-1)^((n-1)/2) * (a/n)
// = (-1 if n = 3 mod 4 else 1) * (a/n)
let result = if a_is_negative && p_long.mod4() == 3 {
let result = if a_is_negative && p_long.as_ref().as_ref()[0].0 & 3 == 3 {
-result
} else {
result
};

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

Expand All @@ -100,14 +93,14 @@ pub(crate) fn jacobi_symbol_vartime<const L: usize>(
// Normalize input: at the end we want `a < p`, `p` odd, and both fitting into a `Word`.
let (result, a, p): (JacobiSymbol, Word, Word) = if p_long.bits_vartime() <= Limb::BITS {
let a = a_limb.0;
let p = p_long.as_limbs()[0].0;
let p = p_long.as_ref().as_ref()[0].0;
(result, a % p, p)
} else {
let (result, a) = reduce_numerator(result, a_limb.0, p_long.as_ref());
let (result, a) = reduce_numerator_long(result, a_limb.0, p_long.as_ref());
if a == 1 {
return result;
}
let (result, a_long, p) = swap(result, a, p_long.get());
let (result, a_long, p) = swap_long(result, a, p_long);
// Can unwrap here, since `p` is swapped with `a`,
// and `a` would be odd after `reduce_numerator()`.
let a =
Expand All @@ -127,7 +120,7 @@ pub(crate) fn jacobi_symbol_vartime<const L: usize>(
// At this point `p` is odd (either coming from outside of the `loop`,
// or from the previous iteration, where a previously reduced `a`
// was swapped into its place), so we can call this.
(result, a) = reduce_numerator(result, a, &p);
(result, a) = reduce_numerator_short(result, a, p);

if a == 1 {
return result;
Expand All @@ -138,7 +131,7 @@ pub(crate) fn jacobi_symbol_vartime<const L: usize>(
// Note that technically `swap()` only returns a valid `result` if `a` and `p` are coprime.
// But if they are not, we will return `Zero` eventually,
// which is not affected by any sign changes.
(result, a, p) = swap(result, a, p);
(result, a, p) = swap_short(result, a, p);

a %= p;
}
Expand Down
Loading
Loading