Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tofn compatibility fixes #4

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ default = ["openssl"]
gmp = ["unknown_order/gmp"]
openssl = ["unknown_order/openssl"]
rust = ["unknown_order/rust"]
wasm = ["getrandom", "rand", "wasm-bindgen"]
wasm = ["getrandom", "wasm-bindgen", "serde-wasm-bindgen"]

[dependencies]
digest = "0.9"
getrandom = { version = "0.2", features = ["js"], optional = true }
rand = { version = "0.8", optional = true }
rand_core = { version = "0.6", default_features = false }
serde = { version = "1.0", features = ["serde_derive"] }
serde_bare = "0.5"
unknown_order = { version = "0.5.0", default-features = false }
wasm-bindgen = { version = "0.2", default-features = false, features = ["serde-serialize"], optional = true }
zeroize = { version = "1.4", features = ["zeroize_derive"] }
serde-wasm-bindgen = { version = "0.4", optional = true }

[dev-dependencies]
elliptic-curve = "0.9"
Expand Down
126 changes: 104 additions & 22 deletions src/decryptionkey.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use crate::{mod_in, Ciphertext, EncryptionKey};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use rand_core::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use unknown_order::BigNumber;
use zeroize::Zeroize;

/// A Paillier decryption key
#[derive(Clone, Debug, Deserialize, Serialize, Zeroize)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Zeroize)]
#[zeroize(drop)]
pub struct DecryptionKey {
pub(crate) pk: EncryptionKey,
Expand All @@ -14,6 +17,13 @@ pub struct DecryptionKey {
pub(crate) totient: BigNumber,
/// L((N + 1)^lambda mod N^2)-1 mod N
pub(crate) u: BigNumber,
// N^-1 mod phi(N)
// Useful for decrypting and retrieving the randomness
pub(crate) n_inv: BigNumber,
// p
pub(crate) p: BigNumber,
// q
pub(crate) q: BigNumber,
}

#[cfg(feature = "wasm")]
Expand All @@ -31,6 +41,18 @@ impl DecryptionKey {
res
}

#[cfg(any(feature = "gmp", feature = "rust"))]
/// Create a new decryption key using the provided `rng`
pub fn from_rng_with_safe_primes(rng: &mut (impl CryptoRng + RngCore)) -> Option<Self> {
let mut p = BigNumber::safe_prime_from_rng(1024, rng);
let mut q = BigNumber::safe_prime_from_rng(1024, rng);
let res = Self::with_primes_unchecked(&p, &q);
// Make sure the primes are zero'd
p.zeroize();
q.zeroize();
res
}

/// Create a new key from two primes.
/// `p` and `q` are checked if prime
pub fn with_primes(p: &BigNumber, q: &BigNumber) -> Option<Self> {
Expand Down Expand Up @@ -62,49 +84,88 @@ impl DecryptionKey {
}
let totient = &pm1 * &qm1;

// (N+1)^lambda mod N^2
let t: BigNumber = &n + 1;
let tt = t.modpow(&lambda, &nn);
// (N+1)^lambda mod N^2 = lambda N + 1 mod N^2
let tt = lambda.modmul(&n, &nn).modadd(&BigNumber::one(), &nn);

let n_inv = n.invert(&totient)?;

// L((N+1)^lambda mod N^2)^-1 mod N
let uu = pk.l(&tt).map(|uu| uu.invert(&n));
match uu {
None => None,
Some(u_inv) => u_inv.map(|u| DecryptionKey {
pk,
lambda,
totient,
u,
}),
}
let u = pk.l(&tt)?.invert(&n)?;

Some(DecryptionKey {
pk,
lambda,
totient,
u,
n_inv,
p: p.clone(),
q: q.clone(),
})
}

/// Reverse ciphertext to plaintext
pub fn decrypt(&self, c: &Ciphertext) -> Option<Vec<u8>> {
if !mod_in(&c, &self.pk.nn) {
if !mod_in(c, &self.pk.nn) {
return None;
}

// a = c^\lambda mod n^2
let a = c.modpow(&self.lambda, &self.pk.nn);

// ell = L(a, N)
self.pk.l(&a).map(|l| {
// m = lu = L(a)*u = L(c^\lamba*)u mod n
let m = l.modmul(&self.u, &self.pk.n);
m.to_bytes()
})
let ell = self.pk.l(&a)?;

// m = lu = L(a)*u = L(c^\lamba*)u mod n
let m = ell.modmul(&self.u, &self.pk.n);

Some(m.to_bytes())
}

/// Reverse ciphertext to plaintext
pub fn decrypt_unchecked(&self, c: &Ciphertext) -> BigNumber {
debug_assert!(mod_in(c, &self.pk.nn));

// a = c^\lambda mod n^2
let a = c.modpow(&self.lambda, &self.pk.nn);

// ell = L(a, N)
let ell = self.pk.l_unchecked(&a);

// m = lu = L(a)*u = L(c^\lamba*)u mod n
ell.modmul(&self.u, &self.pk.n)
}

/// Reverse ciphertext to plaintext and also retrieve the randomness
pub fn decrypt_with_randomness(&self, c: &Ciphertext) -> (BigNumber, BigNumber) {
let n = &self.pk.n;
let nn = &self.pk.nn;

let m = self.decrypt_unchecked(c);

// g^-m = (N + 1)^-m = 1 - m N (mod N^2)
let g_m_inv = BigNumber::one().modsub(&m.modmul(n, nn), nn);

// r^N = c . g^-m (mod N^2)
let r_n = c.modmul(&g_m_inv, nn);

let r = r_n.modpow(&self.n_inv, n);

(m, r)
}

/// Get this key's byte representation.
///
/// This measures about (n * 4) + 4 bytes or i.e.
/// for a 2048 bit modulus == 1032 bytes.
/// This measures about (n * 6) + 7 * 2 bytes or i.e.
/// for a 2048 bit modulus == 1550 bytes.
pub fn to_bytes(&self) -> Vec<u8> {
let bytes = DecryptionKeyBytes {
n: self.pk.n.to_bytes(),
lambda: self.lambda.to_bytes(),
totient: self.totient.to_bytes(),
u: self.u.to_bytes(),
n_inv: self.n_inv.to_bytes(),
p: self.p.to_bytes(),
q: self.q.to_bytes(),
};
serde_bare::to_vec(&bytes).unwrap()
}
Expand All @@ -120,6 +181,9 @@ impl DecryptionKey {
lambda: BigNumber::from_slice(bytes.lambda.as_slice()),
totient: BigNumber::from_slice(bytes.totient.as_slice()),
u: BigNumber::from_slice(bytes.u.as_slice()),
n_inv: BigNumber::from_slice(bytes.n_inv.as_slice()),
p: BigNumber::from_slice(bytes.p.as_slice()),
q: BigNumber::from_slice(bytes.q.as_slice()),
})
}

Expand All @@ -133,6 +197,21 @@ impl DecryptionKey {
&self.lambda
}

/// `N^(-1) mod phi(N)`
pub fn n_inv(&self) -> &BigNumber {
&self.n_inv
}

/// Prime factor `p` of the Paillier modulus
pub fn p(&self) -> &BigNumber {
&self.p
}

/// Prime factor `q` of the Paillier modulus
pub fn q(&self) -> &BigNumber {
&self.q
}

/// The Paillier `totient`
pub fn totient(&self) -> &BigNumber {
&self.totient
Expand All @@ -150,4 +229,7 @@ struct DecryptionKeyBytes {
lambda: Vec<u8>,
totient: Vec<u8>,
u: Vec<u8>,
n_inv: Vec<u8>,
p: Vec<u8>,
q: Vec<u8>,
}
56 changes: 45 additions & 11 deletions src/encryptionkey.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::{mod_in, Ciphertext, DecryptionKey, Nonce};
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use unknown_order::BigNumber;
use zeroize::Zeroize;

/// A Paillier encryption key
#[derive(Clone, Debug, Zeroize)]
#[derive(Clone, Debug, PartialEq, Zeroize)]
pub struct EncryptionKey {
pub(crate) n: BigNumber, // N = p * q, where p,q are primes
pub(crate) nn: BigNumber, // N^2
Expand Down Expand Up @@ -61,6 +63,11 @@ impl EncryptionKey {
Some((x - &one) / &self.n)
}

pub(crate) fn l_unchecked(&self, x: &BigNumber) -> BigNumber {
//(x - 1) / N
(x - &BigNumber::one()) / &self.n
}

/// Encrypt a given message with the encryption key and optionally use a random value
/// x must be less than N
#[allow(clippy::many_single_char_names)]
Expand All @@ -73,19 +80,35 @@ impl EncryptionKey {
return None;
}

let r = r.unwrap_or_else(|| Nonce::random(&self.n));
let r = match r {
Some(r) => {
if !mod_in(&r, &self.n) {
return None;
}
r
}
None => Nonce::random(&self.n),
};

Some((self.encrypt_unchecked(&xx, &r), r))
}

if !mod_in(&r, &self.n) {
return None;
}
/// Encrypt a given message with the encryption key and a provided random value
/// m can be greater than N
pub fn encrypt_unchecked(&self, m: &BigNumber, r: &Nonce) -> Ciphertext {
debug_assert!(mod_in(r, &self.n));

// a = (N+1)^m mod N^2
let a = (&self.n + BigNumber::one()).modpow(&xx, &self.nn);
// b = r^N mod N^2
let b = &r.modpow(&self.n, &self.nn);
// g^m mod N^2 = (N + 1)^m mod N^2 = m N + 1 mod N^2
// See Prop 11.26, Pg. 385 of Intro to Modern Cryptography
let g_m = m
.modmul(&self.n, &self.nn)
.modadd(&BigNumber::one(), &self.nn);

let c = a.modmul(&b, &self.nn);
Some((c, r))
// r^N mod N^2
let r_n = &r.modpow(&self.n, &self.nn);

// c = g^m r^n mod N^2
g_m.modmul(r_n, &self.nn)
}

/// Combines two Paillier ciphertexts
Expand All @@ -101,6 +124,12 @@ impl EncryptionKey {
Some(c1.modmul(c2, &self.nn))
}

/// Combines two Paillier ciphertexts (without checks)
/// commonly denoted in text as c1 \bigoplus c2
pub fn add_unchecked(&self, c1: &Ciphertext, c2: &Ciphertext) -> Ciphertext {
c1.modmul(c2, &self.nn)
}

/// Equivalent to adding two Paillier exponents
pub fn mul(&self, c: &Ciphertext, a: &BigNumber) -> Option<Ciphertext> {
// constant time check
Expand All @@ -113,6 +142,11 @@ impl EncryptionKey {
Some(c.modpow(a, &self.nn))
}

/// Equivalent to adding two Paillier exponents without checks
pub fn mul_unchecked(&self, c: &Ciphertext, a: &BigNumber) -> Ciphertext {
c.modpow(a, &self.nn)
}

/// Get this key's byte representation
pub fn to_bytes(&self) -> Vec<u8> {
self.n.to_bytes()
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! Paillier-rs contains Paillier's cryptosystem (1999)
//! Public-Key Cryptosystems based on composite degree residuosity class.
//! See <http://citeseerx.ist.psu.edu/download?doi=10.1.1.4035&rep=rep1&type=pdf>
#![no_std]
#![deny(
warnings,
missing_docs,
Expand All @@ -15,6 +16,8 @@
)]
#![cfg_attr(docsrs, feature(doc_cfg))]

extern crate alloc;

#[cfg(feature = "wasm")]
#[macro_use]
mod macros;
Expand Down
4 changes: 1 addition & 3 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ macro_rules! wasm_slice_impl {
type Error = &'static str;

fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
value
.into_serde::<$name>()
.map_err(|_| "unable to deserialize value")
serde_wasm_bindgen::from_value(value).map_err(|_| "unable to deserialize value")
}
}
};
Expand Down
2 changes: 2 additions & 0 deletions src/proof_psf.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::{mod_in, DecryptionKey, EncryptionKey};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use digest::{
generic_array::{typenum::Unsigned, GenericArray},
Digest,
Expand Down
2 changes: 1 addition & 1 deletion tests/paillier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ fn bytes() {
assert_eq!(pk1.n(), pk.n());

let bytes = sk.to_bytes();
assert_eq!(bytes.len(), 1032);
assert_eq!(bytes.len(), 1550);
let res = DecryptionKey::from_bytes(bytes.as_slice());
assert!(res.is_ok());
let sk1 = res.unwrap();
Expand Down