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

Allow custom MSB setting in generated primes #69

Merged
merged 1 commit into from
Jan 27, 2025
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Renamed `Sieve` to `SmallPrimesSieve`. ([#64])
- Bumped `crypto-bigint` to 0.6.0-rc.7 and MSRV to 1.83. ([#67])
- Bumped `crypto-bigint` to 0.6. ([#68])
- `random_odd_integer()` takes an additional `SetBits` argument. ([#69])
- `random_odd_integer()` now returns a `Result` instead of panicking. ([#69])


### Added
Expand All @@ -23,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#64]: https://github.com/entropyxyz/crypto-primes/pull/64
[#67]: https://github.com/entropyxyz/crypto-primes/pull/67
[#68]: https://github.com/entropyxyz/crypto-primes/pull/68
[#69]: https://github.com/entropyxyz/crypto-primes/pull/69


## [0.6.0-pre.2] - 2024-10-18
Expand Down
12 changes: 8 additions & 4 deletions benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use rand_core::RngCore;
use crypto_primes::{
generate_prime_with_rng, generate_safe_prime_with_rng,
hazmat::{
lucas_test, random_odd_integer, AStarBase, BruteForceBase, LucasCheck, MillerRabin, SelfridgeBase,
lucas_test, random_odd_integer, AStarBase, BruteForceBase, LucasCheck, MillerRabin, SelfridgeBase, SetBits,
SmallPrimesSieve,
},
is_prime_with_rng, is_safe_prime_with_rng,
Expand All @@ -37,7 +37,7 @@ fn make_random_rng() -> ChaCha8Rng {
}

fn random_odd_uint<T: RandomBits + Integer>(rng: &mut impl CryptoRngCore, bit_length: u32) -> Odd<T> {
random_odd_integer::<T>(rng, NonZero::new(bit_length).unwrap())
random_odd_integer::<T>(rng, NonZero::new(bit_length).unwrap(), SetBits::Msb).unwrap()
}

fn make_sieve<const L: usize>(rng: &mut impl CryptoRngCore) -> SmallPrimesSieve<Uint<L>> {
Expand Down Expand Up @@ -449,7 +449,9 @@ fn bench_glass_pumpkin(c: &mut Criterion) {
// Mimics the sequence of checks `glass-pumpkin` does to find a prime.
fn prime_like_gp(bit_length: u32, rng: &mut impl CryptoRngCore) -> BoxedUint {
loop {
let start = random_odd_integer::<BoxedUint>(rng, NonZero::new(bit_length).unwrap()).get();
let start = random_odd_integer::<BoxedUint>(rng, NonZero::new(bit_length).unwrap(), SetBits::Msb)
.unwrap()
.get();
let sieve = SmallPrimesSieve::new(start, NonZero::new(bit_length).unwrap(), false);
for num in sieve {
let odd_num = Odd::new(num.clone()).unwrap();
Expand All @@ -473,7 +475,9 @@ fn bench_glass_pumpkin(c: &mut Criterion) {
// Mimics the sequence of checks `glass-pumpkin` does to find a safe prime.
fn safe_prime_like_gp(bit_length: u32, rng: &mut impl CryptoRngCore) -> BoxedUint {
loop {
let start = random_odd_integer::<BoxedUint>(rng, NonZero::new(bit_length).unwrap()).get();
let start = random_odd_integer::<BoxedUint>(rng, NonZero::new(bit_length).unwrap(), SetBits::Msb)
.unwrap()
.get();
let sieve = SmallPrimesSieve::new(start, NonZero::new(bit_length).unwrap(), true);
for num in sieve {
let odd_num = Odd::new(num.clone()).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/hazmat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mod sieve;

pub use lucas::{lucas_test, AStarBase, BruteForceBase, LucasBase, LucasCheck, SelfridgeBase};
pub use miller_rabin::MillerRabin;
pub use sieve::{random_odd_integer, SmallPrimesSieve, SmallPrimesSieveFactory};
pub use sieve::{random_odd_integer, SetBits, SmallPrimesSieve, SmallPrimesSieveFactory};

/// Possible results of various primality tests.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down
4 changes: 2 additions & 2 deletions src/hazmat/miller_rabin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ mod tests {
use num_prime::nt_funcs::is_prime64;

use super::MillerRabin;
use crate::hazmat::{primes, pseudoprimes, random_odd_integer, SmallPrimesSieve};
use crate::hazmat::{primes, pseudoprimes, random_odd_integer, SetBits, SmallPrimesSieve};

#[test]
fn miller_rabin_derived_traits() {
Expand Down Expand Up @@ -196,7 +196,7 @@ mod tests {
#[test]
fn trivial() {
let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
let start = random_odd_integer::<U1024>(&mut rng, NonZero::new(1024).unwrap());
let start = random_odd_integer::<U1024>(&mut rng, NonZero::new(1024).unwrap(), SetBits::Msb).unwrap();
for num in SmallPrimesSieve::new(start.get(), NonZero::new(1024).unwrap(), false).take(10) {
let mr = MillerRabin::new(Odd::new(num).unwrap());

Expand Down
122 changes: 97 additions & 25 deletions src/hazmat/sieve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,64 @@ use alloc::{vec, vec::Vec};
use core::marker::PhantomData;
use core::num::{NonZero, NonZeroU32};

use crypto_bigint::{Integer, Odd, RandomBits};
use crypto_bigint::{Integer, Odd, RandomBits, RandomBitsError};
use rand_core::CryptoRngCore;

use crate::hazmat::precomputed::{SmallPrime, LAST_SMALL_PRIME, RECIPROCALS, SMALL_PRIMES};
use crate::traits::SieveFactory;

/// Returns a random odd integer with given bit length
/// (that is, with both `0` and `bit_length-1` bits set).
/// Decide how prime candidates are manipulated by setting certain bits before primality testing,
/// influencing the range of the prime.
#[derive(Debug, Clone, Copy)]
pub enum SetBits {
/// Set the most significant bit, thus limiting the range to `[MAX/2 + 1, MAX]`.
///
/// In other words, all candidates will have the same bit size.
Msb,
/// Set two most significant bits, limiting the range to `[MAX - MAX/4 + 1, MAX]`.
///
/// This is useful in the RSA case because a product of two such numbers will have a guaranteed bit size.
TwoMsb,
/// No additional bits set; uses the full range `[1, MAX]`.
None,
}

/// Returns a random odd integer up to the given bit length.
///
/// The `set_bits` parameter decides which extra bits are set, which decides the range of the number.
///
/// *Panics*: if the `bit_length` is bigger than the bits available in the `Integer`, e.g. 37 for a
/// `U32`.
pub fn random_odd_integer<T: Integer + RandomBits>(rng: &mut impl CryptoRngCore, bit_length: NonZeroU32) -> Odd<T> {
/// Returns an error variant if `bit_length` is greater than the maximum allowed for `T`
/// (applies to fixed-length types).
pub fn random_odd_integer<T: Integer + RandomBits>(
rng: &mut impl CryptoRngCore,
bit_length: NonZeroU32,
set_bits: SetBits,
) -> Result<Odd<T>, RandomBitsError> {
let bit_length = bit_length.get();

let mut random = T::random_bits(rng, bit_length);
let mut random = T::try_random_bits(rng, bit_length)?;

// Make it odd
// `bit_length` is non-zero, so the 0-th bit exists.
random.set_bit_vartime(0, true);

// Make sure it's the correct bit size
// Will not overflow since `bit_length` is ensured to be within the size of the integer.
random.set_bit_vartime(bit_length - 1, true);
// Will not overflow since `bit_length` is ensured to be within the size of the integer
// (checked within the `T::try_random_bits()` call).
// `bit_length - 1`-th bit exists since `bit_length` is non-zero.
match set_bits {
SetBits::None => {}
SetBits::Msb => random.set_bit_vartime(bit_length - 1, true),
SetBits::TwoMsb => {
random.set_bit_vartime(bit_length - 1, true);
// We could panic here, but since the primary purpose of `TwoMsb` is to ensure the bit length
// of the product of two numbers, ignoring this for `bit_length = 1` leads to the desired result.
if bit_length > 1 {
random.set_bit_vartime(bit_length - 2, true);
}
}
}

Odd::new(random).expect("the number is odd by construction")
Ok(Odd::new(random).expect("the number is odd by construction"))
}

// The type we use to calculate incremental residues.
Expand Down Expand Up @@ -251,11 +286,12 @@ impl<T: Integer> Iterator for SmallPrimesSieve<T> {
pub struct SmallPrimesSieveFactory<T> {
max_bit_length: NonZeroU32,
safe_primes: bool,
set_bits: SetBits,
phantom: PhantomData<T>,
}

impl<T: Integer + RandomBits> SmallPrimesSieveFactory<T> {
fn new_impl(max_bit_length: u32, safe_primes: bool) -> Self {
fn new_impl(max_bit_length: u32, set_bits: SetBits, safe_primes: bool) -> Self {
if !safe_primes && max_bit_length < 2 {
panic!("`bit_length` must be 2 or greater.");
}
Expand All @@ -266,20 +302,21 @@ impl<T: Integer + RandomBits> SmallPrimesSieveFactory<T> {
Self {
max_bit_length,
safe_primes,
set_bits,
phantom: PhantomData,
}
}

/// Creates a factory that produces sieves returning numbers of `max_bit_length` bits (with the top bit set)
/// that are not divisible by a number of small factors.
pub fn new(max_bit_length: u32) -> Self {
Self::new_impl(max_bit_length, false)
pub fn new(max_bit_length: u32, set_bits: SetBits) -> Self {
Self::new_impl(max_bit_length, set_bits, false)
}

/// Creates a factory that produces sieves returning numbers `n` of `max_bit_length` bits (with the top bit set)
/// such that neither `n` nor `(n - 1) / 2` are divisible by a number of small factors.
pub fn new_safe_primes(max_bit_length: u32) -> Self {
Self::new_impl(max_bit_length, true)
pub fn new_safe_primes(max_bit_length: u32, set_bits: SetBits) -> Self {
Self::new_impl(max_bit_length, set_bits, true)
}
}

Expand All @@ -291,7 +328,8 @@ impl<T: Integer + RandomBits> SieveFactory for SmallPrimesSieveFactory<T> {
rng: &mut impl CryptoRngCore,
_previous_sieve: Option<&Self::Sieve>,
) -> Option<Self::Sieve> {
let start = random_odd_integer::<T>(rng, self.max_bit_length);
let start =
random_odd_integer::<T>(rng, self.max_bit_length, self.set_bits).expect("random_odd_integer() failed");
Some(SmallPrimesSieve::new(
start.get(),
self.max_bit_length,
Expand All @@ -312,15 +350,17 @@ mod tests {
use rand_chacha::ChaCha8Rng;
use rand_core::{OsRng, SeedableRng};

use super::{random_odd_integer, SmallPrimesSieve, SmallPrimesSieveFactory};
use super::{random_odd_integer, SetBits, SmallPrimesSieve, SmallPrimesSieveFactory};
use crate::hazmat::precomputed::SMALL_PRIMES;

#[test]
fn random() {
let max_prime = SMALL_PRIMES[SMALL_PRIMES.len() - 1];

let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
let start = random_odd_integer::<U64>(&mut rng, NonZero::new(32).unwrap()).get();
let start = random_odd_integer::<U64>(&mut rng, NonZero::new(32).unwrap(), SetBits::Msb)
.unwrap()
.get();
for num in SmallPrimesSieve::new(start, NonZero::new(32).unwrap(), false).take(100) {
let num_u64 = u64::from(num);
assert!(num_u64.leading_zeros() == 32);
Expand All @@ -336,7 +376,9 @@ mod tests {
let max_prime = SMALL_PRIMES[SMALL_PRIMES.len() - 1];

let mut rng = ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
let start = random_odd_integer::<crypto_bigint::BoxedUint>(&mut rng, NonZero::new(32).unwrap()).get();
let start = random_odd_integer::<crypto_bigint::BoxedUint>(&mut rng, NonZero::new(32).unwrap(), SetBits::Msb)
.unwrap()
.get();

for num in SmallPrimesSieve::new(start, NonZero::new(32).unwrap(), false).take(100) {
// For 32-bit targets
Expand Down Expand Up @@ -414,15 +456,16 @@ mod tests {
#[test]
fn random_below_max_length() {
for _ in 0..10 {
let r = random_odd_integer::<U64>(&mut OsRng, NonZero::new(50).unwrap()).get();
let r = random_odd_integer::<U64>(&mut OsRng, NonZero::new(50).unwrap(), SetBits::Msb)
.unwrap()
.get();
assert_eq!(r.bits(), 50);
}
}

#[test]
#[should_panic(expected = "try_random_bits() failed: BitLengthTooLarge { bit_length: 65, bits_precision: 64 }")]
fn random_odd_uint_too_many_bits() {
let _p = random_odd_integer::<U64>(&mut OsRng, NonZero::new(65).unwrap());
assert!(random_odd_integer::<U64>(&mut OsRng, NonZero::new(65).unwrap(), SetBits::Msb).is_err());
}

#[test]
Expand Down Expand Up @@ -450,12 +493,41 @@ mod tests {
#[test]
#[should_panic(expected = "`bit_length` must be 2 or greater")]
fn too_few_bits_regular_primes() {
let _fac = SmallPrimesSieveFactory::<U64>::new(1);
let _fac = SmallPrimesSieveFactory::<U64>::new(1, SetBits::Msb);
}

#[test]
#[should_panic(expected = "`bit_length` must be 3 or greater")]
fn too_few_bits_safe_primes() {
let _fac = SmallPrimesSieveFactory::<U64>::new_safe_primes(2);
let _fac = SmallPrimesSieveFactory::<U64>::new_safe_primes(2, SetBits::Msb);
}

#[test]
fn set_bits() {
for _ in 0..10 {
let x = random_odd_integer::<U64>(&mut OsRng, NonZero::new(64).unwrap(), SetBits::Msb).unwrap();
assert!(bool::from(x.bit(63)));
}

for _ in 0..10 {
let x = random_odd_integer::<U64>(&mut OsRng, NonZero::new(64).unwrap(), SetBits::TwoMsb).unwrap();
assert!(bool::from(x.bit(63)));
assert!(bool::from(x.bit(62)));
}

// 1 in 2^30 chance of spurious failure... good enough?
assert!((0..30)
.map(|_| { random_odd_integer::<U64>(&mut OsRng, NonZero::new(64).unwrap(), SetBits::None).unwrap() })
.any(|x| !bool::from(x.bit(63))));
}

#[test]
fn set_two_msb_small_bit_length() {
// Check that when technically there isn't a second most significant bit,
// `random_odd_integer()` still returns a number.
let x = random_odd_integer::<U64>(&mut OsRng, NonZero::new(1).unwrap(), SetBits::TwoMsb)
.unwrap()
.get();
assert_eq!(x, U64::ONE);
}
}
Loading
Loading