Skip to content

Commit 22a0c81

Browse files
authored
Merge pull request #26 from fjarri/ncc-audit
NCC audit fixes
2 parents c6bbdf3 + 537a097 commit 22a0c81

File tree

7 files changed

+59
-25
lines changed

7 files changed

+59
-25
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
44
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
55

66

7+
## [0.3.1] - Unreleased
8+
9+
### Fixed
10+
11+
- `Sieve::new()` now panics when `max_bit_length == 0` (which would lead to incorrect results anyway, so it is not considered a breaking change). ([#26])
12+
- Default preset now uses A* instead of A base selection method for the Lucas test. This does not change the outcomes, but is implemented as a security recommendation. ([#26])
13+
14+
15+
[#26]: https://github.com/nucypher/rust-umbral/pull/26
16+
17+
718
## [0.3.0] - 2023-05-05
819

920
### Changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This library implements prime number generation and primality checking for [`cry
1010
In particular:
1111

1212
- Generating random primes and safe primes of given bit size;
13-
- Sieving iterator and one-time trial division by small integers;
13+
- Sieving iterator;
1414
- Miller-Rabin test;
1515
- Strong and extra strong Lucas tests, and Lucas-V test.
1616

src/hazmat/jacobi.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ pub(crate) fn jacobi_symbol<const L: usize>(a: i32, p_long: &Uint<L>) -> JacobiS
7777
if p_long.is_even().into() {
7878
panic!("`p_long` must be an odd integer, but got {}", p_long);
7979
}
80-
if a == i32::MIN {
81-
// Otherwise it will cause an overflow when taking its absolute value.
82-
panic!("`a` must be greater than `i32::MIN`");
83-
}
8480

8581
let result = JacobiSymbol::One; // Keep track of all the sign flips here.
8682

@@ -185,12 +181,6 @@ mod tests {
185181
let _j = jacobi_symbol(1, &U128::from(4u32));
186182
}
187183

188-
#[test]
189-
#[should_panic(expected = "`a` must be greater than `i32::MIN`")]
190-
fn jacobi_symbol_a_is_too_small() {
191-
let _j = jacobi_symbol(i32::MIN, &U128::from(5u32));
192-
}
193-
194184
// Reference from `num-modular` - supports long `p`, but only positive `a`.
195185
fn jacobi_symbol_ref(a: i32, p: &U128) -> JacobiSymbol {
196186
let a_bi = BigInt::from(a);
@@ -224,11 +214,18 @@ mod tests {
224214
let a = 2147483647i32; // 2^31 - 1, a prime
225215
let p = U128::from_be_hex("000000007ffffffeffffffe28000003b"); // (2^31 - 1) * (2^64 - 59)
226216
assert_eq!(jacobi_symbol(a, &p), JacobiSymbol::Zero);
217+
assert_eq!(jacobi_symbol_ref(a, &p), JacobiSymbol::Zero);
227218

228219
// a = x^2 mod p, should give 1.
229220
let a = 659456i32; // Obtained from x = 2^70
230221
let p = U128::from_be_hex("ffffffffffffffffffffffffffffff5f"); // 2^128 - 161 - not a prime
231222
assert_eq!(jacobi_symbol(a, &p), JacobiSymbol::One);
223+
assert_eq!(jacobi_symbol_ref(a, &p), JacobiSymbol::One);
224+
225+
let a = i32::MIN; // -2^31, check that no overflow occurs
226+
let p = U128::from_be_hex("000000007ffffffeffffffe28000003b"); // (2^31 - 1) * (2^64 - 59)
227+
assert_eq!(jacobi_symbol(a, &p), JacobiSymbol::One);
228+
assert_eq!(jacobi_symbol_ref(a, &p), JacobiSymbol::One);
232229
}
233230

234231
prop_compose! {

src/hazmat/lucas.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Lucas primality test.
22
use crypto_bigint::{
33
modular::runtime_mod::{DynResidue, DynResidueParams},
4-
Integer, Limb, Uint, Word,
4+
CheckedAdd, Integer, Limb, Uint, Word,
55
};
66

77
use super::{
@@ -21,7 +21,8 @@ const MAX_ATTEMPTS: u32 = 10000;
2121
// (~30x for 1024-bit numbers, ~100x for 2048 bit).
2222
// On the other hand, if `n` is a non-square we expect to find a `D`
2323
// in just a few attempts on average (an estimate for the Selfridge method
24-
// can be found in [^1], section 7; for the brute force method it seems to be about the same).
24+
// can be found in [^Baillie1980], section 7; for the brute force method
25+
// it seems to be about the same).
2526
const ATTEMPTS_BEFORE_SQRT: u32 = 30;
2627

2728
/// A method for selecting the base `(P, Q)` for the Lucas primality test.
@@ -186,7 +187,7 @@ fn decompose<const L: usize>(n: &Uint<L>) -> (u32, Uint<L>) {
186187
}
187188

188189
// This won't overflow since the original `n` was odd, so we right-shifted at least once.
189-
(s, n.wrapping_add(&Uint::<L>::ONE))
190+
(s, n.checked_add(&Uint::<L>::ONE).expect("Integer overflow"))
190191
}
191192

192193
/// The checks to perform in the Lucas test.
@@ -349,7 +350,7 @@ pub fn lucas_test<const L: usize>(
349350
// Compute d-th element of Lucas sequence (U_d(P, Q), V_d(P, Q)), where:
350351
//
351352
// V_0 = 2
352-
// U_0 = 1
353+
// U_0 = 0
353354
//
354355
// U_{2k} = U_k V_k
355356
// V_{2k} = V_k^2 - 2 Q^k

src/hazmat/miller_rabin.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rand_core::CryptoRngCore;
44

55
use crypto_bigint::{
66
modular::runtime_mod::{DynResidue, DynResidueParams},
7-
Integer, NonZero, RandomMod, Uint,
7+
CheckedAdd, Integer, NonZero, RandomMod, Uint,
88
};
99

1010
use super::Primality;
@@ -104,8 +104,11 @@ impl<const L: usize> MillerRabin<L> {
104104

105105
let range = self.candidate.wrapping_sub(&Uint::<L>::from(4u32));
106106
let range_nonzero = NonZero::new(range).unwrap();
107-
let random =
108-
Uint::<L>::random_mod(rng, &range_nonzero).wrapping_add(&Uint::<L>::from(3u32));
107+
// This should not overflow as long as `random_mod()` behaves according to the contract
108+
// (that is, returns a number within the given range).
109+
let random = Uint::<L>::random_mod(rng, &range_nonzero)
110+
.checked_add(&Uint::<L>::from(3u32))
111+
.expect("Integer overflow");
109112
self.test(&random)
110113
}
111114
}

src/hazmat/sieve.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
use alloc::{vec, vec::Vec};
55

6-
use crypto_bigint::{Random, Uint};
6+
use crypto_bigint::{CheckedAdd, Random, Uint};
77
use rand_core::CryptoRngCore;
88

99
use crate::hazmat::precomputed::{SmallPrime, RECIPROCALS, SMALL_PRIMES};
@@ -75,10 +75,14 @@ impl<const L: usize> Sieve<L> {
7575
/// Note that `start` is adjusted to `2`, or the next `1 mod 2` number (`safe_primes = false`);
7676
/// and `5`, or `3 mod 4` number (`safe_primes = true`).
7777
///
78-
/// Panics if `max_bit_length` is greater than the size of the target `Uint`.
78+
/// Panics if `max_bit_length` is zero or greater than the size of the target `Uint`.
7979
///
8080
/// If `safe_primes` is `true`, both the returned `n` and `n/2` are sieved.
8181
pub fn new(start: &Uint<L>, max_bit_length: usize, safe_primes: bool) -> Self {
82+
if max_bit_length == 0 {
83+
panic!("The requested bit length cannot be zero");
84+
}
85+
8286
if max_bit_length > Uint::<L>::BITS {
8387
panic!(
8488
"The requested bit length ({}) is larger than the chosen Uint size",
@@ -148,7 +152,13 @@ impl<const L: usize> Sieve<L> {
148152
}
149153

150154
// Set the new base.
151-
self.base = self.base.wrapping_add(&self.incr.into());
155+
// Should not overflow since `incr` is never greater than `incr_limit`,
156+
// and the latter is chosen such that it doesn't overflow when added to `base`
157+
// (see the rest of this method).
158+
self.base = self
159+
.base
160+
.checked_add(&self.incr.into())
161+
.expect("Integer overflow");
152162

153163
self.incr = 0;
154164

@@ -206,7 +216,13 @@ impl<const L: usize> Sieve<L> {
206216
let result = if self.current_is_composite() {
207217
None
208218
} else {
209-
let mut num = self.base.wrapping_add(&self.incr.into());
219+
// The overflow should never happen here since `incr`
220+
// is never greater than `incr_limit`, and the latter is chosen such that
221+
// it does not overflow when added to `base` (see `update_residues()`).
222+
let mut num = self
223+
.base
224+
.checked_add(&self.incr.into())
225+
.expect("Integer overflow");
210226
if self.safe_primes {
211227
num = (num << 1) | Uint::<L>::ONE;
212228
}
@@ -333,6 +349,12 @@ mod tests {
333349
check_sieve(13, 4, true, &[]);
334350
}
335351

352+
#[test]
353+
#[should_panic(expected = "The requested bit length cannot be zero")]
354+
fn sieve_zero_bits() {
355+
let _sieve = Sieve::new(&U64::ONE, 0, false);
356+
}
357+
336358
#[test]
337359
#[should_panic(expected = "The requested bit length (65) is larger than the chosen Uint size")]
338360
fn sieve_too_many_bits() {

src/presets.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rand_core::CryptoRngCore;
55
use rand_core::OsRng;
66

77
use crate::hazmat::{
8-
lucas_test, random_odd_uint, LucasCheck, MillerRabin, Primality, SelfridgeBase, Sieve,
8+
lucas_test, random_odd_uint, AStarBase, LucasCheck, MillerRabin, Primality, Sieve,
99
};
1010

1111
/// Returns a random prime of size `bit_length` using [`OsRng`] as the RNG.
@@ -100,7 +100,7 @@ pub fn generate_safe_prime_with_rng<const L: usize>(
100100
///
101101
/// Performed checks:
102102
/// - Miller-Rabin check with base 2;
103-
/// - Strong Lucas check with Selfridge base (a.k.a. Baillie method A);
103+
/// - Strong Lucas check with A* base (see [`AStarBase`] for details);
104104
/// - Miller-Rabin check with a random base.
105105
///
106106
/// See [`MillerRabin`] and [`lucas_test`] for more details about the checks.
@@ -158,7 +158,7 @@ fn _is_prime_with_rng<const L: usize>(rng: &mut impl CryptoRngCore, num: &Uint<L
158158
return false;
159159
}
160160

161-
match lucas_test(num, SelfridgeBase, LucasCheck::Strong) {
161+
match lucas_test(num, AStarBase, LucasCheck::Strong) {
162162
Primality::Composite => return false,
163163
Primality::Prime => return true,
164164
_ => {}

0 commit comments

Comments
 (0)