From e7adcfdab3e6872047bab71fc996ef688bace0b2 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 7 Nov 2022 21:42:45 -0800 Subject: [PATCH 1/6] Fix wasm feature compilation --- Cargo.toml | 3 ++- src/macros.rs | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0018633..84a23c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ default = ["openssl"] gmp = ["unknown_order/gmp"] openssl = ["unknown_order/openssl"] rust = ["unknown_order/rust"] -wasm = ["getrandom", "rand", "wasm-bindgen"] +wasm = ["getrandom", "rand", "wasm-bindgen", "serde-wasm-bindgen"] [dependencies] digest = "0.9" @@ -28,6 +28,7 @@ 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/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") } } }; From 3fbc4a387c9a291ba8deb57eb5c35cc150615992 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Fri, 4 Nov 2022 23:24:40 -0700 Subject: [PATCH 2/6] Derive PartialEq for EncryptionKey and DecryptionKey --- src/decryptionkey.rs | 2 +- src/encryptionkey.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/decryptionkey.rs b/src/decryptionkey.rs index 8882a82..3516acc 100644 --- a/src/decryptionkey.rs +++ b/src/decryptionkey.rs @@ -4,7 +4,7 @@ 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, diff --git a/src/encryptionkey.rs b/src/encryptionkey.rs index 3376035..25072c8 100644 --- a/src/encryptionkey.rs +++ b/src/encryptionkey.rs @@ -4,7 +4,7 @@ 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 From 424413782853f0b9b04f7e834a0f26bd2baa6fa5 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Fri, 4 Nov 2022 21:49:39 -0700 Subject: [PATCH 3/6] Store n, p, and q in DecryptionKey to assist with ZKP creation --- src/decryptionkey.rs | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/decryptionkey.rs b/src/decryptionkey.rs index 3516acc..0d1df31 100644 --- a/src/decryptionkey.rs +++ b/src/decryptionkey.rs @@ -14,6 +14,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")] @@ -66,6 +73,8 @@ impl DecryptionKey { let t: BigNumber = &n + 1; let tt = t.modpow(&lambda, &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 { @@ -75,6 +84,9 @@ impl DecryptionKey { lambda, totient, u, + n_inv, + p: p.clone(), + q: q.clone(), }), } } @@ -97,14 +109,17 @@ impl DecryptionKey { /// 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 +135,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 +151,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 +183,7 @@ struct DecryptionKeyBytes { lambda: Vec, totient: Vec, u: Vec, + n_inv: Vec, + p: Vec, + q: Vec, } From 07548416d9fe7d56fec4c188493b0069c77b0e2f Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Fri, 4 Nov 2022 22:57:07 -0700 Subject: [PATCH 4/6] Add DecryptionKey::from_rng_with_safe_primes() --- Cargo.toml | 4 ++-- src/decryptionkey.rs | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 84a23c4..f6a478c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ default = ["openssl"] gmp = ["unknown_order/gmp"] openssl = ["unknown_order/openssl"] rust = ["unknown_order/rust"] -wasm = ["getrandom", "rand", "wasm-bindgen", "serde-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 } diff --git a/src/decryptionkey.rs b/src/decryptionkey.rs index 0d1df31..9e3cc2b 100644 --- a/src/decryptionkey.rs +++ b/src/decryptionkey.rs @@ -1,4 +1,5 @@ use crate::{mod_in, Ciphertext, EncryptionKey}; +use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use unknown_order::BigNumber; use zeroize::Zeroize; @@ -38,6 +39,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 { From 951d579e423dccac6b5e060dc0c4b00645055794 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 7 Nov 2022 21:48:35 -0800 Subject: [PATCH 5/6] Add more low-level encryption and decryption methods --- src/decryptionkey.rs | 75 +++++++++++++++++++++++++++++++------------- src/encryptionkey.rs | 52 ++++++++++++++++++++++++------ tests/paillier.rs | 2 +- 3 files changed, 96 insertions(+), 33 deletions(-) diff --git a/src/decryptionkey.rs b/src/decryptionkey.rs index 9e3cc2b..230f17e 100644 --- a/src/decryptionkey.rs +++ b/src/decryptionkey.rs @@ -82,42 +82,73 @@ 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, - n_inv, - p: p.clone(), - q: q.clone(), - }), - } + 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. diff --git a/src/encryptionkey.rs b/src/encryptionkey.rs index 25072c8..c95a60e 100644 --- a/src/encryptionkey.rs +++ b/src/encryptionkey.rs @@ -61,6 +61,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 +78,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 +122,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 +140,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/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(); From 02d4d0d35518abca33d0fce7b5053bc94aebd009 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 7 Nov 2022 21:48:14 -0800 Subject: [PATCH 6/6] Make the crate no_std --- src/decryptionkey.rs | 2 ++ src/encryptionkey.rs | 2 ++ src/lib.rs | 3 +++ src/proof_psf.rs | 2 ++ 4 files changed, 9 insertions(+) diff --git a/src/decryptionkey.rs b/src/decryptionkey.rs index 230f17e..234271f 100644 --- a/src/decryptionkey.rs +++ b/src/decryptionkey.rs @@ -1,4 +1,6 @@ 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; diff --git a/src/encryptionkey.rs b/src/encryptionkey.rs index c95a60e..ad79f05 100644 --- a/src/encryptionkey.rs +++ b/src/encryptionkey.rs @@ -1,4 +1,6 @@ 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; 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/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,