Skip to content
Draft
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
43 changes: 35 additions & 8 deletions scicrypt-he/src/cryptosystems/paillier.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use rug::Integer;
use scicrypt_numbertheory::{gen_coprime, gen_rsa_modulus};
use scicrypt_numbertheory::gen_rsa_modulus;
use scicrypt_traits::cryptosystems::{
Associable, AsymmetricCryptosystem, DecryptionKey, EncryptionKey,
};
Expand All @@ -19,6 +19,7 @@ pub struct Paillier {
#[derive(PartialEq, Debug)]
pub struct PaillierPK {
n: Integer,
n_squared: Integer,
g: Integer,
}

Expand Down Expand Up @@ -57,12 +58,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, lambda, _, _) = gen_rsa_modulus(self.modulus_size, rng);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can actually use .. shorthand here: let (n, lambda, ..) =


let g = &n + Integer::from(1);
let mu = Integer::from(lambda.invert_ref(&n).unwrap());

(PaillierPK { n, g }, PaillierSK { lambda, mu })
let n_squared = Integer::from(n.square_ref());

(PaillierPK { n, g, n_squared }, PaillierSK { lambda, mu })
}
}

Expand All @@ -89,14 +92,15 @@ impl EncryptionKey for PaillierPK {
plaintext: &Integer,
rng: &mut GeneralRng<R>,
) -> PaillierCiphertext {
let n_squared = Integer::from(self.n.square_ref());
let r = gen_coprime(&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 = Integer::from(self.n.random_below_ref(&mut rng.rug_rng()));

let first = Integer::from(self.g.pow_mod_ref(&plaintext.into(), &n_squared).unwrap());
let second = r.secure_pow_mod(&self.n, &n_squared);
let first = Integer::from(self.g.pow_mod_ref(plaintext, &self.n_squared).unwrap());
let second = r.secure_pow_mod(&self.n, &self.n_squared);

PaillierCiphertext {
c: (first * second).rem(&n_squared),
c: (first * second).rem(&self.n_squared),
}
}
}
Expand Down Expand Up @@ -127,6 +131,29 @@ impl DecryptionKey<PaillierPK> for PaillierSK {

inner.rem(&public_key.n)
}

fn encrypt_fast_raw<R: SecureRng>(
&self,
public_key: &PaillierPK,
plaintext: &<PaillierPK as EncryptionKey>::Plaintext,
rng: &mut GeneralRng<R>,
) -> PaillierCiphertext {
// 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 = Integer::from(public_key.n.random_below_ref(&mut rng.rug_rng()));

let first = Integer::from(
public_key
.g
.pow_mod_ref(plaintext, &public_key.n_squared)
.unwrap(),
);
let second = r.secure_pow_mod(&public_key.n, &public_key.n_squared);

PaillierCiphertext {
c: (first * second).rem(&public_key.n_squared),
}
}
}

impl HomomorphicAddition for PaillierPK {
Expand Down
74 changes: 67 additions & 7 deletions scicrypt-he/src/cryptosystems/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,55 @@ pub struct RsaPK {
e: Integer,
}

/// Decryption key for RSA
pub struct RsaSK {
/// Precomputed decryption key for RSA
pub struct PrecomputedRsaSK {
p: Integer,
q: Integer,
d: Integer,
d_p: Integer,
d_q: Integer,
q_inv: Integer,
}

impl PrecomputedRsaSK {
/// Creates a new precomputed secret key using p, q, and d
pub fn new(p: Integer, q: Integer, d: Integer) -> Self {
let d_p = Integer::from(&d).rem(Integer::from(&p - 1));
let d_q = Integer::from(&d).rem(Integer::from(&q - 1));
let q_inv = Integer::from(q.invert_ref(&p).unwrap());

PrecomputedRsaSK {
p,
q,
d,
d_p,
d_q,
q_inv,
}
}

/// Compresses the precomputed secret key to a smaller format without precomputations
pub fn compress(self) -> CompressedRsaSK {
CompressedRsaSK {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool rust feature, you can use ...self shorthand in cases like this
CompressedRsaSK { ...self }
But this is a draft so i'm guessing this method isn't complete ofc

p: self.p,
q: self.q,
d: self.d,
}
}
}

/// Small secret key for RSA, which is less fast than a `PrecomputedRsaSK`.
pub struct CompressedRsaSK {
p: Integer,
q: Integer,
d: Integer,
}

impl CompressedRsaSK {
/// Creates a `PrecomputedRsaSK` from this key.
pub fn precompute(self) -> PrecomputedRsaSK {
PrecomputedRsaSK::new(self.p, self.q, self.d)
}
}

/// Ciphertext of the RSA cryptosystem, which is multiplicatively homomorphic.
Expand All @@ -36,21 +82,21 @@ impl Associable<RsaPK> for RsaCiphertext {}

impl AsymmetricCryptosystem for Rsa {
type PublicKey = RsaPK;
type SecretKey = RsaSK;
type SecretKey = PrecomputedRsaSK;

fn setup(security_param: &BitsOfSecurity) -> Self {
Rsa {
modulus_size: security_param.to_public_key_bit_length(),
}
}

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

let e = Integer::from(65537);
let d = Integer::from(e.invert_ref(&lambda).unwrap());

(RsaPK { n, e }, RsaSK { d })
(RsaPK { n, e }, PrecomputedRsaSK::new(p, q, d))
}
}

Expand All @@ -70,12 +116,26 @@ impl EncryptionKey for RsaPK {
}
}

impl DecryptionKey<RsaPK> for RsaSK {
impl DecryptionKey<RsaPK> for CompressedRsaSK {
fn decrypt_raw(&self, public_key: &RsaPK, ciphertext: &RsaCiphertext) -> Integer {
Integer::from(ciphertext.c.secure_pow_mod_ref(&self.d, &public_key.n))
}
}

impl DecryptionKey<RsaPK> for PrecomputedRsaSK {
fn decrypt_raw(
&self,
_public_key: &RsaPK,
ciphertext: &<RsaPK as EncryptionKey>::Ciphertext,
) -> <RsaPK as EncryptionKey>::Plaintext {
let m_p = Integer::from(ciphertext.c.secure_pow_mod_ref(&self.d_p, &self.p));
let m_q = Integer::from(ciphertext.c.secure_pow_mod_ref(&self.d_q, &self.q));
let h = (&self.q_inv * (m_p - &m_q)).rem(&self.p);

m_q + h * &self.q
}
}

impl HomomorphicMultiplication for RsaPK {
fn mul(
&self,
Expand Down
8 changes: 4 additions & 4 deletions scicrypt-numbertheory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,20 +107,20 @@ pub fn gen_safe_prime<R: SecureRng>(bit_length: u32, rng: &mut GeneralRng<R>) ->
}

/// Generates a uniformly random RSA modulus, which is the product of two safe primes $p$ and $q$.
/// This method returns both the modulus and $\lambda$, which is the least common multiple of
/// This method returns the modulus, $\lambda$, $p$, and $q$. $\lambda$ is the least common multiple of
/// $p - 1$ and $q - 1$.
pub fn gen_rsa_modulus<R: SecureRng>(
bit_length: u32,
rng: &mut GeneralRng<R>,
) -> (Integer, Integer) {
) -> (Integer, Integer, Integer, Integer) {
let p = gen_safe_prime(bit_length / 2, rng);
let q = gen_safe_prime(bit_length / 2, rng);

let n = Integer::from(&p * &q);

let lambda: Integer = (p - Integer::from(1)).lcm(&(q - Integer::from(1)));
let lambda: Integer = (&p - Integer::from(1)).lcm(&(&q - Integer::from(1)));

(n, lambda)
(n, lambda, p, q)
}

/// Generates a uniformly random coprime $x$ to the `other` integer $y$. This means that
Expand Down
23 changes: 23 additions & 0 deletions scicrypt-traits/src/cryptosystems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,29 @@ pub trait DecryptionKey<PK: EncryptionKey> {

/// Decrypt the ciphertext using the secret key and its related public key.
fn decrypt_raw(&self, public_key: &PK, ciphertext: &PK::Ciphertext) -> PK::Plaintext;

/// Uses both the secret material from the decryption key and the encryption key to encrypt faster than with only the encryption key.
/// This method is always implemented, but it defaults to simply encrypting without the secret key.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, maybe I misunderstand, but if this is a trait, is there a need to specify that this method is always implemented?

fn encrypt_fast<'pk, R: SecureRng>(
&'pk self,
public_key: &'pk PK,
plaintext: &PK::Plaintext,
rng: &mut GeneralRng<R>,
) -> AssociatedCiphertext<'pk, PK::Ciphertext, PK> {
self.encrypt_fast_raw(public_key, plaintext, rng)
.associate(public_key)
}

/// Uses both the secret material from the decryption key and the encryption key to encrypt faster than with only the encryption key.
/// This method is always implemented, but it defaults to simply encrypting without the secret key.
fn encrypt_fast_raw<R: SecureRng>(
&self,
public_key: &PK,
plaintext: &PK::Plaintext,
rng: &mut GeneralRng<R>,
) -> PK::Ciphertext {
public_key.encrypt_raw(plaintext, rng)
}
}

#[derive(PartialEq, Debug)]
Expand Down