diff --git a/Cargo.lock b/Cargo.lock index 776b079..e736411 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,12 +26,6 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "block-buffer" version = "0.10.3" @@ -41,12 +35,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "cc" -version = "1.0.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" - [[package]] name = "cfg-if" version = "1.0.0" @@ -238,21 +226,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "generic-array" version = "0.14.6" @@ -434,51 +407,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "once_cell" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" - -[[package]] -name = "openssl" -version = "0.10.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d971fd5722fec23977260f6e81aa67d2f22cadbdc2aa049f1022d9a3be1566" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-sys" -version = "0.9.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "p256" version = "0.11.1" @@ -525,12 +453,6 @@ dependencies = [ "spki", ] -[[package]] -name = "pkg-config" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" - [[package]] name = "ppv-lite86" version = "0.2.17" @@ -785,7 +707,6 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", - "openssl", "rand", "rug", "serde", @@ -793,12 +714,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 4e7d1fb..6584f37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,13 @@ generic-ec-curves = { git = "https://github.com/dfns-labs/generic-ec", branch = [features] default = ["rust"] gmp = ["libpaillier/gmp"] -openssl = ["libpaillier/openssl"] +# openssl random generation doesn't respect the rng supplied from rust, and so shouldn't be used +#openssl = ["libpaillier/openssl"] rust = ["libpaillier/rust"] serde = ["dep:serde", "generic-ec/serde"] + +[profile.dev.package.num-bigint] +opt-level = 3 +[profile.dev.package.glass_pumpkin] +opt-level = 3 diff --git a/src/common.rs b/src/common.rs index 4ff6625..d1bf7fa 100644 --- a/src/common.rs +++ b/src/common.rs @@ -38,7 +38,8 @@ pub fn convert_scalar(x: &BigNumber) -> generic_ec::Scalar generic_ec::Scalar::::from_be_bytes_mod_order(x.to_bytes()) } -/// Reason for failure. Mainly interesting for debugging purposes +/// Reason for failure. If the proof failes, you should only be interested in a +/// reason for debugging purposes #[derive(Debug, PartialEq, Eq)] pub enum InvalidProof { /// One equality doesn't hold. Parameterized by equality index @@ -49,6 +50,8 @@ pub enum InvalidProof { EncryptionFailed, } +/// Unexpeted error that can happen in a protocol. You should probably panic if +/// you see this. #[derive(Debug, PartialEq, Eq)] pub enum ProtocolError { /// Encryption of supplied data failed when computing proof @@ -58,7 +61,7 @@ pub enum ProtocolError { } #[cfg(test)] -mod test { +pub mod test { use libpaillier::unknown_order::BigNumber; #[test] @@ -72,4 +75,14 @@ mod test { let scalar2 = super::convert_scalar(&bignumber); assert_eq!(scalar1, scalar2); } + + pub fn random_key(rng: &mut R) -> Option { + let p = BigNumber::prime_from_rng(1024, rng); + let q = BigNumber::prime_from_rng(1024, rng); + libpaillier::DecryptionKey::with_primes_unchecked(&p, &q) + } + + pub fn nonce(rng: &mut R, n: &BigNumber) -> Option { + Some(BigNumber::from_rng(n, rng)) + } } diff --git a/src/curve.rs b/src/curve.rs index 513927c..d9bfa33 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -189,7 +189,7 @@ impl generic_ec_core::One for Scalar { impl generic_ec_core::Samplable for Scalar { fn random(rng: &mut R) -> Self { - Scalar(rng.next_u64()) + rng.next_u64().into() } } diff --git a/src/group_element_vs_paillier_encryption_in_range.rs b/src/group_element_vs_paillier_encryption_in_range.rs index a308b1a..4f0f9a7 100644 --- a/src/group_element_vs_paillier_encryption_in_range.rs +++ b/src/group_element_vs_paillier_encryption_in_range.rs @@ -3,8 +3,9 @@ //! ## Description //! //! A party P has a number `X = g ^ x`, with g being a generator of -//! multiplicative group G. P wants to prove to party V that the logarithm of X, -//! i.e. x, is at most L bits. +//! multiplicative group G. P has encrypted x as C. P shares X and C with V and +//! wants to prove that the logarithm of X is the plaintext of C, and that the +//! plaintext (i.e. x) is at most L+1 bits. //! //! Given: //! - `key0`, `pkey0` - pair of public and private keys in paillier cryptosystem @@ -56,12 +57,14 @@ //! //! // 3. Prover computes a non-interactive proof that plaintext is at most 1024 bits: //! +//! let mut rng = rand_core::OsRng::default(); +//! //! let security = p::SecurityParams { //! l: 1024, //! epsilon: 128, +//! q: BigNumber::prime_from_rng(128, &mut rng), //! }; //! -//! let rng = rand_core::OsRng::default(); //! let data = p::Data { key0, c: ciphertext, x: power, g }; //! let pdata = p::PrivateData { x: plaintext, nonce }; //! let (commitment, proof) = @@ -90,31 +93,43 @@ use serde::{Deserialize, Serialize}; use crate::unknown_order::BigNumber; +/// Security parameters for proof. Choosing the values is a tradeoff between +/// speed and chance of rejecting a valid proof or accepting an invalid proof #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct SecurityParams { - /// l in paper, bit size of plaintext + /// l in paper, bit size of +-plaintext pub l: usize, - /// Epsilon in paper, range extension and security parameter for x + /// Epsilon in paper, slackness parameter pub epsilon: usize, + /// q in paper. Security parameter for challenge + pub q: BigNumber, } +/// Public data that both parties know #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))] pub struct Data { + /// N0 in paper, public key that C was encrypted on pub key0: EncryptionKey, + /// C in paper, logarithm of X encrypted on N0 pub c: Ciphertext, + /// X in paper, exponent of plaintext of C pub x: Point, /// A generator in group pub g: Point, } +/// Private data of prover #[derive(Clone)] pub struct PrivateData { + /// x in paper, logarithm of X and plaintext of C pub x: BigNumber, + /// rho in paper, nonce in encryption x -> C pub nonce: Nonce, } +/// Prover's first message, obtained by [`interactive::commit`] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))] pub struct Commitment { @@ -124,6 +139,8 @@ pub struct Commitment { pub d: BigNumber, } +/// Prover's data accompanying the commitment. Kept as state between rounds in +/// the interactive protocol. #[derive(Clone)] pub struct PrivateCommitment { pub alpha: BigNumber, @@ -132,8 +149,12 @@ pub struct PrivateCommitment { pub gamma: BigNumber, } +/// Verifier's challenge to prover. Can be obtained deterministically by +/// [`non_interactive::challenge`] or randomly by [`interactive::challenge`] pub type Challenge = BigNumber; +/// The ZK proof. Computed by [`interactive::prove`] or +/// [`non_interactive::prove`] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Proof { @@ -144,8 +165,11 @@ pub struct Proof { pub use crate::common::Aux; +/// The interactive version of the ZK proof. Should be completed in 3 rounds: +/// prover commits to data, verifier responds with a random challenge, and +/// prover gives proof with commitment and challenge. pub mod interactive { - use generic_ec::{Curve, Scalar}; + use generic_ec::Curve; use libpaillier::unknown_order::BigNumber; use rand_core::RngCore; @@ -163,8 +187,8 @@ pub mod interactive { security: &SecurityParams, mut rng: R, ) -> Result<(Commitment, PrivateCommitment), ProtocolError> { - let two_to_l = BigNumber::one() << security.l; - let two_to_l_e = BigNumber::one() << (security.l + security.epsilon); + let two_to_l = BigNumber::one() << (security.l + 1); + let two_to_l_e = BigNumber::one() << (security.l + security.epsilon + 1); let modulo_l = two_to_l * &aux.rsa_modulo; let modulo_l_e = &two_to_l_e * &aux.rsa_modulo; @@ -256,7 +280,7 @@ pub mod interactive { fail_if(lhs == rhs, InvalidProof::EqualityCheckFailed(3))?; } fail_if( - proof.z1 <= one << (security.l + security.epsilon), + proof.z1 <= one << (security.l + security.epsilon + 1), InvalidProof::RangeCheckFailed(4), )?; @@ -264,16 +288,20 @@ pub mod interactive { } /// Generate random challenge - pub fn challenge(rng: &mut R) -> BigNumber + /// + /// `data` parameter is used to generate challenge in correct range + pub fn challenge(rng: &mut R, security: &SecurityParams) -> BigNumber where - C: Curve, R: RngCore, { - let x = Scalar::::random(rng); - BigNumber::from_slice(x.to_be_bytes().as_bytes()) + // double the range to account for +- + let m = BigNumber::from(2) * &security.q; + BigNumber::from_rng(&m, rng) } } +/// The non-interactive version of proof. Completed in one round, for example +/// see the documentation of parent module. pub mod non_interactive { use generic_ec::{hash_to_curve::FromHash, Curve, Scalar}; use libpaillier::unknown_order::BigNumber; @@ -353,8 +381,8 @@ pub mod non_interactive { .finalize(); let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed.into()); - let scalar = Scalar::::random(&mut rng); - BigNumber::from_slice(scalar.to_be_bytes().as_bytes()) + let m = BigNumber::from(2) * &security.q; + BigNumber::from_rng(&m, &mut rng) } } @@ -363,21 +391,22 @@ mod test { use generic_ec::{hash_to_curve::FromHash, Curve, Scalar}; use libpaillier::unknown_order::BigNumber; + use crate::common::test::{nonce, random_key}; use crate::common::{convert_scalar, InvalidProof}; - fn passing_test() + fn run( + mut rng: R, + security: super::SecurityParams, + plaintext: BigNumber, + ) -> Result<(), crate::common::InvalidProof> where Scalar: FromHash, { - let security = super::SecurityParams { - l: 1024, - epsilon: 128, - }; - let private_key0 = libpaillier::DecryptionKey::random().unwrap(); + let private_key0 = random_key(&mut rng).unwrap(); let key0 = libpaillier::EncryptionKey::from(&private_key0); - let plaintext = BigNumber::from(228); - let (ciphertext, nonce) = key0.encrypt(plaintext.to_bytes(), None).unwrap(); + let nonce = nonce(&mut rng, key0.n()); + let (ciphertext, nonce) = key0.encrypt(plaintext.to_bytes(), nonce).unwrap(); let g = generic_ec::Point::::generator() * generic_ec::Scalar::::from(1337); let x = g * convert_scalar(&plaintext); @@ -392,8 +421,8 @@ mod test { nonce, }; - let p = BigNumber::prime(1024); - let q = BigNumber::prime(1024); + let p = BigNumber::prime_from_rng(1024, &mut rng); + let q = BigNumber::prime_from_rng(1024, &mut rng); let rsa_modulo = p * q; let s: BigNumber = 123.into(); let t: BigNumber = 321.into(); @@ -409,18 +438,25 @@ mod test { &data, &pdata, &security, - rand_core::OsRng::default(), + rng, ) .unwrap(); - let r = super::non_interactive::verify( - shared_state, - &aux, - &data, - &commitment, - &security, - &proof, - ); + super::non_interactive::verify(shared_state, &aux, &data, &commitment, &security, &proof) + } + + fn passing_test() + where + Scalar: FromHash, + { + let mut rng = rand_core::OsRng::default(); + let security = super::SecurityParams { + l: 1024, + epsilon: 256, + q: BigNumber::prime_from_rng(128, &mut rng), + }; + let plaintext = BigNumber::from(228); + let r = run(rng, security, plaintext); match r { Ok(()) => (), Err(e) => panic!("{:?}", e), @@ -431,57 +467,14 @@ mod test { where Scalar: FromHash, { + let mut rng = rand_core::OsRng::default(); let security = super::SecurityParams { l: 1024, epsilon: 128, + q: BigNumber::prime_from_rng(128, &mut rng), }; - let private_key0 = libpaillier::DecryptionKey::random().unwrap(); - let key0 = libpaillier::EncryptionKey::from(&private_key0); - let plaintext = BigNumber::from(1) << (security.l + security.epsilon + 1); - let (ciphertext, nonce) = key0.encrypt(plaintext.to_bytes(), None).unwrap(); - let g = generic_ec::Point::::generator() * generic_ec::Scalar::::from(1337); - let x = g * convert_scalar(&plaintext); - - let data = super::Data { - key0, - c: ciphertext, - x, - g, - }; - let pdata = super::PrivateData { - x: plaintext, - nonce, - }; - - let p = BigNumber::prime(1024); - let q = BigNumber::prime(1024); - let rsa_modulo = p * q; - let s: BigNumber = 123.into(); - let t: BigNumber = 321.into(); - assert_eq!(s.gcd(&rsa_modulo), 1.into()); - assert_eq!(t.gcd(&rsa_modulo), 1.into()); - let aux = super::Aux { s, t, rsa_modulo }; - - let shared_state = sha2::Sha256::default(); - - let (commitment, proof) = super::non_interactive::prove( - shared_state.clone(), - &aux, - &data, - &pdata, - &security, - rand_core::OsRng::default(), - ) - .unwrap(); - let r = super::non_interactive::verify( - shared_state, - &aux, - &data, - &commitment, - &security, - &proof, - ); + let r = run(rng, security, plaintext); match r { Ok(()) => panic!("proof should not pass"), Err(InvalidProof::RangeCheckFailed(_)) => (), @@ -506,4 +499,35 @@ mod test { fn failing_million() { failing_test::() } + + // see notes in + // [crate::paillier_encryption_in_range::test::rejected_with_probability_1_over_2] + // for motivation and design of the following test. + // Altough no security estimate was given in the paper, my own calculations + // show that the parameters here achieve the probability about as good as in + // other tests + + #[test] + fn mul_rejected_with_probability_1_over_2() { + use rand_core::SeedableRng; + fn maybe_rejected(mut rng: rand_chacha::ChaCha20Rng) -> bool { + let security = super::SecurityParams { + l: 1024, + epsilon: 130, + q: BigNumber::prime_from_rng(128, &mut rng), + }; + let plaintext = (BigNumber::from(1) << (security.l + 1)) - 1; + let r = run::<_, generic_ec_curves::rust_crypto::Secp256r1>(rng, security, plaintext); + match r { + Ok(()) => true, + Err(crate::common::InvalidProof::RangeCheckFailed(4)) => false, + Err(e) => panic!("proof should not fail with: {:?}", e), + } + } + + let rng = rand_chacha::ChaCha20Rng::seed_from_u64(2); + assert!(!maybe_rejected(rng), "should fail"); + let rng = rand_chacha::ChaCha20Rng::seed_from_u64(3); + assert!(maybe_rejected(rng), "should pass"); + } } diff --git a/src/paillier_affine_operation_in_range.rs b/src/paillier_affine_operation_in_range.rs index b142dc5..fc29f79 100644 --- a/src/paillier_affine_operation_in_range.rs +++ b/src/paillier_affine_operation_in_range.rs @@ -5,7 +5,7 @@ //! //! A party P performs a paillier affine operation with C, Y, and X //! obtaining `D = C*X + Y`. `X` and `Y` are encrypted values of `x` and `y`. P -//! then wants to prove that `y` and `x` are at most `L` and `L'` bits, +//! then wants to prove that `y` and `x` are at most `L+1` and `L'+1` bits, //! correspondingly, and P doesn't want to disclose none of the plaintexts //! //! Given: @@ -94,13 +94,15 @@ //! // 5. Prover computes a non-interactive proof that plaintext_add and //! // plaintext_mult are at most L and L' bits //! +//! let mut rng = rand_core::OsRng::default(); +//! //! let security = p::SecurityParams { //! l_x: 1024, //! l_y: 1024, //! epsilon: 128, +//! q: BigNumber::prime_from_rng(128, &mut rng), //! }; //! -//! let rng = rand_core::OsRng::default(); //! let data = p::Data { //! key0, //! key1, @@ -142,15 +144,19 @@ use serde::{Deserialize, Serialize}; pub use crate::common::InvalidProof; use crate::unknown_order::BigNumber; +/// Security parameters for proof. Choosing the values is a tradeoff between +/// speed and chance of rejecting a valid proof or accepting an invalid proof #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct SecurityParams { - /// l in paper, bit size of x + /// l in paper, bit size of +-x pub l_x: usize, - /// l' in paper, bit size of y + /// l' in paper, bit size of +-y pub l_y: usize, - /// Epsilon in paper, range extension and security parameter for x and y + /// Epsilon in paper, slackness parameter pub epsilon: usize, + /// q in paper. Security parameter for challenge + pub q: BigNumber, } /// Public data that both parties know @@ -185,7 +191,7 @@ pub struct PrivateData { } // As described in cggmp21 at page 35 -/// Prover's first message, obtained by `commit` +/// Prover's first message, obtained by [`interactive::commit`] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))] pub struct Commitment { @@ -213,10 +219,11 @@ pub struct PrivateCommitment { } /// Verifier's challenge to prover. Can be obtained deterministically by -/// `challenge` +/// [`non_interactive::challenge`] or randomly by [`interactive::challenge`] pub type Challenge = BigNumber; -/// The ZK proof. Computed by `prove` +/// The ZK proof. Computed by [`interactive::prove`] or +/// [`non_interactive::prove`] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Proof { @@ -230,10 +237,13 @@ pub struct Proof { pub use crate::common::Aux; +/// The interactive version of the ZK proof. Should be completed in 3 rounds: +/// prover commits to data, verifier responds with a random challenge, and +/// prover gives proof with commitment and challenge. pub mod interactive { use crate::unknown_order::BigNumber; - use generic_ec::{Curve, Point, Scalar}; + use generic_ec::{Curve, Point}; use rand_core::RngCore; use crate::common::{combine, convert_scalar, gen_inversible, InvalidProof, ProtocolError}; @@ -248,13 +258,16 @@ pub mod interactive { security: &SecurityParams, mut rng: R, ) -> Result<(Commitment, PrivateCommitment), ProtocolError> { - let two_to_l = BigNumber::one() << security.l_x; - let two_to_l_e = BigNumber::one() << (security.l_x + security.epsilon); + let two_to_l = BigNumber::one() << (security.l_x + 1); + let two_to_l_e = BigNumber::one() << (security.l_x + security.epsilon + 1); let modulo_l = two_to_l * &aux.rsa_modulo; let modulo_l_e = &two_to_l_e * &aux.rsa_modulo; let alpha = BigNumber::from_rng(&two_to_l_e, &mut rng); - let beta = BigNumber::from_rng(&(BigNumber::one() << security.l_y), &mut rng); + let beta = BigNumber::from_rng( + &(BigNumber::one() << (security.l_y + security.epsilon + 1)), + &mut rng, + ); let r = gen_inversible(data.key0.n(), &mut rng); let r_y = gen_inversible(data.key1.n(), &mut rng); let gamma = BigNumber::from_rng(&modulo_l_e, &mut rng); @@ -405,26 +418,28 @@ pub mod interactive { )?; fail_if( InvalidProof::RangeCheckFailed(6), - proof.z1 <= &one << (security.l_x + security.epsilon), + proof.z1 <= &one << (security.l_x + security.epsilon + 1), )?; fail_if( InvalidProof::RangeCheckFailed(7), - proof.z2 <= &one << (security.l_y + security.epsilon), + proof.z2 <= &one << (security.l_y + security.epsilon + 1), )?; Ok(()) } /// Generate random challenge - pub fn challenge(rng: &mut R) -> BigNumber + pub fn challenge(rng: &mut R, security: &SecurityParams) -> BigNumber where - C: Curve, R: RngCore, { - let x = Scalar::::random(rng); - BigNumber::from_slice(x.to_be_bytes().as_bytes()) + // double the range to account for +- + let m = BigNumber::from(2) * &security.q; + BigNumber::from_rng(&m, rng) } } +/// The non-interactive version of proof. Completed in one round, for example +/// see the documentation of parent module. pub mod non_interactive { use crate::unknown_order::BigNumber; @@ -484,34 +499,33 @@ pub mod non_interactive { security: &SecurityParams, ) -> Challenge where - Scalar: FromHash, D: Digest, { use rand_core::SeedableRng; let seed = shared_state .chain_update(aux.s.to_bytes()) - .chain_update(&aux.t.to_bytes()) - .chain_update(&aux.rsa_modulo.to_bytes()) - .chain_update(&data.key0.to_bytes()) - .chain_update(&data.key1.to_bytes()) - .chain_update(&data.c.to_bytes()) - .chain_update(&data.d.to_bytes()) - .chain_update(&data.y.to_bytes()) - .chain_update(&data.x.to_bytes(true)) - .chain_update(&commitment.a.to_bytes()) - .chain_update(&commitment.b_x.to_bytes(true)) - .chain_update(&commitment.b_y.to_bytes()) - .chain_update(&commitment.e.to_bytes()) - .chain_update(&commitment.s.to_bytes()) - .chain_update(&commitment.f.to_bytes()) - .chain_update(&commitment.t.to_bytes()) + .chain_update(aux.t.to_bytes()) + .chain_update(aux.rsa_modulo.to_bytes()) + .chain_update(data.key0.to_bytes()) + .chain_update(data.key1.to_bytes()) + .chain_update(data.c.to_bytes()) + .chain_update(data.d.to_bytes()) + .chain_update(data.y.to_bytes()) + .chain_update(data.x.to_bytes(true)) + .chain_update(commitment.a.to_bytes()) + .chain_update(commitment.b_x.to_bytes(true)) + .chain_update(commitment.b_y.to_bytes()) + .chain_update(commitment.e.to_bytes()) + .chain_update(commitment.s.to_bytes()) + .chain_update(commitment.f.to_bytes()) + .chain_update(commitment.t.to_bytes()) .chain_update((security.l_x as u64).to_le_bytes()) .chain_update((security.l_y as u64).to_le_bytes()) .chain_update((security.epsilon as u64).to_le_bytes()) .finalize(); let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed.into()); - let scalar = Scalar::::random(&mut rng); - BigNumber::from_slice(scalar.to_be_bytes().as_bytes()) + let m = BigNumber::from(2) * &security.q; + BigNumber::from_rng(&m, &mut rng) } } @@ -520,31 +534,37 @@ mod test { use generic_ec::{hash_to_curve::FromHash, Curve, Scalar}; use crate::common::convert_scalar; + use crate::common::test::{nonce, random_key}; use crate::unknown_order::BigNumber; - fn passing_test() + fn run( + mut rng: R, + security: super::SecurityParams, + plaintext_orig: BigNumber, + plaintext_mult: BigNumber, + plaintext_add: BigNumber, + ) -> Result<(), crate::common::InvalidProof> where Scalar: FromHash, { - let security = super::SecurityParams { - l_x: 1024, - l_y: 1024, - epsilon: 128, - }; - let private_key0 = libpaillier::DecryptionKey::random().unwrap(); + let affined = &plaintext_mult * &plaintext_orig + &plaintext_add; + + let private_key0 = random_key(&mut rng).unwrap(); let key0 = libpaillier::EncryptionKey::from(&private_key0); - let private_key1 = libpaillier::DecryptionKey::random().unwrap(); + let private_key1 = random_key(&mut rng).unwrap(); let key1 = libpaillier::EncryptionKey::from(&private_key1); let g = generic_ec::Point::::generator(); - let plaintext: BigNumber = 228.into(); - let plaintext_orig = BigNumber::from(100); - let plaintext_mult = BigNumber::from(2); - let plaintext_add = BigNumber::from(28); - let (ciphertext, _) = key0.encrypt(plaintext.to_bytes(), None).unwrap(); - let (ciphertext_orig, _) = key0.encrypt(plaintext_orig.to_bytes(), None).unwrap(); + let (ciphertext, _) = key0 + .encrypt(affined.to_bytes(), nonce(&mut rng, key0.n())) + .unwrap(); + let (ciphertext_orig, _) = key0 + .encrypt(plaintext_orig.to_bytes(), nonce(&mut rng, key0.n())) + .unwrap(); let ciphertext_mult = g * convert_scalar(&plaintext_mult); - let (ciphertext_add, nonce_y) = key1.encrypt(plaintext_add.to_bytes(), None).unwrap(); - let (ciphertext_add_action, nonce) = key0.encrypt(plaintext_add.to_bytes(), None).unwrap(); + let nonce_y = nonce(&mut rng, key1.n()); + let (ciphertext_add, nonce_y) = key1.encrypt(plaintext_add.to_bytes(), nonce_y).unwrap(); + let nonce = nonce(&mut rng, key0.n()); + let (ciphertext_add_action, nonce) = key0.encrypt(plaintext_add.to_bytes(), nonce).unwrap(); // verify that D is obtained from affine transformation of C let transformed = key0 .add( @@ -571,8 +591,8 @@ mod test { nonce_y, }; - let p = BigNumber::prime(1024 + 128 + 1); - let q = BigNumber::prime(1024 + 128 + 1); + let p = BigNumber::prime_from_rng(1024, &mut rng); + let q = BigNumber::prime_from_rng(1024, &mut rng); let rsa_modulo = p * q; let s: BigNumber = 123.into(); let t: BigNumber = 321.into(); @@ -588,97 +608,72 @@ mod test { &data, &pdata, &security, - rand_core::OsRng::default(), + rng, ) .unwrap(); - let r = super::non_interactive::verify( - shared_state, - &aux, - &data, - &commitment, - &security, - &proof, - ); + super::non_interactive::verify(shared_state, &aux, &data, &commitment, &security, &proof) + } + fn passing_test() + where + Scalar: FromHash, + { + let mut rng = rand_core::OsRng::default(); + let security = super::SecurityParams { + l_x: 1024, + l_y: 1024, + epsilon: 256, + q: BigNumber::prime_from_rng(128, &mut rng), + }; + let plaintext_orig = BigNumber::from(100); + let plaintext_mult = BigNumber::from(1) << (security.l_x + 1); + let plaintext_add = BigNumber::from(1) << (security.l_y + 1); + let r = run(rng, security, plaintext_orig, plaintext_mult, plaintext_add); match r { Ok(()) => (), Err(e) => panic!("{:?}", e), } } - fn failing_test() + fn failing_on_additive() where Scalar: FromHash, { + let mut rng = rand_core::OsRng::default(); let security = super::SecurityParams { l_x: 1024, l_y: 1024, - epsilon: 128, + epsilon: 256, + q: BigNumber::prime_from_rng(128, &mut rng), }; - let private_key0 = libpaillier::DecryptionKey::random().unwrap(); - let key0 = libpaillier::EncryptionKey::from(&private_key0); - let private_key1 = libpaillier::DecryptionKey::random().unwrap(); - let key1 = libpaillier::EncryptionKey::from(&private_key1); - let g = generic_ec::Point::::generator(); - let plaintext_orig = BigNumber::from(1337); - let plaintext_mult = (BigNumber::one() << (1024 + 128)) + 1; - let plaintext_add: BigNumber = (BigNumber::one() << (1024 + 128)) + 2; - let (ciphertext_orig, _) = key0.encrypt(plaintext_orig.to_bytes(), None).unwrap(); - let ciphertext_mult = g * convert_scalar(&plaintext_mult); - let (ciphertext_add, nonce_y) = key1.encrypt(plaintext_add.to_bytes(), None).unwrap(); - let (ciphertext_add_action, nonce) = key0.encrypt(plaintext_add.to_bytes(), None).unwrap(); - // verify that D is obtained from affine transformation of C - let transformed = key0 - .add( - &key0.mul(&ciphertext_orig, &plaintext_mult).unwrap(), - &ciphertext_add_action, - ) - .unwrap(); - let data = super::Data { - key0, - key1, - c: ciphertext_orig, - d: transformed, - y: ciphertext_add, - x: ciphertext_mult, - }; - let pdata = super::PrivateData { - x: plaintext_mult, - y: plaintext_add, - nonce, - nonce_y, - }; - - let p = BigNumber::prime(1024 + 128 + 1); - let q = BigNumber::prime(1024 + 128 + 1); - let rsa_modulo = p * q; - let s: BigNumber = 123.into(); - let t: BigNumber = 321.into(); - assert_eq!(s.gcd(&rsa_modulo), 1.into()); - assert_eq!(t.gcd(&rsa_modulo), 1.into()); - let aux = super::Aux { s, t, rsa_modulo }; - - let shared_state = sha2::Sha256::default(); + let plaintext_orig = BigNumber::from(100); + let plaintext_mult = BigNumber::from(1) << (security.l_x + 1); + let plaintext_add = (BigNumber::from(1) << (security.l_y + security.epsilon + 1)) + 1; + let r = run(rng, security, plaintext_orig, plaintext_mult, plaintext_add); + match r { + Ok(()) => panic!("proof should not pass"), + Err(crate::common::InvalidProof::RangeCheckFailed(7)) => (), + Err(e) => panic!("proof should not fail with: {:?}", e), + } + } - let (commitment, proof) = super::non_interactive::prove( - shared_state.clone(), - &aux, - &data, - &pdata, - &security, - rand_core::OsRng::default(), - ) - .unwrap(); - let r = super::non_interactive::verify( - shared_state, - &aux, - &data, - &commitment, - &security, - &proof, - ); + fn failing_on_multiplicative() + where + Scalar: FromHash, + { + let mut rng = rand_core::OsRng::default(); + let security = super::SecurityParams { + l_x: 1024, + l_y: 1024, + epsilon: 256, + q: BigNumber::prime_from_rng(128, &mut rng), + }; + let plaintext_orig = BigNumber::from(100); + let plaintext_mult = (BigNumber::from(1) << (security.l_x + security.epsilon + 1)) + 1; + let plaintext_add = BigNumber::from(1) << (security.l_y + 1); + let r = run(rng, security, plaintext_orig, plaintext_mult, plaintext_add); match r { Ok(()) => panic!("proof should not pass"), - Err(crate::common::InvalidProof::RangeCheckFailed(_)) => (), + Err(crate::common::InvalidProof::RangeCheckFailed(6)) => (), Err(e) => panic!("proof should not fail with: {:?}", e), } } @@ -688,8 +683,12 @@ mod test { passing_test::() } #[test] - fn failing_p256() { - failing_test::() + fn failing_p256_add() { + failing_on_additive::() + } + #[test] + fn failing_p256_mul() { + failing_on_multiplicative::() } #[test] @@ -697,7 +696,81 @@ mod test { passing_test::() } #[test] - fn failing_million() { - failing_test::() + fn failing_million_add() { + failing_on_additive::() + } + #[test] + fn failing_million_mul() { + failing_on_multiplicative::() + } + + // see notes in + // [crate::paillier_encryption_in_range::test::rejected_with_probability_1_over_2] + // for motivation and design of the following two tests + + #[test] + fn mul_rejected_with_probability_1_over_2() { + use rand_core::SeedableRng; + fn maybe_rejected(mut rng: rand_chacha::ChaCha20Rng) -> bool { + let security = super::SecurityParams { + l_x: 1024, + l_y: 1024, + epsilon: 130, + q: BigNumber::prime_from_rng(128, &mut rng), + }; + let plaintext_orig = BigNumber::from(100); + let plaintext_mult = (BigNumber::from(1) << (security.l_x + 1)) - 1; + let plaintext_add = BigNumber::from(1) << (security.l_y / 2); + let r = run::<_, generic_ec_curves::rust_crypto::Secp256r1>( + rng, + security, + plaintext_orig, + plaintext_mult, + plaintext_add, + ); + match r { + Ok(()) => true, + Err(crate::common::InvalidProof::RangeCheckFailed(6)) => false, + Err(e) => panic!("proof should not fail with: {:?}", e), + } + } + + let rng = rand_chacha::ChaCha20Rng::seed_from_u64(0); + assert!(!maybe_rejected(rng), "should fail"); + let rng = rand_chacha::ChaCha20Rng::seed_from_u64(1); + assert!(maybe_rejected(rng), "should pass"); + } + + #[test] + fn add_rejected_with_probability_1_over_2() { + use rand_core::SeedableRng; + fn maybe_rejected(mut rng: rand_chacha::ChaCha20Rng) -> bool { + let security = super::SecurityParams { + l_x: 1024, + l_y: 1024, + epsilon: 130, + q: BigNumber::prime_from_rng(128, &mut rng), + }; + let plaintext_orig = BigNumber::from(100); + let plaintext_mult = BigNumber::from(1) << (security.l_x / 2); + let plaintext_add = (BigNumber::from(1) << (security.l_y + 1)) + 1; + let r = run::<_, generic_ec_curves::rust_crypto::Secp256r1>( + rng, + security, + plaintext_orig, + plaintext_mult, + plaintext_add, + ); + match r { + Ok(()) => true, + Err(crate::common::InvalidProof::RangeCheckFailed(7)) => false, + Err(e) => panic!("proof should not fail with: {:?}", e), + } + } + + let rng = rand_chacha::ChaCha20Rng::seed_from_u64(0); + assert!(maybe_rejected(rng), "should pass"); + let rng = rand_chacha::ChaCha20Rng::seed_from_u64(1); + assert!(!maybe_rejected(rng), "should fail"); } } diff --git a/src/paillier_blum_modulus.rs b/src/paillier_blum_modulus.rs index 20282f9..038e35a 100644 --- a/src/paillier_blum_modulus.rs +++ b/src/paillier_blum_modulus.rs @@ -67,11 +67,17 @@ use serde::{Deserialize, Serialize}; use crate::unknown_order::BigNumber; +/// Reason for failure. If the proof failes, you should only be interested in a +/// reason for debugging purposes #[derive(Debug, PartialEq, Eq)] pub enum InvalidProof { + /// Paillier-Blum modulus is prime ModulusIsPrime, + /// Paillier-Blum modulus ModulusIsEven, + /// Proof's z value in n-th power does not equal commitment value IncorrectNthRoot, + /// Proof's x value in 4-th power does not equal commitment value IncorrectFourthRoot, } @@ -89,7 +95,7 @@ pub struct PrivateData { pub q: BigNumber, } -/// Prover's first message, obtained by `commit` +/// Prover's first message, obtained by [`interactive::commit`] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Commitment { @@ -97,7 +103,7 @@ pub struct Commitment { } /// Verifier's challenge to prover. Can be obtained deterministically by -/// `challenge` +/// [`non_interactive::challenge`] or randomly by [`interactive::challenge`] /// /// Consists of `M` singular challenges #[derive(Debug, PartialEq, Eq, Clone)] @@ -105,6 +111,7 @@ pub struct Challenge { pub ys: [BigNumber; M], } +/// A part of proof. Having enough of those guarantees security #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ProofPoint { @@ -114,12 +121,16 @@ pub struct ProofPoint { pub z: BigNumber, } -/// The ZK proof. Computed by `prove`. Consists of M proofs for each challenge +/// The ZK proof. Computed by [`interactive::prove`] or +/// [`non_interactive::prove`]. Consists of M proofs for each challenge #[derive(Debug, Clone)] pub struct Proof { pub points: [ProofPoint; M], } +/// The interactive version of the ZK proof. Should be completed in 3 rounds: +/// prover commits to data, verifier responds with a random challenge, and +/// prover gives proof with commitment and challenge. pub mod interactive { use crate::unknown_order::BigNumber; use rand_core::RngCore; @@ -187,6 +198,9 @@ pub mod interactive { Ok(()) } + /// Generate random challenge + /// + /// `data` parameter is used to generate challenge in correct range pub fn challenge( Data { ref n }: &Data, rng: &mut R, @@ -196,6 +210,8 @@ pub mod interactive { } } +/// The non-interactive version of proof. Completed in one round, for example +/// see the documentation of parent module. pub mod non_interactive { use crate::unknown_order::BigNumber; use rand_core::RngCore; @@ -222,6 +238,7 @@ pub mod non_interactive { (commitment, proof) } + /// Verify the proof, deriving challenge independently from same data pub fn verify( shared_state: D, data: &Data, @@ -251,8 +268,8 @@ pub mod non_interactive { for (i, y_ref) in ys.iter_mut().enumerate() { let seed = shared_state .clone() - .chain_update(&n.to_bytes()) - .chain_update(&commitment.w.to_bytes()) + .chain_update(n.to_bytes()) + .chain_update(commitment.w.to_bytes()) .chain_update((i as u64).to_le_bytes()) .finalize(); let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed.into()); diff --git a/src/paillier_decryption_modulo_q.rs b/src/paillier_decryption_modulo_q.rs index c4ac243..6dfbd04 100644 --- a/src/paillier_decryption_modulo_q.rs +++ b/src/paillier_decryption_modulo_q.rs @@ -78,6 +78,8 @@ use serde::{Deserialize, Serialize}; pub use crate::common::InvalidProof; use crate::unknown_order::BigNumber; +/// Security parameters for proof. Choosing the values is a tradeoff between +/// speed and chance of rejecting a valid proof or accepting an invalid proof #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct SecurityParams { @@ -112,7 +114,7 @@ pub struct PrivateData { #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -/// Prover's first message, obtained by `commit` +/// Prover's first message, obtained by [`interactive::commit`] pub struct Commitment { pub s: BigNumber, pub t: BigNumber, @@ -131,10 +133,11 @@ pub struct PrivateCommitment { } /// Verifier's challenge to prover. Can be obtained deterministically by -/// `challenge` +/// [`non_interactive::challenge`] or randomly by [`interactive::challenge`] pub type Challenge = BigNumber; -/// The ZK proof. Computed by `prove` +/// The ZK proof. Computed by [`interactive::prove`] or +/// [`non_interactive::prove`]. Consists of M proofs for each challenge #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Proof { @@ -145,6 +148,9 @@ pub struct Proof { pub use crate::common::Aux; +/// The interactive version of the ZK proof. Should be completed in 3 rounds: +/// prover commits to data, verifier responds with a random challenge, and +/// prover gives proof with commitment and challenge. pub mod interactive { use crate::unknown_order::BigNumber; use rand_core::RngCore; @@ -163,8 +169,8 @@ pub mod interactive { security: &SecurityParams, mut rng: R, ) -> Result<(Commitment, PrivateCommitment), ProtocolError> { - let two_to_l_e = BigNumber::one() << (security.l + security.epsilon); - let modulo_l = (BigNumber::one() << security.l) * &aux.rsa_modulo; + let two_to_l_e = BigNumber::one() << (security.l + security.epsilon + 1); + let modulo_l = (BigNumber::one() << (security.l + 1)) * &aux.rsa_modulo; let modulo_l_e = &two_to_l_e * &aux.rsa_modulo; let alpha = BigNumber::from_rng(&two_to_l_e, &mut rng); @@ -188,7 +194,9 @@ pub mod interactive { /// Generate random challenge pub fn challenge(data: &Data, rng: &mut R) -> Challenge { - BigNumber::from_rng(&data.q, rng) + // double the range to account for +- + let m = BigNumber::from(2) * &data.q; + BigNumber::from_rng(&m, rng) } /// Compute proof for given data and prior protocol values @@ -259,6 +267,8 @@ pub mod interactive { } } +/// The non-interactive version of proof. Completed in one round, for example +/// see the documentation of parent module. pub mod non_interactive { use crate::unknown_order::BigNumber; use rand_core::RngCore; @@ -301,22 +311,24 @@ pub mod non_interactive { { use rand_core::SeedableRng; let seed = shared_state - .chain_update(&aux.s.to_bytes()) - .chain_update(&aux.t.to_bytes()) - .chain_update(&aux.rsa_modulo.to_bytes()) - .chain_update(&data.q.to_bytes()) - .chain_update(&data.key.to_bytes()) - .chain_update(&data.c.to_bytes()) - .chain_update(&data.x.to_bytes()) - .chain_update(&commitment.s.to_bytes()) - .chain_update(&commitment.t.to_bytes()) - .chain_update(&commitment.a.to_bytes()) - .chain_update(&commitment.gamma.to_bytes()) + .chain_update(aux.s.to_bytes()) + .chain_update(aux.t.to_bytes()) + .chain_update(aux.rsa_modulo.to_bytes()) + .chain_update(data.q.to_bytes()) + .chain_update(data.key.to_bytes()) + .chain_update(data.c.to_bytes()) + .chain_update(data.x.to_bytes()) + .chain_update(commitment.s.to_bytes()) + .chain_update(commitment.t.to_bytes()) + .chain_update(commitment.a.to_bytes()) + .chain_update(commitment.gamma.to_bytes()) .finalize(); let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed.into()); - BigNumber::from_rng(&data.q, &mut rng) + let m = BigNumber::from(2) * &data.q; + BigNumber::from_rng(&m, &mut rng) } + /// Verify the proof, deriving challenge independently from same data pub fn verify( shared_state: D, aux: &Aux, @@ -492,4 +504,10 @@ mod test { panic!("proof should not pass"); } } + + // Following motivation outlined in + // [crate::paillier_encryption_in_range::test::rejected_with_probability_1_over_2], + // I would like to make a similar borderline test, but no security estimate + // was given in the paper and this proof differs significantly from others + // in this library, so I have to omit the test. } diff --git a/src/paillier_encryption_in_range.rs b/src/paillier_encryption_in_range.rs index da50983..568f5ee 100644 --- a/src/paillier_encryption_in_range.rs +++ b/src/paillier_encryption_in_range.rs @@ -7,7 +7,7 @@ //! cryptosystem. P also has `plaintext`, `nonce`, and //! `ciphertext = key.encrypt(plaintext, nonce)`. //! -//! P wants to prove that `plaintext` is at most `L` bits, without disclosing +//! P wants to prove that `plaintext` is at most `L + 1` bits, without disclosing //! it, the `pkey`, and `nonce` //! ## Example @@ -79,12 +79,15 @@ use serde::{Deserialize, Serialize}; pub use crate::common::InvalidProof; use crate::unknown_order::BigNumber; +/// Security parameters for proof. Choosing the values is a tradeoff between +/// speed and chance of rejecting a valid proof or accepting an invalid proof #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct SecurityParams { - /// l in paper, bit size of plaintext + /// l in paper, security parameter for bit size of plaintext: it needs to + /// be in range [-2^l; 2^l] or equivalently 2^(l+1) pub l: usize, - /// Epsilon in paper, range extension and security parameter for x + /// Epsilon in paper, slackness parameter pub epsilon: usize, /// q in paper. Security parameter for challenge pub q: BigNumber, @@ -110,7 +113,7 @@ pub struct PrivateData { } // As described in cggmp21 at page 33 -/// Prover's first message, obtained by `commit` +/// Prover's first message, obtained by [`interactive::commit`] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Commitment { @@ -130,11 +133,12 @@ pub struct PrivateCommitment { } /// Verifier's challenge to prover. Can be obtained deterministically by -/// `challenge` +/// [`non_interactive::challenge`] or randomly by [`interactive::challenge`] pub type Challenge = BigNumber; // As described in cggmp21 at page 33 -/// The ZK proof. Computed by `prove` +/// The ZK proof. Computed by [`interactive::prove`] or +/// [`non_interactive::prove`] #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Proof { @@ -145,6 +149,9 @@ pub struct Proof { pub use crate::common::Aux; +/// The interactive version of the ZK proof. Should be completed in 3 rounds: +/// prover commits to data, verifier responds with a random challenge, and +/// prover gives proof with commitment and challenge. pub mod interactive { use crate::unknown_order::BigNumber; use rand_core::RngCore; @@ -163,8 +170,9 @@ pub mod interactive { security: &SecurityParams, mut rng: R, ) -> (Commitment, PrivateCommitment) { - let two_to_l = BigNumber::from(1) << security.l; - let two_to_l_plus_e = BigNumber::from(1) << (security.l + security.epsilon); + // add 1 to exponents to account for +- + let two_to_l = BigNumber::from(1) << (security.l + 1); + let two_to_l_plus_e = BigNumber::from(1) << (security.l + security.epsilon + 1); let alpha = BigNumber::from_rng(&two_to_l_plus_e, &mut rng); let mu = BigNumber::from_rng(&(two_to_l * &aux.rsa_modulo), &mut rng); let r = gen_inversible(data.key.n(), &mut rng); @@ -241,18 +249,25 @@ pub mod interactive { return Err(InvalidProof::EqualityCheckFailed(2)); } - if proof.z1 > (BigNumber::one() << (security.l + security.epsilon)) { + if proof.z1 > (BigNumber::one() << (security.l + security.epsilon + 1)) { return Err(InvalidProof::RangeCheckFailed(3)); } Ok(()) } + /// Generate random challenge + /// + /// `security` parameter is used to generate challenge in correct range pub fn challenge(security: &SecurityParams, rng: &mut R) -> Challenge { - BigNumber::from_rng(&security.q, rng) + // double the range to account for +- + let m = BigNumber::from(2) * &security.q; + BigNumber::from_rng(&m, rng) } } +/// The non-interactive version of proof. Completed in one round, for example +/// see the documentation of parent module. pub mod non_interactive { use crate::unknown_order::BigNumber; use rand_core::RngCore; @@ -296,19 +311,21 @@ pub mod non_interactive { { use rand_core::SeedableRng; let seed = shared_state - .chain_update(&aux.s.to_bytes()) - .chain_update(&aux.t.to_bytes()) - .chain_update(&aux.rsa_modulo.to_bytes()) - .chain_update(&data.key.to_bytes()) - .chain_update(&data.ciphertext.to_bytes()) - .chain_update(&commitment.s.to_bytes()) - .chain_update(&commitment.a.to_bytes()) - .chain_update(&commitment.c.to_bytes()) + .chain_update(aux.s.to_bytes()) + .chain_update(aux.t.to_bytes()) + .chain_update(aux.rsa_modulo.to_bytes()) + .chain_update(data.key.to_bytes()) + .chain_update(data.ciphertext.to_bytes()) + .chain_update(commitment.s.to_bytes()) + .chain_update(commitment.a.to_bytes()) + .chain_update(commitment.c.to_bytes()) .finalize(); let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed.into()); - BigNumber::from_rng(&security.q, &mut rng) + let m = BigNumber::from(2) * &security.q; + BigNumber::from_rng(&m, &mut rng) } + /// Verify the proof, deriving challenge independently from same data pub fn verify( shared_state: D, aux: &Aux, @@ -330,22 +347,20 @@ mod test { use crate::common::InvalidProof; use crate::unknown_order::BigNumber; - #[test] - fn passing() { - let security = super::SecurityParams { - l: 1024, - epsilon: 128, - q: BigNumber::prime(256), - }; - let private_key = libpaillier::DecryptionKey::random().unwrap(); + fn run_with( + mut rng: R, + security: super::SecurityParams, + plaintext: BigNumber, + ) -> Result<(), crate::common::InvalidProof> { + let private_key = crate::common::test::random_key(&mut rng).unwrap(); let key = libpaillier::EncryptionKey::from(&private_key); - let plaintext: BigNumber = 228.into(); - let (ciphertext, nonce) = key.encrypt(plaintext.to_bytes(), None).unwrap(); + let nonce = crate::common::test::nonce(&mut rng, key.n()); + let (ciphertext, nonce) = key.encrypt(plaintext.to_bytes(), nonce).unwrap(); let data = super::Data { key, ciphertext }; let pdata = super::PrivateData { plaintext, nonce }; - let p = BigNumber::prime(1024); - let q = BigNumber::prime(1024); + let p = BigNumber::prime_from_rng(1024, &mut rng); + let q = BigNumber::prime_from_rng(1024, &mut rng); let rsa_modulo = p * q; let s: BigNumber = 123.into(); let t: BigNumber = 321.into(); @@ -360,16 +375,21 @@ mod test { &data, &pdata, &security, - rand_core::OsRng::default(), - ); - let r = super::non_interactive::verify( - shared_state, - &aux, - &data, - &commitment, - &security, - &proof, + rng, ); + super::non_interactive::verify(shared_state, &aux, &data, &commitment, &security, &proof) + } + + #[test] + fn passing() { + let mut rng = rand_core::OsRng::default(); + let security = super::SecurityParams { + l: 1024, + epsilon: 256, + q: BigNumber::prime_from_rng(128, &mut rng), + }; + let plaintext = (BigNumber::one() << (security.l + 1)) - 1; + let r = run_with(rng, security, plaintext); match r { Ok(()) => (), Err(e) => panic!("{:?}", e), @@ -377,50 +397,51 @@ mod test { } #[test] fn failing() { + let mut rng = rand_core::OsRng::default(); let security = super::SecurityParams { l: 1024, - epsilon: 128, - q: BigNumber::prime(256), + epsilon: 256, + q: BigNumber::prime_from_rng(128, &mut rng), }; - let p = BigNumber::prime(1024); - let q = BigNumber::prime(1024); - let private_key = libpaillier::DecryptionKey::with_primes(&p, &q).unwrap(); - let key = libpaillier::EncryptionKey::from(&private_key); - let plaintext: BigNumber = (BigNumber::one() << (security.l + security.epsilon)) + 1; - let (ciphertext, nonce) = key.encrypt(plaintext.to_bytes(), None).unwrap(); - let data = super::Data { key, ciphertext }; - let pdata = super::PrivateData { plaintext, nonce }; - - let p = BigNumber::prime(1024); - let q = BigNumber::prime(1024); - let rsa_modulo = p * q; - let s: BigNumber = 123.into(); - let t: BigNumber = 321.into(); - assert_eq!(s.gcd(&rsa_modulo), 1.into()); - assert_eq!(t.gcd(&rsa_modulo), 1.into()); - let aux = super::Aux { s, t, rsa_modulo }; - - let shared_state = sha2::Sha256::default(); - let (commitment, proof) = super::non_interactive::prove( - shared_state.clone(), - &aux, - &data, - &pdata, - &security, - rand_core::OsRng::default(), - ); - let r = super::non_interactive::verify( - shared_state, - &aux, - &data, - &commitment, - &security, - &proof, - ); + let plaintext = (BigNumber::one() << (security.l + security.epsilon + 1)) + 1; + let r = run_with(rng, security, plaintext); match r { Ok(()) => panic!("proof should not pass"), Err(InvalidProof::RangeCheckFailed(_)) => (), Err(e) => panic!("proof should not fail with {:?}", e), } } + + #[test] + fn rejected_with_probability_1_over_2() { + // plaintext in range 2^(l+1) should be rejected with probablility + // q / 2^epsilon. I set parameters like this: + // bitsize(q) = 128 + // epsilon = 129 + // Thus probability should be around 1/2. + // in 32 runs that's not what was observed. Very possible it's an + // artifact of distribution, so I decide to ignore it, and test that + // there are passing and failing values. + + fn maybe_rejected(mut rng: rand_chacha::ChaCha20Rng) -> bool { + let security = super::SecurityParams { + l: 512, + epsilon: 129, + q: BigNumber::prime_from_rng(128, &mut rng), + }; + let plaintext: BigNumber = (BigNumber::one() << (security.l + 1)) - 1; + let r = run_with(rng, security, plaintext); + match r { + Ok(()) => true, + Err(InvalidProof::RangeCheckFailed(_)) => false, + Err(e) => panic!("proof should not fail with {:?}", e), + } + } + + use rand_core::SeedableRng; + let rng = rand_chacha::ChaCha20Rng::seed_from_u64(1); + assert!(maybe_rejected(rng), "should pass"); + let rng = rand_chacha::ChaCha20Rng::seed_from_u64(2); + assert!(!maybe_rejected(rng), "should fail"); + } }