Skip to content

Commit

Permalink
Make lucas_test() take an Odd-wrapped integer
Browse files Browse the repository at this point in the history
  • Loading branch information
fjarri committed Dec 24, 2023
1 parent de8d0fa commit 685a6ee
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 72 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Bumped `crypto-bigint` to 0.6.0-pre.5. ([#38])
- Bumped MSRV to 1.73. (#[38])
- `MillerRabin::new()` takes an `Odd`-wrapped integer by value. `random_odd_uint()` returns an `Odd`-wrapped integer. `LucasBase::generate()` takes an `Odd`-wrapped integer. (#[38])
- `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. (#[38])
- All bit length-type parameters take `u32` instead of `usize`. (#[38])


Expand Down
5 changes: 3 additions & 2 deletions benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,13 @@ fn bench_lucas(c: &mut Criterion) {
// - V_{d * 2^t} checked for t == 0..s-1, but no V = 0 found
// - s = 5, so the previous step has multiple checks
// - Q != 1 (since we're using Selfridge base)
let slow_path = U1024::from_be_hex(concat![
let slow_path = Odd::new(U1024::from_be_hex(concat![
"D1CB9F1B6F3414A4B40A7E51C53C6AE4689DFCDC49FF875E7066A229D704EA8E",
"6B674231D8C5974001673C3CE7FF9D377C8564E5182165A23434BC7B7E6C0419",
"FD25C9921B0E9C90AF2570DB0772E1A9C82ACABBC8FC0F0864CE8A12124FA29B",
"7F870924041DFA13EE5F5541C1BF96CA679EFAE2C96F5F4E9DF6007185198F5F"
]);
]))
.unwrap();

group.bench_function("(U1024) Selfridge base, strong check, slow path", |b| {
b.iter(|| {
Expand Down
149 changes: 81 additions & 68 deletions src/hazmat/lucas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ pub enum LucasCheck {
/// See [`LucasCheck`] for possible checks, and the implementors of [`LucasBase`]
/// for the corresponding bases.
pub fn lucas_test<const L: usize>(
candidate: &Uint<L>,
candidate: &Odd<Uint<L>>,
base: impl LucasBase,
check: LucasCheck,
) -> Primality {
Expand All @@ -296,17 +296,8 @@ pub fn lucas_test<const L: usize>(
// R. Crandall, C. Pomerance, "Prime numbers: a computational perspective",
// 2nd ed., Springer (2005) (ISBN: 0-387-25282-7, 978-0387-25282-7)

if candidate == &Uint::<L>::from(2u32) {
return Primality::Prime;
}

let odd_candidate = match Odd::new(*candidate).into() {
Some(x) => x,
None => return Primality::Composite,
};

// Find the base for the Lucas sequence.
let (p, abs_q, q_is_negative) = match base.generate(&odd_candidate) {
let (p, abs_q, q_is_negative) = match base.generate(candidate) {
Ok(pq) => pq,
Err(primality) => return primality,
};
Expand Down Expand Up @@ -338,17 +329,20 @@ pub fn lucas_test<const L: usize>(
// But in order to avoid an implicit assumption that a sieve has been run,
// we check that gcd(n, Q) = 1 anyway - again, since `Q` is small,
// it does not noticeably affect the performance.
if abs_q != 1 && gcd_vartime(candidate, abs_q) != 1 && candidate > &Uint::<L>::from(abs_q) {
if abs_q != 1
&& gcd_vartime(candidate, abs_q) != 1
&& candidate.as_ref() > &Uint::<L>::from(abs_q)
{
return Primality::Composite;
}

// Find `d` and `s`, such that `d` is odd and `d * 2^s = n - (D/n)`.
// Since `(D/n) == -1` by construction, we're looking for `d * 2^s = n + 1`.
let (s, d) = decompose(&odd_candidate);
let (s, d) = decompose(candidate);

// Some constants in Montgomery form

let params = MontyParams::<L>::new(odd_candidate);
let params = MontyParams::<L>::new(*candidate);

let zero = MontyForm::<L>::zero(params);
let one = MontyForm::<L>::one(params);
Expand Down Expand Up @@ -547,15 +541,6 @@ mod tests {
)
}

#[test]
fn lucas_early_quit() {
// If the number is even, no need to run the test.
assert_eq!(
lucas_test(&U64::from(6u32), SelfridgeBase, LucasCheck::Strong),
Primality::Composite
);
}

#[test]
fn gcd_check() {
// Test that `gcd(2QD, n) == 1` is checked after the base is found.
Expand All @@ -576,7 +561,11 @@ mod tests {
}

assert_eq!(
lucas_test(&U64::from(15u32), TestBase, LucasCheck::Strong),
lucas_test(
&Odd::new(U64::from(15u32)).unwrap(),
TestBase,
LucasCheck::Strong
),
Primality::Composite
);
}
Expand Down Expand Up @@ -626,13 +615,21 @@ mod tests {

// Test both single-limb and multi-limb, just in case.
assert_eq!(
lucas_test(&Uint::<1>::from(*num), SelfridgeBase, LucasCheck::Strong)
.is_probably_prime(),
lucas_test(
&Odd::new(Uint::<1>::from(*num)).unwrap(),
SelfridgeBase,
LucasCheck::Strong
)
.is_probably_prime(),
actual_expected_result
);
assert_eq!(
lucas_test(&Uint::<2>::from(*num), SelfridgeBase, LucasCheck::Strong)
.is_probably_prime(),
lucas_test(
&Odd::new(Uint::<2>::from(*num)).unwrap(),
SelfridgeBase,
LucasCheck::Strong
)
.is_probably_prime(),
actual_expected_result
);
}
Expand All @@ -649,13 +646,21 @@ mod tests {

// Test both single-limb and multi-limb, just in case.
assert_eq!(
lucas_test(&Uint::<1>::from(*num), AStarBase, LucasCheck::LucasV)
.is_probably_prime(),
lucas_test(
&Odd::new(Uint::<1>::from(*num)).unwrap(),
AStarBase,
LucasCheck::LucasV
)
.is_probably_prime(),
actual_expected_result
);
assert_eq!(
lucas_test(&Uint::<2>::from(*num), AStarBase, LucasCheck::LucasV)
.is_probably_prime(),
lucas_test(
&Odd::new(Uint::<2>::from(*num)).unwrap(),
AStarBase,
LucasCheck::LucasV
)
.is_probably_prime(),
actual_expected_result
);
}
Expand All @@ -681,12 +686,22 @@ mod tests {

// Test both single-limb and multi-limb, just in case.
assert_eq!(
lucas_test(&Uint::<1>::from(*num), BruteForceBase, check).is_probably_prime(),
lucas_test(
&Odd::new(Uint::<1>::from(*num)).unwrap(),
BruteForceBase,
check
)
.is_probably_prime(),
actual_expected_result,
"Brute force base, n = {num}, almost_extra = {almost_extra}",
);
assert_eq!(
lucas_test(&Uint::<2>::from(*num), BruteForceBase, check).is_probably_prime(),
lucas_test(
&Odd::new(Uint::<2>::from(*num)).unwrap(),
BruteForceBase,
check
)
.is_probably_prime(),
actual_expected_result
);
}
Expand All @@ -698,8 +713,16 @@ mod tests {
// Good thing we don't need to test for intersection
// with `EXTRA_STRONG_LUCAS` or `STRONG_LUCAS` - there's none.
for num in pseudoprimes::STRONG_FIBONACCI.iter() {
assert!(!lucas_test(num, SelfridgeBase, LucasCheck::Strong).is_probably_prime());
assert!(!lucas_test(num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime());
assert!(

Check warning on line 716 in src/hazmat/lucas.rs

View check run for this annotation

Codecov / codecov/patch

src/hazmat/lucas.rs#L716

Added line #L716 was not covered by tests
!lucas_test(&Odd::new(*num).unwrap(), SelfridgeBase, LucasCheck::Strong)
.is_probably_prime()
);
assert!(!lucas_test(
&Odd::new(*num).unwrap(),
BruteForceBase,
LucasCheck::ExtraStrong
)
.is_probably_prime());
}
}

Expand Down Expand Up @@ -770,7 +793,7 @@ mod tests {

#[test]
fn large_carmichael_number() {
let p = pseudoprimes::LARGE_CARMICHAEL_NUMBER;
let p = Odd::new(pseudoprimes::LARGE_CARMICHAEL_NUMBER).unwrap();
assert!(!lucas_test(&p, SelfridgeBase, LucasCheck::Strong).is_probably_prime());
assert!(!lucas_test(&p, AStarBase, LucasCheck::LucasV).is_probably_prime());
assert!(!lucas_test(&p, BruteForceBase, LucasCheck::AlmostExtraStrong).is_probably_prime());
Expand All @@ -779,12 +802,13 @@ mod tests {

fn test_large_primes<const L: usize>(nums: &[Uint<L>]) {
for num in nums {
assert!(lucas_test(num, SelfridgeBase, LucasCheck::Strong).is_probably_prime());
assert!(lucas_test(num, AStarBase, LucasCheck::LucasV).is_probably_prime());
let num = Odd::new(*num).unwrap();
assert!(lucas_test(&num, SelfridgeBase, LucasCheck::Strong).is_probably_prime());
assert!(lucas_test(&num, AStarBase, LucasCheck::LucasV).is_probably_prime());
assert!(
lucas_test(num, BruteForceBase, LucasCheck::AlmostExtraStrong).is_probably_prime()
lucas_test(&num, BruteForceBase, LucasCheck::AlmostExtraStrong).is_probably_prime()
);
assert!(lucas_test(num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime());
assert!(lucas_test(&num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime());
}
}

Expand All @@ -800,32 +824,29 @@ mod tests {
#[test]
fn test_lucas_v_pseudoprimes() {
for num in pseudoprimes::LARGE_LUCAS_V {
let num = Odd::new(*num).unwrap();
// These are false positives for Lucas-V test
assert!(lucas_test(num, AStarBase, LucasCheck::LucasV).is_probably_prime());
assert!(lucas_test(&num, AStarBase, LucasCheck::LucasV).is_probably_prime());

// These tests should work correctly
assert!(!lucas_test(num, SelfridgeBase, LucasCheck::Strong).is_probably_prime());
assert!(!lucas_test(&num, SelfridgeBase, LucasCheck::Strong).is_probably_prime());
assert!(
!lucas_test(num, BruteForceBase, LucasCheck::AlmostExtraStrong).is_probably_prime()
!lucas_test(&num, BruteForceBase, LucasCheck::AlmostExtraStrong)
.is_probably_prime()
);
assert!(!lucas_test(num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime());
assert!(!lucas_test(&num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime());
}
}

#[test]
fn corner_cases() {
// Test 1 and 2 specifically

// By convention, 1 is composite. That's what `num-prime` returns.
let res = lucas_test(&U64::ONE, BruteForceBase, LucasCheck::AlmostExtraStrong);
assert_eq!(res, Primality::Composite);

let res = lucas_test(
&U64::from(2u32),
&Odd::new(U64::ONE).unwrap(),
BruteForceBase,
LucasCheck::AlmostExtraStrong,
);
assert_eq!(res, Primality::Prime);
assert_eq!(res, Primality::Composite);
}

#[cfg(feature = "tests-exhaustive")]
Expand All @@ -841,40 +862,32 @@ mod tests {
let slpsp = is_slpsp(num);
let vpsp = is_vpsp(num);

let res = lucas_test(
&Uint::<1>::from(num),
BruteForceBase,
LucasCheck::AlmostExtraStrong,
)
.is_probably_prime();
let odd_num = Odd::new(Uint::<1>::from(num)).unwrap();

let res = lucas_test(&odd_num, BruteForceBase, LucasCheck::AlmostExtraStrong)
.is_probably_prime();
let expected = aeslpsp || res_ref;
assert_eq!(
res, expected,
"Brute force base, almost extra strong: n={num}, expected={expected}, actual={res}",
);

let res = lucas_test(
&Uint::<1>::from(num),
BruteForceBase,
LucasCheck::ExtraStrong,
)
.is_probably_prime();
let res =
lucas_test(&odd_num, BruteForceBase, LucasCheck::ExtraStrong).is_probably_prime();
let expected = eslpsp || res_ref;
assert_eq!(
res, expected,
"Brute force base: n={num}, expected={expected}, actual={res}",
);

let res = lucas_test(&Uint::<1>::from(num), SelfridgeBase, LucasCheck::Strong)
.is_probably_prime();
let res = lucas_test(&odd_num, SelfridgeBase, LucasCheck::Strong).is_probably_prime();
let expected = slpsp || res_ref;
assert_eq!(
res, expected,
"Selfridge base: n={num}, expected={expected}, actual={res}",
);

let res = lucas_test(&Uint::<1>::from(num), AStarBase, LucasCheck::LucasV)
.is_probably_prime();
let res = lucas_test(&odd_num, AStarBase, LucasCheck::LucasV).is_probably_prime();
let expected = vpsp || res_ref;

assert_eq!(
Expand Down
2 changes: 1 addition & 1 deletion src/presets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ fn _is_prime_with_rng<const L: usize>(rng: &mut impl CryptoRngCore, num: &Odd<Ui
return false;
}

match lucas_test(num.as_ref(), AStarBase, LucasCheck::Strong) {
match lucas_test(num, AStarBase, LucasCheck::Strong) {
Primality::Composite => return false,
Primality::Prime => return true,
_ => {}
Expand Down

0 comments on commit 685a6ee

Please sign in to comment.