diff --git a/Cargo.toml b/Cargo.toml index 0018633..f6a478c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/decryptionkey.rs b/src/decryptionkey.rs index 8882a82..234271f 100644 --- a/src/decryptionkey.rs +++ b/src/decryptionkey.rs @@ -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, @@ -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")] @@ -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 { + 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 { @@ -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> { - 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 { 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() } @@ -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()), }) } @@ -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 @@ -150,4 +229,7 @@ struct DecryptionKeyBytes { lambda: Vec, totient: Vec, u: Vec, + n_inv: Vec, + p: Vec, + q: Vec, } diff --git a/src/encryptionkey.rs b/src/encryptionkey.rs index 3376035..ad79f05 100644 --- a/src/encryptionkey.rs +++ b/src/encryptionkey.rs @@ -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 @@ -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)] @@ -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 @@ -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 { // constant time check @@ -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 { self.n.to_bytes() diff --git a/src/lib.rs b/src/lib.rs index 6c651cb..aeddc18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ //! Paillier-rs contains Paillier's cryptosystem (1999) //! Public-Key Cryptosystems based on composite degree residuosity class. //! See +#![no_std] #![deny( warnings, missing_docs, @@ -15,6 +16,8 @@ )] #![cfg_attr(docsrs, feature(doc_cfg))] +extern crate alloc; + #[cfg(feature = "wasm")] #[macro_use] mod macros; diff --git a/src/macros.rs b/src/macros.rs index b036905..fd50cda 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -46,9 +46,7 @@ macro_rules! wasm_slice_impl { type Error = &'static str; fn try_from(value: wasm_bindgen::JsValue) -> Result { - value - .into_serde::<$name>() - .map_err(|_| "unable to deserialize value") + serde_wasm_bindgen::from_value(value).map_err(|_| "unable to deserialize value") } } }; diff --git a/src/proof_psf.rs b/src/proof_psf.rs index 6fc14f8..f1294a6 100644 --- a/src/proof_psf.rs +++ b/src/proof_psf.rs @@ -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, diff --git a/tests/paillier.rs b/tests/paillier.rs index 2ebd62b..2fd2ebe 100644 --- a/tests/paillier.rs +++ b/tests/paillier.rs @@ -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();