Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jellevos/scicrypt
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.7.1
Choose a base ref
...
head repository: jellevos/scicrypt
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 5 commits
  • 8 files changed
  • 1 contributor

Commits on Sep 12, 2022

  1. Copy the full SHA
    be3f820 View commit details

Commits on Sep 17, 2022

  1. Copy the full SHA
    8f6e76a View commit details

Commits on Sep 19, 2022

  1. Encrypt zero using m + n

    jellevos committed Sep 19, 2022
    Copy the full SHA
    8a88d1f View commit details
  2. Merge pull request #51 from jellevos/optimize-he2

    Optimize Paillier encryption
    jellevos authored Sep 19, 2022
    Copy the full SHA
    6d9f3a1 View commit details

Commits on Sep 26, 2022

  1. Fix addition overflow bug (#52)

    * Stub
    
    * Fix bug in addition overflow
    
    * Run cargo fmt
    jellevos authored Sep 26, 2022
    Copy the full SHA
    789c88a View commit details
2 changes: 2 additions & 0 deletions scicrypt-bigint/README.md
Original file line number Diff line number Diff line change
@@ -13,6 +13,8 @@ Almost all function should run in constant-time, and only leak information about
- `set_bit_leaky` and `clear_bit_leaky`
- `is_probably_prime_leaky`

**Warning: Additions currently leak the carry. We are working on overcoming this limitation.**

To make code easier to read, one can call `leak()` on an `UnsignedInteger` to get a `LeakyUnsignedInteger` that supports overloaded operators for leaky operations.

We are unsure about random number generation.
81 changes: 70 additions & 11 deletions scicrypt-bigint/src/arithmetic/add.rs
Original file line number Diff line number Diff line change
@@ -12,24 +12,42 @@ impl AddAssign<&UnsignedInteger> for UnsignedInteger {
debug_assert!(self.size_in_bits >= rhs.size_in_bits);
debug_assert!(self.value.size >= rhs.value.size);

let n = rhs.value.size;

if n == 0 {
if rhs.value.size == 0 {
return;
}

unsafe {
let carry = gmp::mpn_add_n(
// LHS has more limbs than RHS, so add all RHS limbs to the corresponding limbs on the LHS.
let mut carry = gmp::mpn_add_n(
self.value.d.as_mut(),
self.value.d.as_ptr(),
rhs.value.d.as_ptr(),
n as i64,
rhs.value.size as i64,
);

let largest_size = self.value.size as i32;

self.value.size = largest_size + carry as i32;
self.size_in_bits = self.size_in_bits + carry as u32;
let remaining_size = (self.value.size - rhs.value.size) as i64;
if remaining_size != 0 {
// Propagate the carry over the remaining (more significant) limbs on the LHS.
let scratch_size =
gmp::mpn_sec_add_1_itch(remaining_size) as usize * GMP_NUMB_BITS as usize;
let mut scratch = Scratch::new(scratch_size);

carry = gmp::mpn_sec_add_1(
self.value.d.as_ptr().offset(rhs.value.size as isize),
self.value.d.as_ptr().offset(rhs.value.size as isize),
remaining_size,
carry,
scratch.as_mut(),
);
}

// Save the carry in a new limb
if carry == 1u64 {
gmp::mpz_realloc2(&mut self.value, self.value.size as u64 + 1);
*self.value.d.as_ptr().offset(self.value.size as isize) = carry;
self.value.size += 1;
self.size_in_bits += 1;
}
}
}
}
@@ -59,8 +77,13 @@ impl AddAssign<u64> for UnsignedInteger {
scratch.as_mut(),
);

self.value.size += carry as i32;
self.size_in_bits += carry as u32;
// Save the carry in a new limb
if carry == 1u64 {
gmp::mpz_realloc2(&mut self.value, self.value.size as u64 + 1);
*self.value.d.as_ptr().offset(self.value.size as isize) = carry;
self.value.size += 1;
self.size_in_bits += 1;
}
}
}
}
@@ -108,6 +131,42 @@ mod tests {
assert_eq!(x.size_in_bits, 103);
}

#[test]
fn test_addition_overflow() {
let mut x = UnsignedInteger::from(u64::MAX);
let y = UnsignedInteger::from_string_leaky("3".to_string(), 10, 2);

x += &y;

assert_eq!(
UnsignedInteger::from_string_leaky("18446744073709551618".to_string(), 10, 65),
x
);
assert_eq!(x.size_in_bits, 65);
}

#[test]
fn test_addition_different_sizes() {
let mut x = UnsignedInteger::from_string_leaky(
"5378239758327583290580573280735".to_string(),
10,
103,
);
let y = UnsignedInteger::from_string_leaky("12".to_string(), 10, 4);

x += &y;

assert_eq!(
UnsignedInteger::from_string_leaky(
"5378239758327583290580573280747".to_string(),
10,
103
),
x
);
assert_eq!(x.size_in_bits, 103);
}

#[test]
fn test_addition_u64() {
let mut x = UnsignedInteger::from_string_leaky(
3 changes: 3 additions & 0 deletions scicrypt-bigint/src/arithmetic/mul.rs
Original file line number Diff line number Diff line change
@@ -12,6 +12,9 @@ impl Mul for &UnsignedInteger {
return rhs * self;
}

debug_assert!(self.value.size != 0);
debug_assert!(rhs.value.size != 0);

debug_assert_eq!(
self.size_in_bits.div_ceil(GMP_NUMB_BITS) as i32,
self.value.size,
30 changes: 11 additions & 19 deletions scicrypt-bigint/src/arithmetic/sub.rs
Original file line number Diff line number Diff line change
@@ -6,25 +6,8 @@ use crate::{scratch::Scratch, UnsignedInteger, GMP_NUMB_BITS};

impl SubAssign<&UnsignedInteger> for UnsignedInteger {
fn sub_assign(&mut self, rhs: &UnsignedInteger) {
if self.size_in_bits <= rhs.size_in_bits {
// Switch the order and reverse the sign of the result
if self.value.size == 0 {
return;
}

unsafe {
gmp::mpn_sub_n(
self.value.d.as_mut(),
rhs.value.d.as_ptr(),
self.value.d.as_ptr(),
self.value.size as i64,
);

self.value.size = -rhs.value.size;
self.size_in_bits = rhs.size_in_bits;
}
return;
}
debug_assert!(self.size_in_bits >= rhs.size_in_bits);
debug_assert!(self.value.size >= rhs.value.size);

if rhs.value.size == 0 {
return;
@@ -50,6 +33,15 @@ impl Sub<&UnsignedInteger> for UnsignedInteger {
}
}

impl Sub<u64> for UnsignedInteger {
type Output = UnsignedInteger;

fn sub(mut self, rhs: u64) -> Self::Output {
self -= rhs;
self
}
}

impl SubAssign<u64> for UnsignedInteger {
fn sub_assign(&mut self, rhs: u64) {
debug_assert!(self.size_in_bits >= 64);
84 changes: 56 additions & 28 deletions scicrypt-he/src/cryptosystems/paillier.rs
Original file line number Diff line number Diff line change
@@ -23,20 +23,48 @@ use scicrypt_traits::randomness::SecureRng;
use scicrypt_traits::security::BitsOfSecurity;
use serde::{Deserialize, Serialize};

// FIXME: Consider adding a Paillier cryptosystem with CustomGen (custom generator)

/// The Paillier cryptosystem.
#[derive(Copy, Clone)]
pub struct Paillier {
modulus_size: u32,
}

/// A minimal version of the public key for Paillier, which can be expanded to be more computationally efficient.
pub struct MinimalPaillierPK {
/// Public modulus n for encryption
pub n: UnsignedInteger,
}

impl MinimalPaillierPK {
/// Expands this minimal key by precomputing some values. The resulting public key is faster to use but takes slightly more space.
pub fn expand(self) -> PaillierPK {
let n_squared = self.n.square();

PaillierPK {
n: self.n,
n_squared,
}
}
}

/// Public key for the Paillier cryptosystem.
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
pub struct PaillierPK {
/// Public modulus n for encryption
pub n: UnsignedInteger,
/// Public generator g for encryption
pub g: UnsignedInteger,
/// The modulus squared, i.e. n^2
pub n_squared: UnsignedInteger,
}

impl PaillierPK {
/// Minimizes the public key so that only the essential information is kept. This is useful if the public key must be transmitted or stored somewhere.
pub fn minimize(&self) -> MinimalPaillierPK {
MinimalPaillierPK { n: self.n.clone() }
}
}

/// Decryption key for the Paillier cryptosystem.
pub struct PaillierSK {
lambda: UnsignedInteger,
@@ -74,12 +102,14 @@ impl AsymmetricCryptosystem for Paillier {
/// let (public_key, secret_key) = paillier.generate_keys(&mut rng);
/// ```
fn generate_keys<R: SecureRng>(&self, rng: &mut GeneralRng<R>) -> (PaillierPK, PaillierSK) {
let (n, lambda) = gen_rsa_modulus(self.modulus_size, rng);
let (n, p, q) = gen_rsa_modulus(self.modulus_size, rng);

// The generator g is implicit: n + 1

let g = n.clone() + 1;
let mu = (lambda.clone() % &n).invert(&n).unwrap();
let lambda = &(p - 1) * &(q - 1);
let mu = lambda.clone().invert(&n).unwrap();

(PaillierPK { n, g }, PaillierSK { lambda, mu })
(MinimalPaillierPK { n }.expand(), PaillierSK { lambda, mu })
}
}

@@ -90,9 +120,9 @@ impl EncryptionKey for PaillierPK {
type Randomness = UnsignedInteger;

fn encrypt_without_randomness(&self, plaintext: &Self::Plaintext) -> Self::Ciphertext {
let n_squared = self.n.square();
PaillierCiphertext {
c: self.g.pow_mod(plaintext, &n_squared),
c: ((&self.n * &(self.n.clone() + plaintext)) + 1) % &self.n_squared,
//c: (self.n.clone() + 1).pow_mod(plaintext, &self.n_squared),
}
}

@@ -101,8 +131,9 @@ impl EncryptionKey for PaillierPK {
ciphertext: Self::Ciphertext,
rng: &mut GeneralRng<R>,
) -> Self::Ciphertext {
let n_squared = self.n.square();
let r = UnsignedInteger::random_below(&n_squared, rng);
// r must be coprime with n_squared but this only fails with probability 2^(1 - n_in_bits)
// 0 also only occurs with extremely low probability, so we can simply sample randomly s.t. 0 < r < n
let r = UnsignedInteger::random_below(&self.n, rng);

self.randomize_with(ciphertext, &r)
}
@@ -112,11 +143,10 @@ impl EncryptionKey for PaillierPK {
ciphertext: Self::Ciphertext,
randomness: &Self::Randomness,
) -> Self::Ciphertext {
let n_squared = self.n.square();
let randomizer = randomness.pow_mod(&self.n, &n_squared);
let randomizer = randomness.pow_mod(&self.n, &self.n_squared);

PaillierCiphertext {
c: (&ciphertext.c * &randomizer) % &n_squared,
c: (&ciphertext.c * &randomizer) % &self.n_squared,
}
}
}
@@ -142,9 +172,7 @@ impl DecryptionKey<PaillierPK> for PaillierSK {
public_key: &PaillierPK,
ciphertext: &PaillierCiphertext,
) -> UnsignedInteger {
let n_squared = public_key.n.square();

let mut inner = ciphertext.c.pow_mod(&self.lambda, &n_squared);
let mut inner = ciphertext.c.pow_mod(&self.lambda, &public_key.n_squared);
inner -= 1;
inner = inner / &public_key.n;
inner = &inner * &self.mu;
@@ -169,15 +197,13 @@ impl HomomorphicAddition for PaillierPK {
ciphertext_b: &Self::Ciphertext,
) -> Self::Ciphertext {
PaillierCiphertext {
c: (&ciphertext_a.c * &ciphertext_b.c) % &self.n.square(),
c: (&ciphertext_a.c * &ciphertext_b.c) % &self.n_squared,
}
}

fn mul_constant(&self, ciphertext: &Self::Ciphertext, input: &Self::Input) -> Self::Ciphertext {
let modulus = self.n.square();

PaillierCiphertext {
c: ciphertext.c.pow_mod(input, &modulus),
c: ciphertext.c.pow_mod(input, &self.n_squared),
}
}

@@ -186,9 +212,9 @@ impl HomomorphicAddition for PaillierPK {
ciphertext_a: &Self::Ciphertext,
ciphertext_b: &Self::Ciphertext,
) -> Self::Ciphertext {
let modulus = self.n.square();
PaillierCiphertext {
c: (&ciphertext_a.c * &ciphertext_b.c.clone().invert(&modulus).unwrap()) % &modulus,
c: (&ciphertext_a.c * &ciphertext_b.c.clone().invert(&self.n_squared).unwrap())
% &self.n_squared,
}
}

@@ -197,9 +223,8 @@ impl HomomorphicAddition for PaillierPK {
ciphertext: &Self::Ciphertext,
constant: &Self::Plaintext,
) -> Self::Ciphertext {
let modulus = self.n.square();
PaillierCiphertext {
c: (&ciphertext.c * &self.g.pow_mod(constant, &modulus)) % &modulus,
c: (&ciphertext.c * &((&self.n * constant + 1) % &self.n_squared)) % &self.n_squared,
}
}

@@ -208,10 +233,13 @@ impl HomomorphicAddition for PaillierPK {
ciphertext: &Self::Ciphertext,
constant: &Self::Plaintext,
) -> Self::Ciphertext {
let modulus = self.n.square();
// FIXME: We should not have to use `invert_leaky` here
PaillierCiphertext {
c: (&ciphertext.c * &self.g.pow_mod(constant, &modulus).invert(&modulus).unwrap())
% &modulus,
c: (&ciphertext.c
* &((&self.n * constant + 1) % &self.n_squared)
.invert_leaky(&self.n_squared)
.unwrap())
% &self.n_squared,
}
}
}
@@ -246,7 +274,7 @@ mod tests {
let paillier = Paillier::setup(&BitsOfSecurity::ToyParameters);
let (pk, sk) = paillier.generate_keys(&mut rng);

let ciphertext = pk.encrypt(&UnsignedInteger::from(0), &mut rng);
let ciphertext = pk.encrypt(&UnsignedInteger::zero(0), &mut rng);

assert!(sk.decrypt_identity(&ciphertext));
}
5 changes: 4 additions & 1 deletion scicrypt-he/src/cryptosystems/rsa.rs
Original file line number Diff line number Diff line change
@@ -49,7 +49,10 @@ impl AsymmetricCryptosystem for Rsa {
}

fn generate_keys<R: SecureRng>(&self, rng: &mut GeneralRng<R>) -> (RsaPK, RsaSK) {
let (n, lambda) = gen_rsa_modulus(self.modulus_size, rng);
let (n, p, q) = gen_rsa_modulus(self.modulus_size, rng);

// TODO: Is this the right choice?
let lambda = (p - 1).lcm_leaky(&(q - 1));

let e = UnsignedInteger::new(65537, 17);
let d = e
5 changes: 3 additions & 2 deletions scicrypt-he/src/threshold_cryptosystems/paillier.rs
Original file line number Diff line number Diff line change
@@ -130,8 +130,9 @@ impl EncryptionKey for ThresholdPaillierPK {
where
Self: Sized,
{
let n_squared = self.modulus.square();
let r = UnsignedInteger::random_below(&n_squared, rng);
// r must be coprime with n_squared but this only fails with probability 2^(1 - n_in_bits)
// 0 also only occurs with extremely low probability, so we can simply sample randomly s.t. 0 < r < n
let r = UnsignedInteger::random_below(&self.modulus, rng);

self.randomize_with(ciphertext, &r)
}
Loading