diff --git a/CHANGELOG.md b/CHANGELOG.md index ff9095a..406e137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## v0.4.0 +* Fix divergent behavior on different integer backends. [#49] \ + Each of the following change is **possibly breaking** even if it doesn't change the API: + * `Integer::from_str_radix(s, radix)` requires that `s` is composed of optional leading single + sign (`+` or `-`) and followed by ASCII alphanum characters + * `num-bigint` backend used to allow underscore characters `_` in integer encoding, which now + lead to a parsing error + * `rug` backend used to allow whitspace characters (` `, `\t`, `\n`) in integer encoding, + which is no longer accepted as well. + * `num-bigint` backend used to panic when called `invert`, `pow_mod`, or `combine` with + `modulo=0`. Now it returns `None` in these cases (which is aligned with `rug` backend) + * `Integer::random_below`, `random_below_ref`, and `assign_random_below` return an error if upper + bound is `<= 0` (there are no number to sample the integer from). `rug` used to panicked in + those cases, while `num-bigint` ignored this condition, and simply treated the input as if it + was positive. + * `jacobi` returns an error when invalid `n` is provided. Both backend behaved differently: `rug` + used to return some integer, while `num-bigint` used to return `0` in release and panic in debug + build. + * `num_bigint` integer used to panic in `x << shift` (or `>>`) when the `shift` was negative. Now + it reverses the shift direction, like `rug` does: e.g. `x << -3` becomes `x >> 3` + * Safe prime generation returns an error if asked to generate a prime of bit length `< 100`, as + sampling small primes is not supported by the underlying algorithm. Note that cryptographic + primes need to be way larger. E.g. RSA requires primes to be at least 1536-bits. + * Prime generation returns an error if asked to generate a prime of bit length `< 2` (there are no + primes of that bitsize). Previously, function would panic or hang in infinite loop. + * `sample[_pm]_in_mult_group_of` returns an error if provided `n < 2`. Previously, `n=0` would + panic or hang (depending on the backend), and `n=1` would hang in infinite loop. + +[#49]: https://github.com/LFDT-Lockness/fast-paillier/pull/49 + ## v0.3.3 * Unify encoding of integer=0 across backends [#45] * Deserialize returns an error on invalid radix, instead of panicking [#45] diff --git a/Cargo.lock b/Cargo.lock index 1195f9d..d52fc44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,7 +288,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "fast-paillier" -version = "0.3.3" +version = "0.4.0" dependencies = [ "bytemuck", "ciborium", diff --git a/Cargo.toml b/Cargo.toml index 50823f7..4cdf004 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fast-paillier" -version = "0.3.3" +version = "0.4.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Optimized Paillier encryption scheme" diff --git a/benches/comparison.rs b/benches/comparison.rs index 926f754..584553c 100644 --- a/benches/comparison.rs +++ b/benches/comparison.rs @@ -30,8 +30,8 @@ fn encryption(c: &mut criterion::Criterion) { let mut group = c.benchmark_group("Encrypt"); let mut generate_inputs = || { - let x = ek.n().random_below_ref(&mut rng) - ek.half_n(); - let nonce = Integer::sample_in_mult_group_of(&mut rng, ek.n()); + let x = ek.n().random_below_ref(&mut rng).unwrap() - ek.half_n(); + let nonce = Integer::sample_in_mult_group_of(&mut rng, ek.n()).unwrap(); (x, nonce) }; @@ -83,7 +83,7 @@ fn decryption(c: &mut criterion::Criterion) { let mut group = c.benchmark_group("Decrypt"); - let mut generate_inputs = || Integer::sample_in_mult_group_of(&mut rng, ek.nn()); + let mut generate_inputs = || Integer::sample_in_mult_group_of(&mut rng, ek.nn()).unwrap(); group.bench_function("Decrypt with CRT", |b| { b.iter_batched( @@ -123,8 +123,8 @@ fn omul(c: &mut criterion::Criterion) { let mut group = c.benchmark_group("OMul"); let mut generate_inputs = || { - let scalar = Integer::sample_pm_in_mult_group_of(&mut rng, ek.n()); - let enc_x = Integer::sample_in_mult_group_of(&mut rng, ek.nn()); + let scalar = Integer::sample_pm_in_mult_group_of(&mut rng, ek.n()).unwrap(); + let enc_x = Integer::sample_in_mult_group_of(&mut rng, ek.nn()).unwrap(); (scalar, enc_x) }; @@ -148,7 +148,7 @@ fn omul(c: &mut criterion::Criterion) { pub fn naive_safe_prime(rng: &mut impl rand_core::RngCore, bits: u32) -> Integer { use fast_paillier::backend::IsPrime; loop { - let mut x = Integer::generate_prime(rng, bits - 1); + let mut x = Integer::generate_prime(rng, bits - 1).unwrap(); x <<= 1; x += 1; diff --git a/src/backend.rs b/src/backend.rs index e21047d..931d4b1 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -73,34 +73,56 @@ impl Integer { } /// Samples `x` in Z*_n - pub fn sample_in_mult_group_of(rng: &mut impl rand_core::RngCore, n: &Self) -> Self { + /// + /// Returns an error if `n <= 1`. + pub fn sample_in_mult_group_of( + rng: &mut impl rand_core::RngCore, + n: &Self, + ) -> Result { + if *n <= Integer::one() { + return Err(InvalidInputReason::SampleInMultGroupSize.into()); + } let mut x = Integer::zero(); loop { - x.assign_random_below(n, rng); + x.assign_random_below(n, rng)?; if x.in_mult_group_of(n) { - return x; + return Ok(x); } } } /// Samples `x` such that abs(x) is in `Z*_n` - pub fn sample_pm_in_mult_group_of(rng: &mut impl rand_core::RngCore, n: &Self) -> Self { + /// + /// Returns an error if `n <= 1`. + pub fn sample_pm_in_mult_group_of( + rng: &mut impl rand_core::RngCore, + n: &Self, + ) -> Result { + if *n <= Integer::one() { + return Err(InvalidInputReason::SampleInMultGroupSize.into()); + } let mut x = Integer::zero(); let mut sign_buf = [0u8; 1]; loop { - x.assign_random_below(n, rng); + x.assign_random_below(n, rng)?; rng.fill_bytes(&mut sign_buf); if sign_buf[0] & 1 == 1 { x = -x; } if x.abs_in_mult_group_of(n) { - return x; + return Ok(x); } } } /// Generates a random safe prime - pub fn generate_safe_prime(rng: &mut impl rand_core::RngCore, bits: u32) -> Self { + /// + /// Returns an error if `bits < 100`. Note that cryptographic primes need to be way larger. + /// E.g. RSA requires primes to be at least 1536-bits. + pub fn generate_safe_prime( + rng: &mut impl rand_core::RngCore, + bits: u32, + ) -> Result { sieve_generate_safe_primes(rng, bits, 135) } } @@ -112,14 +134,24 @@ impl Integer { /// The best way to select the parameter is by trial. The one used by /// [`generate_safe_prime`] is indistinguishable from optimal for 500-1700 bit /// lengths. +/// +/// Returns an error if `bits < 100`. Note that cryptographic primes need to be way larger. +/// E.g. RSA requires primes to be at least 1536-bits. pub fn sieve_generate_safe_primes( rng: &mut impl rand_core::RngCore, bits: u32, amount: usize, -) -> Integer { +) -> Result { use crate::backend::IsPrime; use crate::utils::small_primes; + // sieve optimization, the way it is written, rejects valid safe primes of small bitsize. + // The fix would be to use subset of `small_primes` for small `bits`, but we never want + // to generate small safe primes, so no point in complicating the code. + if bits < 100 { + return Err(InvalidInputReason::SafePrimeBitSize.into()); + } + let amount = amount.min(small_primes::SMALL_PRIMES.len()); let mut x = Integer::zero(); @@ -143,7 +175,7 @@ pub fn sieve_generate_safe_primes( x <<= 1; x += 1; if let IsPrime::Yes | IsPrime::Probably = x.is_probably_prime_without_sieve() { - return x; + return Ok(x); } } } @@ -320,6 +352,29 @@ mod serialize { } } +/// Error returned by methods on integer when provided inputs are not acceptable +#[derive(Debug, thiserror::Error)] +#[error("invalid input: {0}")] +pub struct InvalidInput(#[from] InvalidInputReason); + +#[derive(Debug, thiserror::Error)] +enum InvalidInputReason { + #[error("random_below: upper bound must be positive")] + RandomBelowUpperBound, + #[error("jacobi: `n` must be an odd number larger than 2")] + Jacobi, + #[error( + "generate_safe_prime algorithm can only generate primes from 100 bits; note that \ + cryptographic primes need to be way larger. E.g. RSA requires primes to be at least \ + 1536-bits." + )] + SafePrimeBitSize, + #[error("generate_prime was called with `bit_size < 2`, there are no primes of this bit size")] + PrimeBitSize, + #[error("sample_in_[pm_]mult_group: `n` must be at least 2")] + SampleInMultGroupSize, +} + #[cfg(test)] mod test { use super::Integer; @@ -328,10 +383,8 @@ mod test { fn safe_prime_size() { let mut rng = rand_dev::DevRng::new(); for size in [500, 512, 513, 514] { - let mut prime = Integer::generate_safe_prime(&mut rng, size); - // rug doesn't have bit length operations, so - prime >>= size - 1; - assert_eq!(prime, Integer::one()); + let prime = Integer::generate_safe_prime(&mut rng, size).unwrap(); + assert_eq!(prime.significant_bits(), size as u64); } } diff --git a/src/backend/macro_defs.rs b/src/backend/macro_defs.rs index 3a22811..7e86f5f 100644 --- a/src/backend/macro_defs.rs +++ b/src/backend/macro_defs.rs @@ -165,10 +165,8 @@ macro_rules! make_all_ops { $crate::backend::macro_defs::make_assign!(Integer, RemAssign, rem_assign); $crate::backend::macro_defs::make_ops_for_primitive!(Integer, Shl, shl, u32 $(, $complete)?); - $crate::backend::macro_defs::make_ops_for_primitive!(Integer, Shl, shl, i32 $(, $complete)?); $crate::backend::macro_defs::make_ops_for_primitive!(Integer, Shl, shl, usize $(, $complete)?); $crate::backend::macro_defs::make_ops_for_primitive!(Integer, Shr, shr, u32 $(, $complete)?); - $crate::backend::macro_defs::make_ops_for_primitive!(Integer, Shr, shr, i32 $(, $complete)?); $crate::backend::macro_defs::make_ops_for_primitive!(Integer, Shr, shr, usize $(, $complete)?); impl core::ops::Neg for $type { diff --git a/src/backend/num_bigint.rs b/src/backend/num_bigint.rs index 2f8d263..e8cbaca 100644 --- a/src/backend/num_bigint.rs +++ b/src/backend/num_bigint.rs @@ -2,10 +2,12 @@ use alloc::{string::String, vec::Vec}; -use super::IsPrime; use num_integer::Integer as _; use num_traits::Signed as _; +use super::IsPrime; +use crate::backend::{InvalidInput, InvalidInputReason}; + /// Big integer type used in this crate #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] pub struct Integer(num_bigint::BigInt); @@ -75,6 +77,17 @@ impl Integer { } /// Parses the integer using the given radix pub fn from_str_radix(s: &str, radix: u16) -> Option { + // num-bigint already enforces these checks, but we double-enforce them + // to make sure they are in sync with what we already enforce for rug. + // See RugInteger::from_str_radix + if !s + .chars() + .enumerate() + .skip_while(|&(i, c)| i == 0 && (c == '+' || c == '-')) + .all(|(_, c)| c.is_ascii_alphanumeric()) + { + return None; + } num_traits::Num::from_str_radix(s, radix.into()) .ok() .map(Integer) @@ -145,6 +158,10 @@ impl Integer { self.pow_mod_ref(exponent, modulo) } pub fn pow_mod_ref(&self, exponent: &Self, modulo: &Self) -> Option { + // num-bigint panics if modulo is zero; return `None` instead + if modulo.cmp0().is_eq() { + return None; + } let r = if exponent.0.is_negative() { let nself = self.0.modinv(&modulo.0)?; Integer(nself.modpow(&-&exponent.0, &modulo.0)) @@ -215,6 +232,10 @@ impl Integer { self.invert_ref(modulo) } pub fn invert_ref(&self, modulo: &Self) -> Option { + // When modulo=0, num-bigint panics; we return `None` instead + if modulo.cmp0().is_eq() { + return None; + } // Nbi returns wrong result if modulo = 1, and self is negative. Correct response is `0`, // but nbi gives `1`. if self.cmp0().is_lt() && modulo.is_one() { @@ -236,26 +257,41 @@ impl Integer { self } - pub fn random_below(self, rng: &mut impl rand_core::RngCore) -> Self { + /// Generates random integer from range 0..self (exlusive) + /// + /// Returns an error if `self` is not positive number + pub fn random_below(self, rng: &mut impl rand_core::RngCore) -> Result { + if self.cmp0() != core::cmp::Ordering::Greater { + return Err(InvalidInputReason::RandomBelowUpperBound.into()); + } // when the number is a power of two, the randomness generation between // rug and nbi differs, and rug uses exactly the same as random_bits let bits = self.0.bits() - 1; - if self.0.trailing_zeros() == Some(bits) { + Ok(if self.0.trailing_zeros() == Some(bits) { Self::random_bits_u64(bits, rng) } else { let range = num_traits::ConstZero::ZERO..self.0; Integer(rand::Rng::gen_range(rng, range)) - } + }) } - pub fn random_below_ref(&self, rng: &mut impl rand_core::RngCore) -> Self { + /// Generates random integer from range 0..self (exlusive) + /// + /// Returns an error if `self` is not positive number + pub fn random_below_ref( + &self, + rng: &mut impl rand_core::RngCore, + ) -> Result { + if self.cmp0() != core::cmp::Ordering::Greater { + return Err(InvalidInputReason::RandomBelowUpperBound.into()); + } // see the note in random_below let bits = self.0.bits() - 1; - if self.0.trailing_zeros() == Some(bits) { + Ok(if self.0.trailing_zeros() == Some(bits) { Self::random_bits_u64(bits, rng) } else { let range = num_traits::ConstZero::ZERO..self.0.clone(); Integer(rand::Rng::gen_range(rng, range)) - } + }) } pub fn random_bits(bits: u32, rng: &mut impl rand_core::RngCore) -> Self { Self::random_bits_u64(bits.into(), rng) @@ -280,13 +316,16 @@ impl Integer { Integer(num_bigint::BigInt::from_biguint(sign, uint)) } + /// Generates random integer from range 0..modulo (exlusive) and assigns to `self` + /// + /// Returns an error if modulo is not positive. `self` is not modified in this case. pub fn assign_random_below( &mut self, modulo: &Self, rng: &mut impl rand_core::RngCore, - ) -> &mut Self { - *self = modulo.random_below_ref(rng); - self + ) -> Result<&mut Self, InvalidInput> { + *self = modulo.random_below_ref(rng)?; + Ok(self) } pub fn assign_random_bits( @@ -335,7 +374,15 @@ impl Integer { } /// Generates a random prime - pub fn generate_prime(rng: &mut impl rand_core::RngCore, bit_size: u32) -> Self { + /// + /// Returns an error if `bit_size < 2` as there are no primes of this bit size. + pub fn generate_prime( + rng: &mut impl rand_core::RngCore, + bit_size: u32, + ) -> Result { + if bit_size < 2 { + return Err(InvalidInputReason::PrimeBitSize.into()); + } let mut x = Integer::zero(); loop { x.assign_random_bits(bit_size, rng); @@ -345,28 +392,27 @@ impl Integer { x.is_probably_prime(0, rng), IsPrime::Probably | IsPrime::Yes ) { - break x; + break Ok(x); } } } /// Compute jacobi symbol of a over n /// - /// Requires odd `n >= 3`. If it doesn't hold, function - /// panics if debug asserts are enabled, or returns 0 otherwise. + /// Requires odd `n >= 3`. Returns an error if `n` is not in required domain. /// /// Implementation is taken from [Handbook of Applied cryptography][book], /// p. 73, Algorithm 2.149 /// /// [book]: https://cacr.uwaterloo.ca/hac/about/chap2.pdf #[inline(always)] - pub fn jacobi(&self, n: &Self) -> i32 { + pub fn jacobi(&self, n: &Self) -> Result { if n.0.is_even() || n.0 <= num_bigint::BigInt::from(2) { - debug_assert!(false, "n should be an odd number larger than 2"); - return 0; + Err(InvalidInputReason::Jacobi.into()) + } else { + let a = self.modulo_ref(n); + Ok(jacobi_inner(1, &a.0, &n.0)) } - let a = self.modulo_ref(n); - jacobi_inner(1, &a.0, &n.0) } /// Compute l^le * r^re modulo self @@ -437,6 +483,50 @@ fn last_limb(x: &num_bigint::BigInt) -> u32 { x.magnitude().iter_u32_digits().next().unwrap_or(0) } +// num-bigint panics if `<<` or `>>` are called with negative value of i32. +// rug just reverses the direction: (x << -n) == (x >> n). +// We manually reproduce rug's behavior. +impl core::ops::Shl for Integer { + type Output = Integer; + fn shl(self, rhs: i32) -> Self::Output { + if rhs >= 0 { + Self(self.0 << rhs) + } else if rhs == i32::MIN { + // `-rhs` would overflow i32 here + // i32::MIN = -(i32::MAX + 1) + Self(self.0 >> i32::MAX >> 1) + } else { + Self(self.0 >> (-rhs)) + } + } +} +impl core::ops::Shr for Integer { + type Output = Integer; + fn shr(self, rhs: i32) -> Self::Output { + if rhs >= 0 { + Self(self.0 >> rhs) + } else if rhs == i32::MIN { + // `-rhs` would overflow i32 here + // i32::MIN = -(i32::MAX + 1) + Self(self.0 << i32::MAX << 1) + } else { + Self(self.0 << (-rhs)) + } + } +} +impl core::ops::Shl for &Integer { + type Output = Integer; + fn shl(self, rhs: i32) -> Self::Output { + self.clone() << rhs + } +} +impl core::ops::Shr for &Integer { + type Output = Integer; + fn shr(self, rhs: i32) -> Self::Output { + self.clone() >> rhs + } +} + #[cfg(feature = "quickcheck")] impl quickcheck::Arbitrary for Integer { fn arbitrary(g: &mut quickcheck::Gen) -> Self { @@ -451,7 +541,7 @@ impl quickcheck::Arbitrary for Integer { if prev.cmp0().is_eq() { None } else { - prev >>= 1; + prev /= 2; Some(prev.clone()) } })) @@ -499,13 +589,13 @@ mod test { let n = &p * &q; for _ in 0..100 { - let x = n.random_below_ref(&mut rng); + let x = n.random_below_ref(&mut rng).unwrap(); let root = blum_sqrt(&x, &p, &q, &n); let x_ = root.square().modulo(&n); - let jp = x.modulo_ref(&p).jacobi(&p); - let jq = x.modulo_ref(&q).jacobi(&q); - let j = x.jacobi(&n); + let jp = x.modulo_ref(&p).jacobi(&p).unwrap(); + let jq = x.modulo_ref(&q).jacobi(&q).unwrap(); + let j = x.jacobi(&n).unwrap(); assert_eq!(jp * jq, j); if jp == 1 && jq == 1 { diff --git a/src/backend/rug.rs b/src/backend/rug.rs index 73b2294..09e2787 100644 --- a/src/backend/rug.rs +++ b/src/backend/rug.rs @@ -2,9 +2,11 @@ use alloc::{string::String, vec::Vec}; -use super::IsPrime; use rug::Complete; +use super::IsPrime; +use crate::backend::{InvalidInput, InvalidInputReason}; + /// Big integer type used in this crate #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] pub struct Integer(rug::Integer); @@ -76,6 +78,18 @@ impl Integer { } /// Parses the integer using the given radix pub fn from_str_radix(s: &str, radix: u16) -> Option { + // rug accepts strings that have whitespaces in them, integer is parsed while + // ignoring whitespaces, even in the middle of the number. This behavior is + // different from num-bigint backend, and it is strange. We manually restrict + // strings to be ascii alphanum + if !s + .chars() + .enumerate() + .skip_while(|&(i, c)| i == 0 && (c == '+' || c == '-')) + .all(|(_, c)| c.is_ascii_alphanumeric()) + { + return None; + } rug::Integer::from_str_radix(s, radix.into()) .ok() .map(Integer) @@ -206,13 +220,28 @@ impl Integer { self } - pub fn random_below(self, rng: &mut impl rand_core::RngCore) -> Self { + /// Generates random integer from range 0..self (exlusive) + /// + /// Returns an error if `self` is not positive number + pub fn random_below(self, rng: &mut impl rand_core::RngCore) -> Result { + if self.cmp0() != core::cmp::Ordering::Greater { + return Err(InvalidInputReason::RandomBelowUpperBound.into()); + } let mut rng = external_rand(rng); - Integer(self.0.random_below(&mut rng)) + Ok(Integer(self.0.random_below(&mut rng))) } - pub fn random_below_ref(&self, rng: &mut impl rand_core::RngCore) -> Self { + /// Generates random integer from range 0..self (exlusive) + /// + /// Returns an error if `self` is not positive number + pub fn random_below_ref( + &self, + rng: &mut impl rand_core::RngCore, + ) -> Result { + if self.cmp0() != core::cmp::Ordering::Greater { + return Err(InvalidInputReason::RandomBelowUpperBound.into()); + } let mut rng = external_rand(rng); - Integer(self.0.random_below_ref(&mut rng).complete()) + Ok(Integer(self.0.random_below_ref(&mut rng).complete())) } pub fn random_bits(bits: u32, rng: &mut impl rand_core::RngCore) -> Self { let mut rng = external_rand(rng); @@ -229,15 +258,21 @@ impl Integer { } } + /// Generates random integer from range 0..modulo (exlusive) and assigns to `self` + /// + /// Returns an error if modulo is not positive. `self` is not modified in this case. pub fn assign_random_below( &mut self, modulo: &Self, rng: &mut impl rand_core::RngCore, - ) -> &mut Self { + ) -> Result<&mut Self, InvalidInput> { + if modulo.cmp0() != core::cmp::Ordering::Greater { + return Err(InvalidInputReason::RandomBelowUpperBound.into()); + } let mut rng = external_rand(rng); let r = modulo.0.random_below_ref(&mut rng); rug::Assign::assign(&mut self.0, r); - self + Ok(self) } pub fn assign_random_bits( @@ -284,7 +319,15 @@ impl Integer { } /// Generates a random prime - pub fn generate_prime(rng: &mut impl rand_core::RngCore, bit_size: u32) -> Self { + /// + /// Returns an error if `bit_size < 2` as there are no primes of this bit size. + pub fn generate_prime( + rng: &mut impl rand_core::RngCore, + bit_size: u32, + ) -> Result { + if bit_size < 2 { + return Err(InvalidInputReason::PrimeBitSize.into()); + } let mut x = Integer::zero(); loop { x.assign_random_bits(bit_size, rng); @@ -294,13 +337,20 @@ impl Integer { x.is_probably_prime(25, rng), IsPrime::Probably | IsPrime::Yes ) { - break x; + break Ok(x); } } } - pub fn jacobi(&self, n: &Self) -> i32 { - self.0.jacobi(&n.0) + /// Compute jacobi symbol of a over n + /// + /// Requires odd `n >= 3`. Returns an error if `n` is not in required domain. + pub fn jacobi(&self, n: &Self) -> Result { + if n.0.is_even() || n.0 <= 2 { + Err(InvalidInputReason::Jacobi.into()) + } else { + Ok(self.0.jacobi(&n.0)) + } } /// Compute l^le * r^re modulo self @@ -344,9 +394,37 @@ impl quickcheck::Arbitrary for Integer { if prev.cmp0().is_eq() { None } else { - prev >>= 1; + prev /= 2; Some(prev.clone()) } })) } } + +// num_bigint have its own implementation of shl/shr, so we have to write manual dummy +// implementation for rug too, instead of outsourcing it to the macro that handles the rest +// arithmetic ops +impl core::ops::Shl for Integer { + type Output = Integer; + fn shl(self, rhs: i32) -> Self::Output { + Self(self.0 << rhs) + } +} +impl core::ops::Shr for Integer { + type Output = Integer; + fn shr(self, rhs: i32) -> Self::Output { + Self(self.0 >> rhs) + } +} +impl core::ops::Shl for &Integer { + type Output = Integer; + fn shl(self, rhs: i32) -> Self::Output { + self.clone() << rhs + } +} +impl core::ops::Shr for &Integer { + type Output = Integer; + fn shr(self, rhs: i32) -> Self::Output { + self.clone() >> rhs + } +} diff --git a/src/decryption_key.rs b/src/decryption_key.rs index ae3cda0..395e709 100644 --- a/src/decryption_key.rs +++ b/src/decryption_key.rs @@ -28,8 +28,8 @@ impl DecryptionKey { /// /// Samples two safe 1536-bits primes that meets 128 bits security level pub fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Result { - let p = Integer::generate_safe_prime(rng, 1536); - let q = Integer::generate_safe_prime(rng, 1536); + let p = Integer::generate_safe_prime(rng, 1536).map_err(crate::Bug::GenerateSafePrime)?; + let q = Integer::generate_safe_prime(rng, 1536).map_err(crate::Bug::GenerateSafePrime)?; Self::from_primes(p, q) } @@ -106,11 +106,7 @@ impl DecryptionKey { return Err(Reason::Encrypt.into()); } - let x = if x.cmp0().is_ge() { - x.clone() - } else { - x + self.n() - }; + let x = if x.cmp0().is_ge() { x } else { &(x + self.n()) }; // a = (1 + N)^x mod N^2 = (1 + xN) mod N^2 let a = (Integer::one() + x * self.ek.n()) % self.ek.nn(); @@ -135,7 +131,8 @@ impl DecryptionKey { rng: &mut (impl RngCore + CryptoRng), x: &Plaintext, ) -> Result<(Ciphertext, Nonce), Error> { - let nonce = Integer::sample_in_mult_group_of(rng, self.ek.n()); + let nonce = Integer::sample_in_mult_group_of(rng, self.ek.n()) + .map_err(crate::Bug::SampleInMultGroup)?; let ciphertext = self.encrypt_with(x, &nonce)?; Ok((ciphertext, nonce)) } diff --git a/src/encryption_key.rs b/src/encryption_key.rs index 2262b65..e4973e4 100644 --- a/src/encryption_key.rs +++ b/src/encryption_key.rs @@ -16,9 +16,9 @@ pub struct EncryptionKey { impl EncryptionKey { /// Constructs an encryption key from `N` pub fn from_n(n: Integer) -> Self { - let nn = n.clone() * &n; - let half_n = n.clone() >> 1u32; - let neg_half_n = -half_n.clone(); + let nn = &n * &n; + let half_n = &n >> 1u32; + let neg_half_n = -&half_n; Self { n, nn, @@ -63,14 +63,10 @@ impl EncryptionKey { return Err(Reason::Encrypt.into()); } - let x = if x.cmp0().is_ge() { - x.clone() - } else { - x + self.n() - }; + let x = if x.cmp0().is_ge() { x } else { &(x + self.n()) }; // a = (1 + N)^x mod N^2 = (1 + xN) mod N^2 - let a = (Integer::one() + &x * self.n()) % self.nn(); + let a = (Integer::one() + x * self.n()) % self.nn(); // b = nonce^N mod N^2 let b = nonce .pow_mod_ref(self.n(), self.nn()) @@ -90,7 +86,8 @@ impl EncryptionKey { rng: &mut (impl RngCore + CryptoRng), x: &Plaintext, ) -> Result<(Ciphertext, Nonce), Error> { - let nonce = Integer::sample_in_mult_group_of(rng, self.n()); + let nonce = Integer::sample_in_mult_group_of(rng, self.n()) + .map_err(crate::Bug::SampleInMultGroup)?; let ciphertext = self.encrypt_with(x, &nonce)?; Ok((ciphertext, nonce)) } diff --git a/src/lib.rs b/src/lib.rs index 2453154..8930323 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,6 +74,10 @@ enum Reason { enum Bug { #[error("pow mod undefined")] PowModUndef, + #[error("sample_in_mult_group_of: {0}")] + SampleInMultGroup(backend::InvalidInput), + #[error("generate_safe_prime: {0}")] + GenerateSafePrime(backend::InvalidInput), } impl From for Error { @@ -171,7 +175,8 @@ impl AnyEncryptionKeyExt for E { rng: &mut (impl RngCore + CryptoRng), x: &Plaintext, ) -> Result<(Ciphertext, Nonce), Error> { - let nonce = Integer::sample_in_mult_group_of(rng, self.n()); + let nonce = + Integer::sample_in_mult_group_of(rng, self.n()).map_err(Bug::SampleInMultGroup)?; let ciphertext = self.encrypt_with(x, &nonce)?; Ok((ciphertext, nonce)) } diff --git a/tests/integration/backend.rs b/tests/integration/backend.rs index 24a5b97..1d459dc 100644 --- a/tests/integration/backend.rs +++ b/tests/integration/backend.rs @@ -12,6 +12,9 @@ macro_rules! make_quickcheck { // Case for normal methods. Called like `make_quickcheck!(method_name(self: // T1, arg: T2, arg2: T3))` ($method:ident ( self: $self_ty:ty $(, $arg:ident: $t:ty)* $(,)? ) ) => { + make_quickcheck!($method(self: $self_ty $(,$arg: $t)*), |a, b| a.equals(b)); + }; + ($method:ident ( self: $self_ty:ty $(, $arg:ident: $t:ty)* $(,)? ), |$r1:ident, $r2: ident| $compare:expr ) => { quickcheck::quickcheck! { fn $method(nbi: $self_ty $(, $arg: $t)*) -> bool { #[allow(unused_mut)] @@ -21,7 +24,7 @@ macro_rules! make_quickcheck { let mut rug = RugInteger::from_bytes_msf_signed(&bytes, sign); - let r1 = { + let $r1 = { // Since Arg::to_nbi takes mutable reference to the arg, we clone them, // so the `rug` method below receives the same args as provided by // quickcheck without any modification @@ -36,7 +39,7 @@ macro_rules! make_quickcheck { )* ) }; - let r2 = { + let $r2 = { // Change args mutability $( let mut $arg = $arg; @@ -49,7 +52,7 @@ macro_rules! make_quickcheck { ) }; - r1.equals(r2) + $compare } } }; @@ -96,6 +99,20 @@ macro_rules! make_quickcheck { }; } +/// Compare function to be used with methods that return `Result<..., E>` where `E` +/// does not implement `Eq` +fn compare_results(r1: Result, r2: Result) -> bool +where + A: HeteroEq, + E: core::fmt::Debug, +{ + match (r1, r2) { + (Ok(a), Ok(b)) => a.equals(b), + (Err(a), Err(b)) => format!("{a:?}") == format!("{b:?}"), + _ => false, + } +} + make_quickcheck!(is_one(self: NbiInteger)); make_quickcheck!(is_even(self: NbiInteger)); make_quickcheck!(cmp_abs(self: NbiInteger, other: RefInteger)); @@ -103,10 +120,10 @@ make_quickcheck!(lcm_ref(self: NbiInteger, other: RefInteger)); make_quickcheck!(gcd_ref(self: NbiInteger, other: RefInteger)); make_quickcheck!(cmp0(self: NbiInteger)); make_quickcheck!( - pow_mod(self: NbiInteger, exponent: RefInteger, modulo: NonZero) + pow_mod(self: NbiInteger, exponent: RefInteger, modulo: RefInteger) ); make_quickcheck!( - pow_mod_ref(self: NbiInteger, exponent: RefInteger, modulo: NonZero) + pow_mod_ref(self: NbiInteger, exponent: RefInteger, modulo: RefInteger) ); make_quickcheck!(Self::u_pow_u(base: u32, exponent: SmallU32)); make_quickcheck!(square(self: NbiInteger)); @@ -119,19 +136,19 @@ make_quickcheck!(modulo_mut(self: NbiInteger, divisor: Positive)); make_quickcheck!(mod_u(self: NbiInteger, divisor: Positive)); make_quickcheck!(significant_bits(self: NbiInteger)); make_quickcheck!(significant_dwords(self: NbiInteger)); -make_quickcheck!(invert(self: NbiInteger, modulo: Positive)); -make_quickcheck!(invert_ref(self: NbiInteger, modulo: Positive)); +make_quickcheck!(invert(self: NbiInteger, modulo: RefInteger)); +make_quickcheck!(invert_ref(self: NbiInteger, modulo: RefInteger)); make_quickcheck!(set_bit(self: NbiInteger, index: SmallU32, value: bool)); -make_quickcheck!(random_below(self: Positive, rng: Prng)); -make_quickcheck!(random_below_ref(self: Positive, rng: Prng)); +make_quickcheck!(random_below(self: NbiInteger, rng: Prng), |a, b| compare_results(a, b)); +make_quickcheck!(random_below_ref(self: NbiInteger, rng: Prng), |a, b| compare_results(a, b)); make_quickcheck!(Self::random_bits(bits: SmallU32, rng: Prng)); make_quickcheck!(Self::random_bits_signed(bits: SmallU32, rng: Prng)); -make_quickcheck!(assign_random_below(self: NbiInteger, modulo: Positive, rng: Prng)); +make_quickcheck!(assign_random_below(self: NbiInteger, modulo: RefInteger, rng: Prng), |a, b| compare_results(a, b)); make_quickcheck!(assign_random_bits(self: NbiInteger, bits: SmallU32, rng: Prng)); -make_quickcheck!(jacobi(self: NbiInteger, n: OddPositive)); +make_quickcheck!(jacobi(self: NbiInteger, n: RefInteger), |a, b| compare_results(a, b)); make_quickcheck!( combine( - self: NonZero, + self: NbiInteger, l: RefInteger, le: RefInteger, r: RefInteger, @@ -141,6 +158,8 @@ make_quickcheck!( make_quickcheck!(to_bytes_msf(self: NbiInteger)); make_quickcheck!(to_bytes_msf_signed(self: NbiInteger)); make_quickcheck!(to_bytes_lsf(self: NbiInteger)); +make_quickcheck!(abs(self: NbiInteger)); +make_quickcheck!(sign(self: NbiInteger)); #[test] fn round_random() { @@ -150,36 +169,100 @@ fn round_random() { let rug = RugInteger::from(8); for _ in 0..32 { - let nbi_random = nbi.random_below_ref(&mut nbi_rng); - let rug_random = rug.random_below_ref(&mut rug_rng); + let nbi_random = nbi.random_below_ref(&mut nbi_rng).unwrap(); + let rug_random = rug.random_below_ref(&mut rug_rng).unwrap(); assert!(nbi_random.equals(rug_random)); } } quickcheck::quickcheck! { - fn serde_json(nbi: NbiInteger) -> bool { - let r1 = serde_json::to_string(&nbi).unwrap(); - - let (bytes, sign) = nbi.to_bytes_msf_signed(); - let rug = RugInteger::from_bytes_msf_signed(&bytes, sign); - let r2 = serde_json::to_string(&rug).unwrap(); + fn serde_json(x: RefInteger) -> bool { + let r1 = serde_json::to_string(&x.0).unwrap(); + let r2 = serde_json::to_string(&x.1).unwrap(); r1 == r2 } - fn is_probably_prime(nbi: NbiInteger, rng: Prng) -> bool { + fn is_probably_prime(x: RefInteger, rng: Prng) -> bool { use fast_paillier::backend::IsPrime; let mut rng = rng; - let (bytes, sign) = nbi.to_bytes_msf_signed(); - let rug = RugInteger::from_bytes_msf_signed(&bytes, sign); - let r1 = nbi.is_probably_prime(25, &mut rng.rng); - let r2 = rug.is_probably_prime(25, &mut rng.rng); + let r1 = x.0.is_probably_prime(25, &mut rng.rng); + let r2 = x.1.is_probably_prime(25, &mut rng.rng); // It doesn't matter if one backend returns `Yes`, and the other returns `Probably`. // It matters to check if one returns `Yes|Probably`, and the other returns `No` matches!((r1, r2), (IsPrime::Yes | IsPrime::Probably, IsPrime::Yes | IsPrime::Probably) | (IsPrime::No, IsPrime::No)) } + + fn from_radix(s: AsciiString) -> bool { + let nbi = NbiInteger::from_str_radix(&s.0, 16); + let rug = RugInteger::from_str_radix(&s.0, 16); + eprintln!("{nbi:?}, {rug:?}"); + match (nbi, rug) { + (Some(a), Some(b)) => a.equals(b), + (None, None) => true, + _ => false + } + } + + fn shl_i32(x: RefInteger, shift: i32) -> bool { + let shift = shift % 100; + let r1 = x.0 << shift; + let r2 = x.1 << shift; + r1.equals(r2) + } + fn shr_i32(x: RefInteger, shift: i32) -> bool { + let shift = shift % 100; + let r1 = x.0 >> shift; + let r2 = x.1 >> shift; + r1.equals(r2) + } + fn shl_u32(x: RefInteger, shift: u32) -> bool { + let shift = shift % 100; + let r1 = x.0 << shift; + let r2 = x.1 << shift; + r1.equals(r2) + } + fn shr_u32(x: RefInteger, shift: u32) -> bool { + let shift = shift % 100; + let r1 = x.0 >> shift; + let r2 = x.1 >> shift; + r1.equals(r2) + } + fn shl_usize(x: RefInteger, shift: usize) -> bool { + let shift = shift % 100; + let r1 = x.0 << shift; + let r2 = x.1 << shift; + r1.equals(r2) + } + fn shr_usize(x: RefInteger, shift: usize) -> bool { + let shift = shift % 100; + let r1 = x.0 >> shift; + let r2 = x.1 >> shift; + r1.equals(r2) + } +} + +#[test] +fn shift_by_i32_min() { + for x in [5i32, -5, 0, 1, -1] { + let nbi = NbiInteger::from(x); + let rug = RugInteger::from(x); + + // x << i32::MIN == x >> 2^31: 0 for non-negative x, -1 for negative x + assert!((&nbi << i32::MIN).equals(&rug << i32::MIN)); + assert!((nbi.clone() << i32::MIN).equals(rug.clone() << i32::MIN)); + let expected = if x < 0 { -1 } else { 0 }; + assert_eq!(nbi << i32::MIN, NbiInteger::from(expected)); + } + + // x >> i32::MIN == x << 2^31, which allocates ton of memory on heap for non-zero x, so only + // check zero to make sure it doesn't panic + let nbi = NbiInteger::from(0u8); + let rug = RugInteger::from(0u8); + assert!((&nbi >> i32::MIN).equals(&rug >> i32::MIN)); + assert!((nbi >> i32::MIN).equals(rug >> i32::MIN)); } ///// Helper traits for macro ///// @@ -295,38 +378,6 @@ impl From for RefInteger { } } -/// Helper quickcheck newtype. Similar to [`RefInteger`], but generates integers -/// that are odd and larger than 2 (to be used as an argument to `jacobi`) -#[derive(Clone, Debug)] -struct OddPositive(NbiInteger, RugInteger); -impl<'a> Arg<'a> for OddPositive { - type NbiArg = &'a NbiInteger; - type RugArg = &'a RugInteger; - fn to_nbi(&'a mut self) -> Self::NbiArg { - &self.0 - } - fn to_rug(&'a mut self) -> Self::RugArg { - &self.1 - } -} -impl quickcheck::Arbitrary for OddPositive { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let nbi = NbiInteger::arbitrary(g); - - // make odd positive - let (_, nbi) = nbi.to_num_bigint().into_parts(); - let nbi = num_bigint::BigInt::from_biguint(num_bigint::Sign::Plus, nbi); - let mut nbi = NbiInteger::from_num_bigint(nbi); - if nbi < NbiInteger::from(2) { - nbi |= 2; - } - nbi |= 1; - - let rug = RugInteger::from_bytes_msf(&nbi.to_bytes_msf()); - OddPositive(nbi, rug) - } -} - /// Helper quickcheck newtype: generates a u32 that is small enough to be used /// as a bit index or a (non-modular) exponent #[derive(Clone, Copy, Debug)] @@ -455,3 +506,16 @@ impl<'a> Arg<'a> for Prng { &mut self.rng } } + +#[derive(Debug, Clone)] +struct AsciiString(String); +impl quickcheck::Arbitrary for AsciiString { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let size = u8::arbitrary(g) % 32; + let s = std::iter::repeat_with(|| u8::arbitrary(g) & 0x7F) + .take(usize::from(size)) + .map(char::from) + .collect(); + Self(s) + } +} diff --git a/tests/integration/main.rs b/tests/integration/main.rs index 572352b..26006bf 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -16,7 +16,7 @@ fn encrypt_decrypt() { for _ in 0..50 { // Generate plaintext in [-N/2; N/2) - let plaintext = ek.n().random_below_ref(&mut rng); + let plaintext = ek.n().random_below_ref(&mut rng).unwrap(); let plaintext = plaintext - (ek.n() / 2u8); println!("Plaintext: {plaintext}"); @@ -79,8 +79,8 @@ fn homorphic_ops() { let ek = dk.encryption_key(); for _ in 0..100 { - let a = ek.n().random_below_ref(&mut rng); - let b = ek.n().random_below_ref(&mut rng); + let a = ek.n().random_below_ref(&mut rng).unwrap(); + let b = ek.n().random_below_ref(&mut rng).unwrap(); let a = a - (ek.n() / 2u8); let b = b - (ek.n() / 2u8); println!("a: {a}"); @@ -127,10 +127,10 @@ fn encryption_with_known_factorization() { for i in 0..100 { println!("Iteration {i}"); - let x = ek.n().random_below_ref(&mut rng); + let x = ek.n().random_below_ref(&mut rng).unwrap(); let x = x - ek.half_n(); - let nonce = Integer::sample_in_mult_group_of(&mut rng, ek.n()); + let nonce = Integer::sample_in_mult_group_of(&mut rng, ek.n()).unwrap(); let enc_x1 = ek.encrypt_with(&x, &nonce).unwrap(); let enc_x2 = dk.encrypt_with(&x, &nonce).unwrap(); @@ -143,15 +143,15 @@ fn encryption_with_known_factorization() { fn factorized_exp_mod_n() { let mut rng = rand_dev::DevRng::new(); - let p = Integer::generate_safe_prime(&mut rng, 512); - let q = Integer::generate_safe_prime(&mut rng, 512); + let p = Integer::generate_safe_prime(&mut rng, 512).unwrap(); + let q = Integer::generate_safe_prime(&mut rng, 512).unwrap(); let n = &p * &q; println!("n: {n}"); let crt = utils::CrtExp::build_n(&p, &q).unwrap(); for _ in 0..100 { - let x: Integer = n.random_below_ref(&mut rng); + let x: Integer = n.random_below_ref(&mut rng).unwrap(); let mut e: Integer = Integer::random_bits(1024, &mut rng); if rng.gen::() { e = -e @@ -172,15 +172,15 @@ fn factorized_exp_mod_n() { fn factorized_exp_mod_nn() { let mut rng = rand_dev::DevRng::new(); - let p = Integer::generate_safe_prime(&mut rng, 512); - let q = Integer::generate_safe_prime(&mut rng, 512); + let p = Integer::generate_safe_prime(&mut rng, 512).unwrap(); + let q = Integer::generate_safe_prime(&mut rng, 512).unwrap(); let nn = (&p * &q).square(); println!("nn: {nn}"); let crt = utils::CrtExp::build_nn(&p, &q).unwrap(); for _ in 0..100 { - let x = nn.random_below_ref(&mut rng); + let x = nn.random_below_ref(&mut rng).unwrap(); let mut e = Integer::random_bits(1024, &mut rng); if rng.gen::() { e = -e @@ -213,7 +213,80 @@ fn unsigned_mod_to_signed(x: Integer, n: &Integer) -> Integer { } fn random_key_for_tests(rng: &mut impl rand_core::RngCore) -> DecryptionKey { - let p = Integer::generate_safe_prime(rng, 512); - let q = Integer::generate_safe_prime(rng, 512); + let p = Integer::generate_safe_prime(rng, 512).unwrap(); + let q = Integer::generate_safe_prime(rng, 512).unwrap(); DecryptionKey::from_primes(p, q).unwrap() } + +#[test] +fn bigint_from_radix_prohibits_whitespaces() { + assert_eq!(Integer::from_str_radix("123 4", 10), None) +} + +#[test] +fn sample_in_multgroup_of_nonpositive_returns_err() { + let mut rng = rand_dev::DevRng::new(); + let mut x = 0u8; + rand_core::RngCore::fill_bytes(&mut rng, core::slice::from_mut(&mut x)); + + for x in (0..20).chain([x]) { + let x = -Integer::from(x); + let result = Integer::sample_in_mult_group_of(&mut rng, &x); + + assert!( + result.is_err(), + "sampling from nonpositive group must return an error" + ); + + let result = Integer::sample_pm_in_mult_group_of(&mut rng, &x); + assert!( + result.is_err(), + "sampling from nonpositive group must return an error" + ); + } +} + +#[test] +fn gen_safe_primes_panics_on_small_bitsize() { + let mut rng = rand_dev::DevRng::new(); + for bitsize in 0..100 { + let _ = fast_paillier::backend::sieve_generate_safe_primes(&mut rng, bitsize, 1000) + .unwrap_err(); + } +} + +#[test] +fn gen_safe_primes_100bits() { + let mut rng = rand_dev::DevRng::new(); + let _ = fast_paillier::backend::sieve_generate_safe_primes(&mut rng, 100, 1000); +} + +#[test] +fn gen_prime_returns_error_on_small_bitsize() { + let mut rng = rand_dev::DevRng::new(); + for bitsize in 0..2 { + let _ = Integer::generate_prime(&mut rng, bitsize).unwrap_err(); + } +} +#[test] +fn gen_prime_2bits() { + let mut rng = rand_dev::DevRng::new(); + Integer::generate_prime(&mut rng, 2).unwrap(); +} + +#[test] +fn sample_in_mult_group_0_or_1_return_error() { + let mut rng = rand_dev::DevRng::new(); + for n in 0..=1 { + Integer::sample_in_mult_group_of(&mut rng, &Integer::from(n)).unwrap_err(); + Integer::sample_pm_in_mult_group_of(&mut rng, &Integer::from(n)).unwrap_err(); + } +} + +#[test] +fn sample_in_mult_group_2_works() { + let mut rng = rand_dev::DevRng::new(); + + let x = Integer::sample_in_mult_group_of(&mut rng, &Integer::from(2)).unwrap(); + assert_eq!(x, Integer::one()); +}