From 908b7614c217031bc7a48a9d3d896c9a90972bc5 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Wed, 11 Dec 2024 18:14:32 +0100 Subject: [PATCH 1/2] Upload paillier-zk from commit 29bd8f710bc1cd851098400708adf051bc6e4d2d Signed-off-by: Denis Varlakov --- .github/workflows/publish.yml | 22 +- .github/workflows/rust.yml | 8 + Cargo.lock | 10 +- Cargo.toml | 3 +- paillier-zk/.gitignore | 4 + paillier-zk/CHANGELOG.md | 21 + paillier-zk/Cargo.toml | 51 ++ paillier-zk/README.md | 15 + paillier-zk/clippy.toml | 1 + paillier-zk/examples/pregenerate.rs | 104 +++ paillier-zk/src/_doctest.rs | 10 + paillier-zk/src/common.rs | 446 ++++++++++++ paillier-zk/src/common/sqrt.rs | 69 ++ paillier-zk/src/curve.rs | 273 ++++++++ ...element_vs_paillier_encryption_in_range.rs | 473 +++++++++++++ paillier-zk/src/lib.rs | 72 ++ paillier-zk/src/multiexp.rs | 228 +++++++ paillier-zk/src/no_small_factor.rs | 470 +++++++++++++ .../src/paillier_affine_operation_in_range.rs | 645 ++++++++++++++++++ paillier-zk/src/paillier_blum_modulus.rs | 335 +++++++++ .../src/paillier_encryption_in_range.rs | 427 ++++++++++++ .../test-data/prover_decryption_key.json | 10 + .../test-data/prover_encryption_key.json | 4 + .../test-data/someone_encryption_key.json | 4 + .../test-data/someone_encryption_key0.json | 4 + .../test-data/someone_encryption_key1.json | 4 + paillier-zk/test-data/verifier_aux.json | 14 + 27 files changed, 3719 insertions(+), 8 deletions(-) create mode 100644 paillier-zk/.gitignore create mode 100644 paillier-zk/CHANGELOG.md create mode 100644 paillier-zk/Cargo.toml create mode 100644 paillier-zk/README.md create mode 100644 paillier-zk/clippy.toml create mode 100644 paillier-zk/examples/pregenerate.rs create mode 100644 paillier-zk/src/_doctest.rs create mode 100644 paillier-zk/src/common.rs create mode 100644 paillier-zk/src/common/sqrt.rs create mode 100644 paillier-zk/src/curve.rs create mode 100644 paillier-zk/src/group_element_vs_paillier_encryption_in_range.rs create mode 100644 paillier-zk/src/lib.rs create mode 100644 paillier-zk/src/multiexp.rs create mode 100644 paillier-zk/src/no_small_factor.rs create mode 100644 paillier-zk/src/paillier_affine_operation_in_range.rs create mode 100644 paillier-zk/src/paillier_blum_modulus.rs create mode 100644 paillier-zk/src/paillier_encryption_in_range.rs create mode 100644 paillier-zk/test-data/prover_decryption_key.json create mode 100644 paillier-zk/test-data/prover_encryption_key.json create mode 100644 paillier-zk/test-data/someone_encryption_key.json create mode 100644 paillier-zk/test-data/someone_encryption_key0.json create mode 100644 paillier-zk/test-data/someone_encryption_key1.json create mode 100644 paillier-zk/test-data/verifier_aux.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 778cc7f6..b7c29f54 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,7 @@ on: - 'v*' - 'key-share-v*' - 'cggmp21-keygen-v*' + - 'paillier-zk-v*' workflow_dispatch: name: Publish @@ -35,9 +36,9 @@ jobs: && startsWith(github.ref_name, 'key-share-v') steps: - uses: actions/checkout@v3 - - run: cargo publish -p key-share --token ${CRATES_TOKEN} + - run: cargo publish -p key-share env: - CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_TOKEN }} publish-cggmp21-keygen: name: Publish cggmp21-keygen @@ -48,6 +49,19 @@ jobs: && startsWith(github.ref_name, 'cggmp21-keygen-v') steps: - uses: actions/checkout@v3 - - run: cargo publish -p cggmp21-keygen --token ${CRATES_TOKEN} + - run: cargo publish -p cggmp21-keygen env: - CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_TOKEN }} + + publish-paillier-zk: + name: Publish paillier-zk + environment: crates.io + runs-on: ubuntu-latest + if: >- + github.ref_type == 'tag' + && startsWith(github.ref_name, 'paillier-zk-v') + steps: + - uses: actions/checkout@v3 + - run: cargo publish -p paillier-zk + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a2495a84..783e544e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -34,6 +34,7 @@ jobs: - key-share - cggmp21-keygen - cggmp21 + - paillier-zk steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 @@ -90,6 +91,13 @@ jobs: cache-on-failure: "true" - name: Run clippy run: cargo clippy --all --all-features --lib --exclude cggmp21-tests -- --no-deps -D clippy::all -D clippy::unwrap_used -D clippy::expect_used + clippy-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" - name: Run clippy tests run: cargo clippy --tests --all-features --lib -- -D clippy::all diff --git a/Cargo.lock b/Cargo.lock index af117f23..09e23492 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1640,20 +1640,24 @@ dependencies = [ [[package]] name = "paillier-zk" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f7666e5a36fe77ef28a4f8306367a759c7caacbdf8eeea89f524ccc2ade933" +version = "0.4.2" dependencies = [ + "anyhow", "digest", "fast-paillier", "generic-ec", "rand_core", + "rand_dev", "rand_hash", "rug", "serde", + "serde_json", "serde_with 3.0.0", + "sha2", + "subtle", "thiserror", "udigest", + "zeroize", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 381648c0..40c25ea5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "cggmp21", "cggmp21-keygen", "key-share", + "paillier-zk", "tests", ] exclude = [ @@ -14,12 +15,12 @@ exclude = [ cggmp21 = { version = "0.5", path = "cggmp21" } cggmp21-keygen = { version = "0.4", path = "cggmp21-keygen" } key-share = { version = "0.5", path = "key-share", default-features = false } +paillier-zk = { version = "0.4.1", path = "paillier-zk", default-features = false } generic-ec = { version = "0.4.1", default-features = false } generic-ec-zkp = { version = "0.4.1", default-features = false } round-based = { version = "0.3", default-features = false } -paillier-zk = "0.4.1" udigest = { version = "0.2.1", default-features = false } digest = { version = "0.10", default-features = false } diff --git a/paillier-zk/.gitignore b/paillier-zk/.gitignore new file mode 100644 index 00000000..8c8042ce --- /dev/null +++ b/paillier-zk/.gitignore @@ -0,0 +1,4 @@ +/target +.rstags + +.helix/ diff --git a/paillier-zk/CHANGELOG.md b/paillier-zk/CHANGELOG.md new file mode 100644 index 00000000..bb8f44c3 --- /dev/null +++ b/paillier-zk/CHANGELOG.md @@ -0,0 +1,21 @@ +## v0.4.2 +* Update links in the crate settings, update readme [#53] + +[#53]: https://github.com/LFDT-Lockness/paillier-zk/pull/53 + +## v0.4.1 +* Prettify code by using `#[udigest(as = ...)]` attribute [#51] + +[#51]: https://github.com/LFDT-Lockness/paillier-zk/pull/51 + +## v0.4.0 +* security fix: derive challenges for zero-knowledge proof unambiguously + +## v0.3.0 +* Update `generic-ec` dep to v0.3 [#48] + +[#48]: https://github.com/LFDT-Lockness/paillier-zk/pull/48 + +## v0.2.0 + +All changes prior to this version were not documented diff --git a/paillier-zk/Cargo.toml b/paillier-zk/Cargo.toml new file mode 100644 index 00000000..790892bb --- /dev/null +++ b/paillier-zk/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "paillier-zk" +version = "0.4.2" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "ZK-proofs for Paillier encryption scheme" +repository = "https://github.com/LFDT-Lockness/paillier-zk" +categories = ["algorithms", "cryptography"] +keywords = ["paillier", "zk-proofs", "zero-knowledge"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +generic-ec = { version = "0.4", features = ["udigest"] } +rand_core = { version = "0.6", default-features = false } +digest = "0.10" +fast-paillier = "0.1" +rug = { version = "1.21", default-features = false, features = ["integer", "rand"] } + +thiserror = "1" + +serde = { version = "1", features = ["derive"], optional = true } +serde_with = { version = "3", default-features = false, features = ["macros"], optional = true } + +udigest = { version = "0.2.1", default-features = false, features = ["inline-struct", "derive"] } +rand_hash = "0.1" + +[dev-dependencies] +generic-ec = { version = "0.4", features = ["udigest", "all-curves"] } +rand_dev = { version = "0.1", default-features = false } +sha2 = { version = "0.10", default-features = false } + +subtle = { version = "2.4", default-features = false } +zeroize = { version = "1.5", default-features = false } + +anyhow = "1" +serde_json = "1" + +[features] +default = [] +serde = ["dep:serde", "dep:serde_with", "generic-ec/serde", "rug/serde", "fast-paillier/serde"] + +# This features is exlusively used for `cargo test --doc` +__internal_doctest = ["serde"] + +[[example]] +name = "pregenerate" +required-features = ["serde"] + +[package.metadata.docs.rs] +all-features = true diff --git a/paillier-zk/README.md b/paillier-zk/README.md new file mode 100644 index 00000000..66459654 --- /dev/null +++ b/paillier-zk/README.md @@ -0,0 +1,15 @@ +![License](https://img.shields.io/crates/l/paillier-zk.svg) +[![Docs](https://docs.rs/paillier-zk/badge.svg)](https://docs.rs/paillier-zk) +[![Crates io](https://img.shields.io/crates/v/paillier-zk.svg)](https://crates.io/crates/paillier-zk) +[![Discord](https://img.shields.io/discord/905194001349627914?logo=discord&logoColor=ffffff&label=Discord)][in Discord] + +[in Discord]: https://discordapp.com/channels/905194001349627914/1285268686147424388 + +# paillier-zk + +This crate provides ZK-proofs for some properties about paillier encryption. +See the module docs for the properties and examples of usage. + +This library is built on top of [fast-paillier](https://lib.rs/fast-paillier) crate. +This crate and the underlying big integer implementation are reexported for the +consumer to be able to use them, instead of trying to match a version. diff --git a/paillier-zk/clippy.toml b/paillier-zk/clippy.toml new file mode 100644 index 00000000..5539ef09 --- /dev/null +++ b/paillier-zk/clippy.toml @@ -0,0 +1 @@ +disallowed-methods = [] diff --git a/paillier-zk/examples/pregenerate.rs b/paillier-zk/examples/pregenerate.rs new file mode 100644 index 00000000..8e67c524 --- /dev/null +++ b/paillier-zk/examples/pregenerate.rs @@ -0,0 +1,104 @@ +//! Pregenerates aux data and keys +//! +//! This example shows how aux data can be generated to set up proofs. Generated data is used by doctests. + +use anyhow::{Context, Result}; +use rug::{Complete, Integer}; + +use paillier_zk::IntegerExt; + +fn main() -> Result<()> { + let mut rng = rand_core::OsRng; + + // Generate Verifier's aux data + { + let p = generate_blum_prime(&mut rng, 1024); + let q = generate_blum_prime(&mut rng, 1024); + let n = (&p * &q).complete(); + + let (s, t) = { + let phi_n = (p.clone() - 1u8) * (q.clone() - 1u8); + let r = Integer::gen_invertible(&n, &mut rng); + let lambda = phi_n.random_below(&mut fast_paillier::utils::external_rand(&mut rng)); + + let t = r.square().modulo(&n); + let s = t.pow_mod_ref(&lambda, &n).unwrap().into(); + + (s, t) + }; + + let aux = paillier_zk::paillier_encryption_in_range::Aux { + s, + t, + rsa_modulo: n, + multiexp: None, + crt: None, + }; + + let aux_json = serde_json::to_vec_pretty(&aux).context("serialzie aux")?; + std::fs::write("./test-data/verifier_aux.json", aux_json).context("save aux")?; + } + + // Generate a bunch of paillier keys + generate_paillier_key( + &mut rng, + Some("./test-data/prover_decryption_key.json".as_ref()), + Some("./test-data/prover_encryption_key.json".as_ref()), + )?; + generate_paillier_key( + &mut rng, + None, // "someone's" secret decryption key remains unknown + Some("./test-data/someone_encryption_key0.json".as_ref()), + )?; + generate_paillier_key( + &mut rng, + None, // "someone's" secret decryption key remains unknown + Some("./test-data/someone_encryption_key1.json".as_ref()), + )?; + + Ok(()) +} + +fn generate_paillier_key( + rng: &mut impl rand_core::RngCore, + output_dk: Option<&std::path::Path>, + output_ek: Option<&std::path::Path>, +) -> anyhow::Result<()> { + // 1536 bits primes used for paillier key achieve 128 bits security + let p = generate_blum_prime(rng, 1536); + let q = generate_blum_prime(rng, 1536); + + let dk: fast_paillier::DecryptionKey = + fast_paillier::DecryptionKey::from_primes(p, q).context("generated p, q are invalid")?; + let ek = dk.encryption_key(); + + if let Some(path) = output_dk { + let dk_json = serde_json::to_vec_pretty(&dk).context("serialize decryption key")?; + std::fs::write(path, dk_json).context("save decryption key")?; + } + + if let Some(path) = output_ek { + let ek_json = serde_json::to_vec_pretty(&ek).context("serialize encryption key")?; + std::fs::write(path, ek_json).context("save encryption key")?; + } + + Ok(()) +} + +/// Note: Blum primes MUST NOT be used in real system +/// +/// Blum primes are faster to generate so we use them for the tests, however they do not meet +/// security requirements of the proofs. Safe primes MUST BE used intead of blum primes. +/// +/// Safe primes can be generated using [`fast_paillier::utils::generate_safe_prime`] +fn generate_blum_prime(rng: &mut impl rand_core::RngCore, bits_size: u32) -> Integer { + loop { + let mut n: Integer = + Integer::random_bits(bits_size, &mut fast_paillier::utils::external_rand(rng)).into(); + n.set_bit(bits_size - 1, true); + n.next_prime_mut(); + if n.mod_u(4) == 3 { + break n; + } + } +} diff --git a/paillier-zk/src/_doctest.rs b/paillier-zk/src/_doctest.rs new file mode 100644 index 00000000..322cd043 --- /dev/null +++ b/paillier-zk/src/_doctest.rs @@ -0,0 +1,10 @@ +/// Refer to `examples/pregenerate.rs` to see how data is pregenerated +#[macro_export] +macro_rules! load_pregenerated_data { + ($($name:ident: $type:ty),+$(,)?) => {$( + pub fn $name() -> $type { + const JSON: &str = include_str!(concat!("../test-data/", stringify!($name), ".json")); + serde_json::from_str(JSON).unwrap() + } + )+}; +} diff --git a/paillier-zk/src/common.rs b/paillier-zk/src/common.rs new file mode 100644 index 00000000..c4641f11 --- /dev/null +++ b/paillier-zk/src/common.rs @@ -0,0 +1,446 @@ +pub mod sqrt; + +use std::sync::Arc; + +use generic_ec::Scalar; +use rug::{Complete, Integer}; + +/// Auxiliary data known to both prover and verifier +#[cfg_attr( + feature = "__internal_doctest", + derive(serde::Serialize, serde::Deserialize) +)] +#[derive(Clone, Debug)] +pub struct Aux { + /// ring-pedersen parameter + pub s: Integer, + /// ring-pedersen parameter + pub t: Integer, + /// N^ in paper + pub rsa_modulo: Integer, + /// Precomuted table for computing `s^x t^y mod rsa_modulo` faster + /// + /// If absent, optimization is disabled. + #[cfg_attr(feature = "__internal_doctest", serde(skip))] + pub multiexp: Option>, + #[cfg_attr(feature = "__internal_doctest", serde(skip))] + pub crt: Option, +} + +impl Aux { + /// Returns `s^x t^y mod rsa_modulo` + pub fn combine(&self, x: &Integer, y: &Integer) -> Result { + if let Some(table) = &self.multiexp { + match table.prod_exp(x, y) { + Some(res) => return Ok(res), + None if cfg!(debug_assertions) => { + return Err(BadExponentReason::ExpSize { + exp_size: (x.significant_bits(), y.significant_bits()), + max_exp_size: table.max_exponents_size(), + } + .into()) + } + None => { + // When debug assertions are disabled, we fallback to naive exponentiation + } + } + } + + // Naive exponentiation when optimizations are not enabled + self.rsa_modulo.combine(&self.s, x, &self.t, y) + } + + /// Returns `x^e mod rsa_modulo` + pub fn pow_mod(&self, x: &Integer, e: &Integer) -> Result { + match &self.crt { + Some(crt) => { + let e = crt.prepare_exponent(e); + crt.exp(x, &e).ok_or_else(BadExponent::undefined) + } + None => Ok(x + .pow_mod_ref(e, &self.rsa_modulo) + .ok_or_else(BadExponent::undefined)? + .into()), + } + } + + /// Returns a stripped version of `Aux` that contains only public data which can be digested + /// via [`udigest::Digestable`] + pub fn digest_public_data(&self) -> impl udigest::Digestable { + let order = rug::integer::Order::Msf; + udigest::inline_struct!("paillier_zk.aux" { + s: udigest::Bytes(self.s.to_digits::(order)), + t: udigest::Bytes(self.t.to_digits::(order)), + rsa_modulo: udigest::Bytes(self.rsa_modulo.to_digits::(order)), + }) + } +} + +/// Error indicating that proof is invalid +#[derive(Debug, Clone, thiserror::Error)] +#[error("invalid proof")] +pub struct InvalidProof( + #[source] + #[from] + InvalidProofReason, +); + +/// Reason for failure. If the proof failes, you should only be interested in a +/// reason for debugging purposes +#[derive(Debug, PartialEq, Eq, Clone, Copy, thiserror::Error)] +pub enum InvalidProofReason { + /// One equality doesn't hold. Parameterized by equality index + #[error("equality check failed {0}")] + EqualityCheck(usize), + /// One range check doesn't hold. Parameterized by check index + #[error("range check failed {0}")] + RangeCheck(usize), + /// Encryption of supplied data failed when attempting to verify + #[error("encryption failed")] + Encryption, + #[error("paillier encryption failed")] + PaillierEnc, + #[error("paillier homomorphic op failed")] + PaillierOp, + /// Failed to evaluate powmod + #[error("powmod failed")] + ModPow, + /// Paillier-Blum modulus is prime + #[error("modulus is prime")] + ModulusIsPrime, + /// Paillier-Blum modulus is even + #[error("modulus is even")] + ModulusIsEven, + /// Proof's z value in n-th power does not equal commitment value + #[error("incorrect nth root")] + IncorrectNthRoot, + /// Proof's x value in 4-th power does not equal commitment value + #[error("incorrect 4th root")] + IncorrectFourthRoot, +} + +impl InvalidProof { + #[cfg(test)] + pub(crate) fn reason(&self) -> InvalidProofReason { + self.0 + } +} + +impl From for InvalidProof { + fn from(_err: BadExponent) -> Self { + InvalidProofReason::ModPow.into() + } +} + +impl From for InvalidProof { + fn from(_err: PaillierError) -> Self { + InvalidProof(InvalidProofReason::Encryption) + } +} + +/// Error indicating that encryption failed +#[derive(Clone, Copy, Debug, thiserror::Error)] +#[error("paillier encryption failed")] +pub struct PaillierError; + +pub trait IntegerExt: Sized { + /// Generate element in Zm*. Does so by trial. + fn gen_invertible(modulo: &Self, rng: &mut R) -> Self; + + /// Compute l^le * r^re modulo self + fn combine(&self, l: &Self, le: &Self, r: &Self, re: &Self) -> Result; + + /// Embed BigInt into chosen scalar type + fn to_scalar(&self) -> Scalar; + + /// Returns prime order of curve C + fn curve_order() -> Self; + + /// Generates a random integer in interval `[-range; range]` + fn from_rng_pm(range: &Self, rng: &mut R) -> Self; + + /// Checks whether `self` is in interval `[-range; range]` + fn is_in_pm(&self, range: &Self) -> bool; + + /// Returns `self smod n` + /// + /// For odd `n`, result is in `{-n/2, .., n/2}`. For even `n`, result is in + /// `{-n/2, .., n/2 - 1}` + fn signed_modulo(&self, n: &Self) -> Self; +} + +impl IntegerExt for Integer { + fn gen_invertible(modulo: &Integer, rng: &mut R) -> Self { + fast_paillier::utils::sample_in_mult_group(rng, modulo) + } + + fn combine(&self, l: &Self, le: &Self, r: &Self, re: &Self) -> Result { + let l_to_le: Integer = l + .pow_mod_ref(le, self) + .ok_or_else(BadExponent::undefined)? + .into(); + let r_to_re: Integer = r + .pow_mod_ref(re, self) + .ok_or_else(BadExponent::undefined)? + .into(); + Ok((l_to_le * r_to_re).modulo(self)) + } + + fn to_scalar(&self) -> Scalar { + let bytes_be = self.to_digits::(rug::integer::Order::Msf); + let s = Scalar::::from_be_bytes_mod_order(bytes_be); + if self.cmp0().is_ge() { + s + } else { + -s + } + } + + fn curve_order() -> Self { + let order_minus_one = -Scalar::::one(); + let i = Integer::from_digits(&order_minus_one.to_be_bytes(), rug::integer::Order::Msf); + i + 1 + } + + fn from_rng_pm(range: &Self, rng: &mut R) -> Self { + let mut rng = fast_paillier::utils::external_rand(rng); + let range_twice = range.clone() << 1u32; + range_twice.random_below(&mut rng) - range + } + + fn is_in_pm(&self, range: &Self) -> bool { + let minus_range = -range.clone(); + minus_range <= *self && self <= range + } + + fn signed_modulo(&self, n: &Self) -> Self { + let self_mod_n = self.modulo_ref(n).complete(); + let half_n = (n >> 1_u32).complete(); + if half_n.is_odd() && self_mod_n <= half_n || self_mod_n < half_n { + self_mod_n + } else { + self_mod_n - n + } + } +} + +/// Error indicating that computation cannot be evaluated because of bad exponent +/// +/// Returned by [`BigNumberExt::powmod`] and other functions that do exponentiation internally +#[derive(Clone, Copy, Debug, thiserror::Error)] +#[error(transparent)] +pub struct BadExponent(#[from] BadExponentReason); + +impl BadExponent { + /// Constructs an error that exponent is undefined + pub fn undefined() -> Self { + Self(BadExponentReason::Undefined) + } +} + +#[derive(Clone, Copy, Debug, thiserror::Error)] +enum BadExponentReason { + #[error("exponent is undefined")] + Undefined, + #[error("multiexp error: exponent size is too large (exponents size: {exp_size:?}, max exponent size: {max_exp_size:?})")] + ExpSize { + exp_size: (u32, u32), + max_exp_size: (usize, usize), + }, +} + +/// Returns `Err(err)` if `assertion` is false +pub fn fail_if(err: E, assertion: bool) -> Result<(), E> { + if assertion { + Ok(()) + } else { + Err(err) + } +} + +/// Returns `Err(err)` if `lhs != rhs` +pub fn fail_if_ne(err: E, lhs: T, rhs: T) -> Result<(), E> { + if lhs == rhs { + Ok(()) + } else { + Err(err) + } +} + +pub mod encoding { + /// Digests a rug integer + pub struct Integer; + impl udigest::DigestAs for Integer { + fn digest_as( + value: &rug::Integer, + encoder: udigest::encoding::EncodeValue, + ) { + let digits = value.to_digits::(rug::integer::Order::Msf); + encoder.encode_leaf_value(digits) + } + } + + /// Digests any encryption key + pub struct AnyEncryptionKey; + impl udigest::DigestAs<&dyn fast_paillier::AnyEncryptionKey> for AnyEncryptionKey { + fn digest_as( + value: &&dyn fast_paillier::AnyEncryptionKey, + encoder: udigest::encoding::EncodeValue, + ) { + Integer::digest_as(value.n(), encoder) + } + } +} + +/// A common logic shared across tests and doctests +#[cfg(test)] +pub mod test { + use rug::{Complete, Integer}; + + use super::IntegerExt; + + pub fn random_key(rng: &mut R) -> Option { + let p = generate_blum_prime(rng, 1024); + let q = generate_blum_prime(rng, 1024); + fast_paillier::DecryptionKey::from_primes(p, q).ok() + } + + pub fn aux(rng: &mut R) -> super::Aux { + let p = generate_blum_prime(rng, 1024); + let q = generate_blum_prime(rng, 1024); + let n = (&p * &q).complete(); + + let (s, t) = { + let phi_n = (p.clone() - 1u8) * (q.clone() - 1u8); + let r = Integer::gen_invertible(&n, rng); + let lambda = phi_n.random_below(&mut fast_paillier::utils::external_rand(rng)); + + let t = r.square().modulo(&n); + let s = t.pow_mod_ref(&lambda, &n).unwrap().into(); + + (s, t) + }; + + super::Aux { + s, + t, + rsa_modulo: n, + multiexp: None, + crt: None, + } + } + + pub fn generate_blum_prime(rng: &mut impl rand_core::RngCore, bits_size: u32) -> Integer { + loop { + let n = generate_prime(rng, bits_size); + if n.mod_u(4) == 3 { + break n; + } + } + } + + pub fn generate_prime(rng: &mut impl rand_core::RngCore, bits_size: u32) -> Integer { + let mut n: Integer = + Integer::random_bits(bits_size, &mut fast_paillier::utils::external_rand(rng)).into(); + n.set_bit(bits_size - 1, true); + n.next_prime_mut(); + n + } +} + +#[cfg(test)] +mod _test { + use rug::Integer; + + use super::IntegerExt; + + #[test] + fn to_scalar_encoding() { + type E = generic_ec::curves::Secp256k1; + + let bytes = [123u8, 231u8]; + let int = u16::from_be_bytes(bytes); + let bn = rug::Integer::from(int); + let scalar = bn.to_scalar(); + assert_eq!(scalar, generic_ec::Scalar::::from(int)); + + assert_eq!(bn.to_digits::(rug::integer::Order::Msf), &bytes); + + let curve_order = Integer::curve_order::(); + assert_eq!(curve_order.to_scalar(), generic_ec::Scalar::::zero()); + assert_eq!( + (curve_order - 1u8).to_scalar(), + -generic_ec::Scalar::::one() + ); + } + + #[test] + fn signed_modulo() { + let n = Integer::from(7); + + assert_eq!(Integer::from(0).signed_modulo(&n), 0); + assert_eq!(Integer::from(1).signed_modulo(&n), 1); + assert_eq!(Integer::from(2).signed_modulo(&n), 2); + assert_eq!(Integer::from(3).signed_modulo(&n), 3); + assert_eq!(Integer::from(4).signed_modulo(&n), -3); + assert_eq!(Integer::from(5).signed_modulo(&n), -2); + assert_eq!(Integer::from(6).signed_modulo(&n), -1); + assert_eq!(Integer::from(7).signed_modulo(&n), 0); + assert_eq!(Integer::from(8).signed_modulo(&n), 1); + + let n = Integer::from(4); + assert_eq!(Integer::from(0).signed_modulo(&n), 0); + assert_eq!(Integer::from(1).signed_modulo(&n), 1); + assert_eq!(Integer::from(2).signed_modulo(&n), -2); + assert_eq!(Integer::from(3).signed_modulo(&n), -1); + } + + #[test] + fn multiexp() { + let mut rng = rand_dev::DevRng::new(); + let mut aux = super::test::aux(&mut rng); + let table = std::sync::Arc::new( + crate::multiexp::MultiexpTable::build(&aux.s, &aux.t, 512, 448, aux.rsa_modulo.clone()) + .unwrap(), + ); + let (x_bits, y_bits) = table.max_exponents_size(); + aux.multiexp = Some(table); + + // Corner case: upper bound + let x_max = (Integer::ONE.clone() << x_bits) - 1; + let y_max = (Integer::ONE.clone() << y_bits) - 1; + let actual = aux.combine(&x_max, &y_max).unwrap(); + let expected = aux + .rsa_modulo + .combine(&aux.s, &x_max, &aux.t, &y_max) + .unwrap(); + assert_eq!(actual, expected); + + // Corner case: lower bound + let x_min = -x_max.clone(); + let y_min = -y_max.clone(); + let actual = aux.combine(&x_min, &y_min).unwrap(); + let expected = aux + .rsa_modulo + .combine(&aux.s, &x_min, &aux.t, &y_min) + .unwrap(); + assert_eq!(actual, expected); + + // Random integers within the range + let mut rng = fast_paillier::utils::external_rand(&mut rng); + for _ in 0..100 { + let x = (x_max.clone() + 1u8).random_below(&mut rng); + let y = (y_max.clone() + 1u8).random_below(&mut rng); + + let x = if rng.bits(1) == 1 { x } else { -x }; + let y = if rng.bits(1) == 1 { y } else { -y }; + + println!("x: {x}"); + println!("y: {y}"); + + let actual = aux.combine(&x, &y).unwrap(); + let expected = aux.rsa_modulo.combine(&aux.s, &x, &aux.t, &y).unwrap(); + assert_eq!(actual, expected); + } + } +} diff --git a/paillier-zk/src/common/sqrt.rs b/paillier-zk/src/common/sqrt.rs new file mode 100644 index 00000000..6b71a41b --- /dev/null +++ b/paillier-zk/src/common/sqrt.rs @@ -0,0 +1,69 @@ +use rand_core::RngCore; +use rug::{Complete, Integer}; + +/// Find principal square root in a Blum modulus quotient ring. +/// +/// Pre-requisites: +/// - x is a quadratic residue in Zn +/// - `n = pq`, p and q are Blum primes +/// +/// If these don't hold, the result is a bogus number in Zn +pub fn blum_sqrt(x: &Integer, p: &Integer, q: &Integer, n: &Integer) -> Integer { + // Exponent in pq Blum modulus to obtain the principal square root. + // Described in [Handbook of Applied cryptography, p. 75, Fact + // 2.160](https://cacr.uwaterloo.ca/hac/about/chap2.pdf) + let e = ((p - 1u8).complete() * (q - 1u8).complete() + 4) / 8; + + // e guaranteed to be non-negative by the prerequisite that p and q are blum primes + #[allow(clippy::expect_used)] + x.pow_mod_ref(&e, n) + .expect("e guaranteed to be non-negative") + .into() +} + +/// Find `(y' = (-1)^a w^b y, a, b)` such that y' is a quadratic residue in Zn. +/// +/// a and b are treated as false = 0, true = 1 +/// +/// Pre-requisites: +/// - `n = pq`, p and q are Blum primes +/// - `jacobi(w, n) = -1`, that is w is quadratic non-residue in Zn with jacobi +/// symbol of -1 +/// +/// If these don't hold, the y' might not exist. In this case, returns `None` +pub fn find_residue( + y: &Integer, + w: &Integer, + p: &Integer, + q: &Integer, + n: &Integer, +) -> Option<(bool, bool, Integer)> { + let jp = y.modulo_ref(p).complete().jacobi(p); + let jq = y.modulo_ref(q).complete().jacobi(q); + match (jp, jq) { + (1, 1) => return Some((false, false, y.clone())), + (-1, -1) => return Some((true, false, (n - y).complete())), + _ => (), + } + + let y = (y * w).complete().modulo(n); + let jp = y.modulo_ref(p).complete().jacobi(p); + let jq = y.modulo_ref(q).complete().jacobi(q); + match (jp, jq) { + (1, 1) => Some((false, true, y)), + (-1, -1) => Some((true, true, n - y)), + _ => None, + } +} + +/// Finds a element in Zn that has jacobi symbol of -1 +pub fn sample_neg_jacobi(n: &Integer, rng: &mut R) -> Integer { + loop { + let w = n + .clone() + .random_below(&mut fast_paillier::utils::external_rand(rng)); + if w.jacobi(n) == -1 { + break w; + } + } +} diff --git a/paillier-zk/src/curve.rs b/paillier-zk/src/curve.rs new file mode 100644 index 00000000..192accc5 --- /dev/null +++ b/paillier-zk/src/curve.rs @@ -0,0 +1,273 @@ +use subtle::{Choice, ConstantTimeEq}; + +#[derive(PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, Default)] +pub struct MillionRing(u64); + +impl From for MillionRing { + fn from(x: u64) -> Self { + MillionRing(x) + } +} + +const MODULO: u64 = 1_000_000_007; + +impl generic_ec::core::Additive for MillionRing { + fn add(a: &Self, b: &Self) -> Self { + ((a.0 + b.0) % MODULO).into() + } + + fn sub(a: &Self, b: &Self) -> Self { + ((a.0 + MODULO - b.0) % MODULO).into() + } + + fn negate(a: &Self) -> Self { + if a.0 == 0 { + 0u64.into() + } else { + (MODULO - a.0).into() + } + } +} + +impl From for MillionRing { + fn from(_: generic_ec::core::CurveGenerator) -> Self { + 2u64.into() + } +} + +impl generic_ec::core::Zero for MillionRing { + fn zero() -> Self { + 0u64.into() + } + + fn is_zero(x: &Self) -> Choice { + if x.0 == 0 { + 1.into() + } else { + 0.into() + } + } +} + +impl zeroize::Zeroize for MillionRing { + fn zeroize(&mut self) { + self.0 = 0u64 + } +} + +impl generic_ec::core::OnCurve for MillionRing { + #[inline] + fn is_on_curve(&self) -> Choice { + Choice::from(1) + } +} + +impl generic_ec::core::SmallFactor for MillionRing { + #[inline] + fn is_torsion_free(&self) -> Choice { + Choice::from(1) + } +} + +impl ConstantTimeEq for MillionRing { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + +impl subtle::ConditionallySelectable for MillionRing { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + u64::conditional_select(&a.0, &b.0, choice).into() + } +} + +impl generic_ec::core::CompressedEncoding for MillionRing { + type Bytes = [u8; 8]; + + fn to_bytes_compressed(&self) -> Self::Bytes { + self.0.to_le_bytes() + } +} + +impl generic_ec::core::UncompressedEncoding for MillionRing { + type Bytes = [u8; 8]; + + fn to_bytes_uncompressed(&self) -> Self::Bytes { + self.0.to_le_bytes() + } +} + +impl generic_ec::core::Decode for MillionRing { + fn decode(bytes: &[u8]) -> Option { + let x = u64::from_be_bytes(bytes.try_into().ok()?); + Some(MillionRing(x % MODULO)) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, Default)] +pub struct Scalar(u64); + +impl From for Scalar { + fn from(x: u64) -> Self { + Scalar(x % MODULO) + } +} + +impl From for u64 { + fn from(x: Scalar) -> Self { + x.0 + } +} + +impl generic_ec::core::Additive for Scalar { + fn add(a: &Self, b: &Self) -> Self { + ((a.0 + b.0) % MODULO).into() + } + + fn sub(a: &Self, b: &Self) -> Self { + ((a.0 + MODULO - b.0) % MODULO).into() + } + + fn negate(a: &Self) -> Self { + if a.0 == 0 { + 0u64.into() + } else { + (MODULO - a.0).into() + } + } +} + +impl generic_ec::core::Multiplicative for Scalar { + type Output = Self; + + fn mul(a: &Self, b: &Self) -> Self { + (a.0 * b.0 % MODULO).into() + } +} + +impl generic_ec::core::Multiplicative for Scalar { + type Output = MillionRing; + + fn mul(a: &Self, b: &MillionRing) -> MillionRing { + (a.0 * b.0 % MODULO).into() + } +} + +impl generic_ec::core::Multiplicative for Scalar { + type Output = MillionRing; + + fn mul(a: &Self, _: &generic_ec::core::CurveGenerator) -> MillionRing { + generic_ec::core::Multiplicative::mul( + a, + &MillionRing::from(generic_ec::core::CurveGenerator), + ) + } +} + +impl generic_ec::core::Invertible for Scalar { + fn invert(x: &Self) -> subtle::CtOption { + subtle::CtOption::new(*x, x.ct_eq(&1u64.into())) + } +} + +impl generic_ec::core::Zero for Scalar { + fn zero() -> Self { + 0u64.into() + } + + fn is_zero(x: &Self) -> Choice { + x.0.ct_eq(&0) + } +} + +impl generic_ec::core::One for Scalar { + fn one() -> Self { + 1u64.into() + } + + fn is_one(x: &Self) -> Choice { + x.0.ct_eq(&1) + } +} + +impl generic_ec::core::Samplable for Scalar { + fn random(rng: &mut R) -> Self { + rng.next_u64().into() + } +} + +impl zeroize::Zeroize for Scalar { + fn zeroize(&mut self) { + self.0 = 0 + } +} + +impl ConstantTimeEq for Scalar { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + +impl subtle::ConditionallySelectable for Scalar { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + u64::conditional_select(&a.0, &b.0, choice).into() + } +} + +impl generic_ec::core::IntegerEncoding for Scalar { + type Bytes = [u8; 8]; + + fn to_be_bytes(&self) -> Self::Bytes { + self.0.to_be_bytes() + } + + fn to_le_bytes(&self) -> Self::Bytes { + self.0.to_le_bytes() + } + + fn from_be_bytes_mod_order(bytes: &[u8]) -> Self { + use generic_ec::core::{Additive, Multiplicative}; + + let scalar_0x100 = Scalar::from(0x100); + bytes + .iter() + .map(|i| Scalar::from(u64::from(*i))) + .fold(Scalar::default(), |acc, i| { + Scalar::add(&Scalar::mul(&acc, &scalar_0x100), &i) + }) + } + + fn from_le_bytes_mod_order(bytes: &[u8]) -> Self { + use generic_ec::core::{Additive, Multiplicative}; + + let scalar_0x100 = Scalar::from(0x100); + bytes + .iter() + .rev() + .map(|i| Scalar::from(u64::from(*i))) + .fold(Scalar::default(), |acc, i| { + Scalar::add(&Scalar::mul(&acc, &scalar_0x100), &i) + }) + } + + fn from_be_bytes_exact(bytes: &Self::Bytes) -> Option { + Some(u64::from_be_bytes(*bytes).into()) + } + + fn from_le_bytes_exact(bytes: &Self::Bytes) -> Option { + Some(u64::from_le_bytes(*bytes).into()) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, Default)] +pub struct C; + +impl generic_ec::Curve for C { + const CURVE_NAME: &'static str = "test curve Z/1000000007"; + type Point = MillionRing; + type Scalar = Scalar; + type CompressedPointArray = [u8; 8]; + type UncompressedPointArray = [u8; 8]; + type ScalarArray = [u8; 8]; + type CoordinateArray = [u8; 8]; +} diff --git a/paillier-zk/src/group_element_vs_paillier_encryption_in_range.rs b/paillier-zk/src/group_element_vs_paillier_encryption_in_range.rs new file mode 100644 index 00000000..382900be --- /dev/null +++ b/paillier-zk/src/group_element_vs_paillier_encryption_in_range.rs @@ -0,0 +1,473 @@ +//! ZK-proof, called Пlog* or Rlog* in the CGGMP21 paper. +//! +//! ## Description +//! +//! A party P has a number `X = x G`, with G being a generator of +//! curve `E`. 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 bits. +//! +//! Given: +//! - `key0`, `pkey0` - pair of public and private keys in paillier cryptosystem +//! - Curve `E` +//! - `X = x G` and `C = key0.encrypt(x)` - data to obtain proof about +//! +//! Prove: +//! - `decrypt(C) = log X` +//! - `bitsize(x) <= l` +//! +//! Disclosing only: `key0`, `C`, `X` +//! +//! ## Example +//! +//! ```rust +//! use rug::{Integer, Complete}; +//! use generic_ec::{Point, curves::Secp256k1 as E}; +//! use paillier_zk::{group_element_vs_paillier_encryption_in_range as p, IntegerExt}; +//! # mod pregenerated { +//! # use super::*; +//! # paillier_zk::load_pregenerated_data!( +//! # verifier_aux: p::Aux, +//! # prover_decryption_key: fast_paillier::DecryptionKey, +//! # ); +//! # } +//! +//! # fn main() -> Result<(), Box> { +//! // Prover and verifier have a shared protocol state +//! let shared_state = "some shared state"; +//! let mut rng = rand_core::OsRng; +//! # let mut rng = rand_dev::DevRng::new(); +//! +//! // 0. Setup: prover and verifier share common Ring-Pedersen parameters: +//! +//! let aux: p::Aux = pregenerated::verifier_aux(); +//! let security = p::SecurityParams { +//! l: 1024, +//! epsilon: 300, +//! q: (Integer::ONE << 128_u32).complete(), +//! }; +//! +//! // 1. Setup: prover prepares the paillier keys +//! +//! let private_key: fast_paillier::DecryptionKey = +//! pregenerated::prover_decryption_key(); +//! let key0 = private_key.encryption_key(); +//! +//! // 2. Setup: prover has some plaintext `x`, encrypts it and obtains `C`, and computes `X` +//! +//! let x = Integer::from_rng_pm(&(Integer::ONE << security.l).complete(), &mut rng); +//! let (C, nonce) = key0.encrypt_with_random(&mut rng, &x)?; +//! let X = Point::::generator() * x.to_scalar(); +//! +//! // 3. Prover computes a non-interactive proof that plaintext is at most `l` bits: +//! +//! let data = p::Data { +//! key0, +//! c: &C, +//! x: &X, +//! b: &Point::::generator().into(), +//! }; +//! let (commitment, proof) = +//! p::non_interactive::prove::( +//! &shared_state, +//! &aux, +//! data, +//! p::PrivateData { x: &x, nonce: &nonce }, +//! &security, +//! &mut rng, +//! )?; +//! +//! // 4. Prover sends this data to verifier +//! +//! # fn send(_: &p::Data, _: &p::Commitment, _: &p::Proof) { } +//! send(&data, &commitment, &proof); +//! +//! // 5. Verifier receives the data and the proof and verifies it +//! +//! # let recv = || (data, commitment, proof); +//! let (data, commitment, proof) = recv(); +//! p::non_interactive::verify::( +//! &shared_state, +//! &aux, +//! data, +//! &commitment, +//! &security, +//! &proof, +//! )?; +//! # Ok(()) } +//! ``` +//! +//! If the verification succeeded, verifier can continue communication with prover + +use fast_paillier::{AnyEncryptionKey, Ciphertext, Nonce}; +use generic_ec::{Curve, Point}; +use rug::Integer; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +pub use crate::common::Aux; + +/// 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, udigest::Digestable)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct SecurityParams { + /// l in paper, bit size of +-plaintext + pub l: usize, + /// Epsilon in paper, slackness parameter + pub epsilon: usize, + /// q in paper. Security parameter for challenge + #[udigest(as = crate::common::encoding::Integer)] + pub q: Integer, +} + +/// Public data that both parties know +#[derive(Debug, Clone, Copy, udigest::Digestable)] +#[udigest(bound = "")] +pub struct Data<'a, C: Curve> { + /// N0 in paper, public key that C was encrypted on + #[udigest(as = crate::common::encoding::AnyEncryptionKey)] + pub key0: &'a dyn AnyEncryptionKey, + /// C in paper, logarithm of X encrypted on N0 + #[udigest(as = &crate::common::encoding::Integer)] + pub c: &'a Ciphertext, + /// A basepoint, generator in group + pub b: &'a Point, + /// X in paper, exponent of plaintext of C + pub x: &'a Point, +} + +/// Private data of prover +#[derive(Clone, Copy)] +pub struct PrivateData<'a> { + /// x in paper, logarithm of X and plaintext of C + pub x: &'a Integer, + /// rho in paper, nonce in encryption x -> C + pub nonce: &'a Nonce, +} + +/// Prover's first message, obtained by [`interactive::commit`] +#[derive(Debug, Clone, udigest::Digestable)] +#[udigest(bound = "")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct Commitment { + #[udigest(as = crate::common::encoding::Integer)] + pub s: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub a: Ciphertext, + pub y: Point, + #[udigest(as = crate::common::encoding::Integer)] + pub d: Integer, +} + +/// Prover's data accompanying the commitment. Kept as state between rounds in +/// the interactive protocol. +#[derive(Clone)] +pub struct PrivateCommitment { + pub alpha: Integer, + pub mu: Integer, + pub r: Nonce, + pub gamma: Integer, +} + +/// Verifier's challenge to prover. Can be obtained deterministically by +/// [`non_interactive::challenge`] or randomly by [`interactive::challenge`] +pub type Challenge = Integer; + +/// The ZK proof. Computed by [`interactive::prove`] or +/// [`non_interactive::prove`] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Proof { + pub z1: Integer, + pub z2: Integer, + pub z3: Integer, +} + +/// 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; + use rand_core::RngCore; + use rug::{Complete, Integer}; + + use crate::common::{fail_if, fail_if_ne, IntegerExt, InvalidProofReason}; + use crate::{Error, InvalidProof}; + + use super::{ + Aux, Challenge, Commitment, Data, PrivateCommitment, PrivateData, Proof, SecurityParams, + }; + + /// Create random commitment + pub fn commit( + aux: &Aux, + data: Data, + pdata: PrivateData, + security: &SecurityParams, + mut rng: R, + ) -> Result<(Commitment, PrivateCommitment), Error> { + let two_to_l_e = (Integer::ONE << (security.l + security.epsilon)).complete(); + let hat_n_at_two_to_l = &aux.rsa_modulo * (Integer::ONE << security.l).complete(); + let hat_n_at_two_to_l_e = (&aux.rsa_modulo * &two_to_l_e).complete(); + + let alpha = Integer::from_rng_pm(&two_to_l_e, &mut rng); + let mu = Integer::from_rng_pm(&hat_n_at_two_to_l, &mut rng); + let r = Integer::gen_invertible(data.key0.n(), &mut rng); + let gamma = Integer::from_rng_pm(&hat_n_at_two_to_l_e, &mut rng); + + let commitment = Commitment { + s: aux.combine(pdata.x, &mu)?, + a: data.key0.encrypt_with(&alpha, &r)?, + y: data.b * alpha.to_scalar(), + d: aux.combine(&alpha, &gamma)?, + }; + let private_commitment = PrivateCommitment { + alpha, + mu, + r, + gamma, + }; + Ok((commitment, private_commitment)) + } + + /// Compute proof for given data and prior protocol values + pub fn prove( + data: Data, + pdata: PrivateData, + pcomm: &PrivateCommitment, + challenge: &Challenge, + ) -> Result { + Ok(Proof { + z1: (&pcomm.alpha + challenge * pdata.x).complete(), + z2: data + .key0 + .n() + .combine(&pcomm.r, Integer::ONE, pdata.nonce, challenge)?, + z3: (&pcomm.gamma + challenge * &pcomm.mu).complete(), + }) + } + + /// Verify the proof + pub fn verify( + aux: &Aux, + data: Data, + commitment: &Commitment, + security: &SecurityParams, + challenge: &Challenge, + proof: &Proof, + ) -> Result<(), InvalidProof> { + { + let lhs = data + .key0 + .encrypt_with(&proof.z1, &proof.z2) + .map_err(|_| InvalidProofReason::PaillierEnc)?; + let rhs = { + let e_at_c = data + .key0 + .omul(challenge, data.c) + .map_err(|_| InvalidProofReason::PaillierOp)?; + data.key0 + .oadd(&commitment.a, &e_at_c) + .map_err(|_| InvalidProofReason::PaillierOp)? + }; + fail_if_ne(InvalidProofReason::EqualityCheck(1), lhs, rhs)?; + } + { + let lhs = data.b * proof.z1.to_scalar(); + let rhs = commitment.y + data.x * challenge.to_scalar(); + fail_if_ne(InvalidProofReason::EqualityCheck(2), lhs, rhs)?; + } + { + let lhs = aux.combine(&proof.z1, &proof.z3)?; + let s_to_e = aux.pow_mod(&commitment.s, challenge)?; + let rhs = (&commitment.d * s_to_e).modulo(&aux.rsa_modulo); + fail_if_ne(InvalidProofReason::EqualityCheck(3), lhs, rhs)?; + } + fail_if( + InvalidProofReason::RangeCheck(4), + proof + .z1 + .is_in_pm(&(Integer::ONE << (security.l + security.epsilon)).complete()), + )?; + + Ok(()) + } + + /// Generate random challenge + /// + /// `data` parameter is used to generate challenge in correct range + pub fn challenge(security: &SecurityParams, rng: &mut R) -> Integer + where + R: RngCore, + { + Integer::from_rng_pm(&security.q, rng) + } +} + +/// The non-interactive version of proof. Completed in one round, for example +/// see the documentation of parent module. +pub mod non_interactive { + use digest::Digest; + use generic_ec::Curve; + use rand_core::RngCore; + + use crate::{Error, InvalidProof}; + + use super::{Aux, Challenge, Commitment, Data, PrivateData, Proof, SecurityParams}; + + /// Compute proof for the given data, producing random commitment and + /// deriving determenistic challenge. + /// + /// Obtained from the above interactive proof via Fiat-Shamir heuristic. + pub fn prove( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + pdata: PrivateData, + security: &SecurityParams, + rng: &mut impl RngCore, + ) -> Result<(Commitment, Proof), Error> { + let (comm, pcomm) = super::interactive::commit(aux, data, pdata, security, rng)?; + let challenge = challenge::(shared_state, aux, data, &comm, security); + let proof = super::interactive::prove(data, pdata, &pcomm, &challenge)?; + Ok((comm, proof)) + } + + /// Verify the proof, deriving challenge independently from same data + pub fn verify( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + commitment: &Commitment, + security: &SecurityParams, + proof: &Proof, + ) -> Result<(), InvalidProof> { + let challenge = challenge::(shared_state, aux, data, commitment, security); + super::interactive::verify(aux, data, commitment, security, &challenge, proof) + } + + /// Internal function for deriving challenge from protocol values + /// deterministically + pub fn challenge( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + commitment: &Commitment, + security: &SecurityParams, + ) -> Challenge { + let tag = "paillier-zk.group_element_vs_paillier_encryption_in_range.ni-challenge"; + let aux = aux.digest_public_data(); + let seed = udigest::inline_struct!(tag { + shared_state, + aux, + security, + data, + commitment, + }); + let mut rng = rand_hash::HashRng::::from_seed(seed); + super::interactive::challenge(security, &mut rng) + } +} + +#[cfg(test)] +mod test { + use generic_ec::{Curve, Point, Scalar}; + use rug::{Complete, Integer}; + use sha2::Digest; + + use crate::common::test::random_key; + use crate::common::{IntegerExt, InvalidProofReason}; + + fn run( + mut rng: R, + security: super::SecurityParams, + plaintext: Integer, + ) -> Result<(), crate::common::InvalidProof> { + let private_key0 = random_key(&mut rng).unwrap(); + let key0 = private_key0.encryption_key().clone(); + + let (ciphertext, nonce) = key0.encrypt_with_random(&mut rng, &plaintext).unwrap(); + let b = Point::::generator() * Scalar::random(&mut rng); + let x = b * plaintext.to_scalar(); + + let data = super::Data { + key0: &key0, + c: &ciphertext, + x: &x, + b: &b, + }; + let pdata = super::PrivateData { + x: &plaintext, + nonce: &nonce, + }; + + let aux = crate::common::test::aux(&mut rng); + + let shared_state = "shared state"; + + let (commitment, proof) = super::non_interactive::prove::( + &shared_state, + &aux, + data, + pdata, + &security, + &mut rng, + ) + .unwrap(); + + super::non_interactive::verify::( + &shared_state, + &aux, + data, + &commitment, + &security, + &proof, + ) + } + + fn passing_test() { + let mut rng = rand_dev::DevRng::new(); + let security = super::SecurityParams { + l: 1024, + epsilon: 300, + q: (Integer::ONE << 128_u32).complete(), + }; + let plaintext = Integer::from_rng_pm(&(Integer::ONE << security.l).complete(), &mut rng); + run::<_, C, D>(rng, security, plaintext).expect("proof failed"); + } + + fn failing_test() { + let rng = rand_dev::DevRng::new(); + let security = super::SecurityParams { + l: 1024, + epsilon: 300, + q: (Integer::ONE << 128_u32).complete(), + }; + let plaintext = (Integer::ONE << (security.l + security.epsilon + 1)).complete(); + let r = run::<_, C, D>(rng, security, plaintext).expect_err("proof should not pass"); + match r.reason() { + InvalidProofReason::RangeCheck(_) => (), + e => panic!("proof should not fail with: {e:?}"), + } + } + + #[test] + fn passing_p256() { + passing_test::() + } + #[test] + fn failing_p256() { + failing_test::() + } + + #[test] + fn passing_million() { + passing_test::() + } + #[test] + fn failing_million() { + failing_test::() + } +} diff --git a/paillier-zk/src/lib.rs b/paillier-zk/src/lib.rs new file mode 100644 index 00000000..95ddc7b3 --- /dev/null +++ b/paillier-zk/src/lib.rs @@ -0,0 +1,72 @@ +#![doc = include_str!("../README.md")] +#![deny(clippy::disallowed_methods)] +#![cfg_attr( + not(test), + deny(clippy::panic, clippy::unwrap_used, clippy::expect_used) +)] + +use thiserror::Error; + +mod common; +pub mod group_element_vs_paillier_encryption_in_range; +pub mod multiexp; +pub mod no_small_factor; +pub mod paillier_affine_operation_in_range; +pub mod paillier_blum_modulus; +pub mod paillier_encryption_in_range; + +#[cfg(test)] +mod curve; + +#[cfg(all(doctest, not(feature = "__internal_doctest")))] +compile_error!("doctest require that `__internal_doctest` feature is turned on"); + +#[cfg(feature = "__internal_doctest")] +pub mod _doctest; + +use common::InvalidProofReason; +pub use common::{BadExponent, IntegerExt, InvalidProof, PaillierError}; +pub use {fast_paillier, rug, rug::Integer}; + +/// Library general error type +#[derive(Debug, Error)] +#[error(transparent)] +pub struct Error(#[from] ErrorReason); + +#[derive(Debug, Error)] +enum ErrorReason { + #[error("couldn't evaluate modpow")] + ModPow( + #[source] + #[from] + BadExponent, + ), + #[error("couldn't find residue")] + FindResidue, + #[error("couldn't encrypt a message")] + Encryption, + #[error("can't find multiplicative inverse")] + Invert, + #[error("paillier error")] + Paillier(#[source] fast_paillier::Error), + #[error("bug: vec has unexpected length")] + Length, +} + +impl From for Error { + fn from(err: BadExponent) -> Self { + Error(ErrorReason::ModPow(err)) + } +} + +impl From for Error { + fn from(_err: PaillierError) -> Self { + Error(ErrorReason::Encryption) + } +} + +impl From for Error { + fn from(err: fast_paillier::Error) -> Self { + Self(ErrorReason::Paillier(err)) + } +} diff --git a/paillier-zk/src/multiexp.rs b/paillier-zk/src/multiexp.rs new file mode 100644 index 00000000..4fc8c565 --- /dev/null +++ b/paillier-zk/src/multiexp.rs @@ -0,0 +1,228 @@ +//! Optimized multiexponentiation with precomputations +//! +//! Many ZK proofs often require computing `s^x t^y mod N` with s, t, and N being known in advance. +//! This module provides [`MultiexpTable`] that can compute multiexponent faster. + +#![allow(non_snake_case)] + +use rug::{Complete, Integer}; + +/// Precomputed table for performing faster multiexponentiation +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MultiexpTable { + s: Vec, + ell_x: Integer, + s_to_ell_x: Integer, + t: Vec, + ell_y: Integer, + t_to_ell_y: Integer, + N: Integer, +} + +impl MultiexpTable { + /// Builds a multiexponentiation table to perform `s^x t^y mod N` faster + /// where `x` and `y` are up to `x_bits` and `y_bits` + /// + /// Returns `None` is `s` or `t` are non-positive or if any of them are not co-prime to `N` or + /// if `N` is less than 2. + pub fn build(s: &Integer, t: &Integer, x_bits: u32, y_bits: u32, N: Integer) -> Option { + if s.cmp0().is_le() + || t.cmp0().is_le() + || N <= *Integer::ONE + || s.gcd_ref(&N).complete() != *Integer::ONE + || t.gcd_ref(&N).complete() != *Integer::ONE + { + return None; + } + let k_x = x_bits / 8 + 1; + let k_y = y_bits / 8 + 1; + let mut s_table = Vec::with_capacity(k_x.try_into().ok()?); + let mut t_table = Vec::with_capacity(k_y.try_into().ok()?); + + let B: u32 = 256; + for i in 0..k_x { + let B_to_i = Integer::u_pow_u(B, i).complete(); + s_table.push(s.clone().pow_mod(&B_to_i, &N).ok()?); + } + for i in 0..k_y { + let B_to_i = Integer::u_pow_u(B, i).complete(); + t_table.push(t.clone().pow_mod(&B_to_i, &N).ok()?); + } + + // smallest negative value possible for `x` + let ell_x = -(Integer::ONE.clone() << (k_x * 8)) + 1; + let s_to_ell_x = s.pow_mod_ref(&ell_x, &N)?.into(); + // smallest negative value possible for `y` + let ell_y = -(Integer::ONE.clone() << (k_y * 8)) + 1; + let t_to_ell_y = t.pow_mod_ref(&ell_y, &N)?.into(); + + Some(Self { + s: s_table, + ell_x, + s_to_ell_x, + t: t_table, + ell_y, + t_to_ell_y, + N, + }) + } + + /// Calculates `s^x t^y mod N` + /// + /// Returns `None` if either `x` or `y` do not fit into `x_bits` or `y_bits` provided in [`MultiexpTable::build`]. + pub fn prod_exp(&self, x: &Integer, y: &Integer) -> Option { + let order = rug::integer::Order::Lsf; + + let x_is_neg = x.cmp0().is_lt(); + // `x_digits` correspond to digits of `x` is it's non-negative, and `x - ell_x` otherwise + let x_digits = if !x_is_neg { + x.to_digits::(order) + } else { + let x = (x - &self.ell_x).complete(); + if x.cmp0().is_lt() { + // `x` is less than lower bound + return None; + } + x.to_digits::(order) + }; + + let y_is_neg = y.cmp0().is_lt(); + // `y_digits` correspond to digits of `y` is it's non-negative, and `y - ell_y` otherwise + let y_digits = if !y_is_neg { + y.to_digits::(order) + } else { + let y = (y - &self.ell_y).complete(); + if y.cmp0().is_lt() { + // `y` is less than lower bound + return None; + } + y.to_digits::(order) + }; + + if x_digits.len() > self.s.len() || y_digits.len() > self.t.len() { + // `x` or `y` are higher than upper bound + return None; + } + + let mut digits_table = [(); 255].map(|_| None); + build_digits_table(&mut digits_table, &self.s, &x_digits, &self.N); + build_digits_table(&mut digits_table, &self.t, &y_digits, &self.N); + + let mut res = Integer::ONE.clone(); + let mut acc = Integer::ONE.clone(); + for d in digits_table.iter().rev() { + if let Some(d) = d { + acc = (acc * d) % &self.N; + } + res = (res * &acc) % &self.N; + } + + if x_is_neg { + res = (res * &self.s_to_ell_x) % &self.N; + } + if y_is_neg { + res = (res * &self.t_to_ell_y) % &self.N; + } + + Some(res) + } + + /// Returns max size of exponents (in bits) that can be computed + /// + /// Max exponent size is guaranteed to be equal or greater than `x_bits` and `y_bits` + /// provided in [MultiexpTable::build] + pub fn max_exponents_size(&self) -> (usize, usize) { + (self.s.len() * 8, self.t.len() * 8) + } + + /// Estimates size of the table in RAM in bytes + pub fn size_in_bytes(&self) -> usize { + let Self { + s, + ell_x, + s_to_ell_x, + t, + ell_y, + t_to_ell_y, + N, + } = self; + + // A few bytes to encode length of Vec `s` and `t` + let vec_len = 2 * (usize::BITS as usize / 8); + // And a few bytes more to encode length of each integer + let int_len = (5 + s.len() + t.len()) * (usize::BITS as usize / 8); + + type Limb = u32; + let s: usize = s.iter().map(|s_i| s_i.significant_digits::()).sum(); + let ell_x = ell_x.significant_digits::(); + let s_to_ell_x = s_to_ell_x.significant_digits::(); + let t: usize = t.iter().map(|t_i| t_i.significant_digits::()).sum(); + let ell_y = ell_y.significant_digits::(); + let t_to_ell_y = t_to_ell_y.significant_digits::(); + let N = N.significant_digits::(); + + let limbs_bytes = + (Limb::BITS as usize / 8) * (s + ell_x + s_to_ell_x + t + ell_y + t_to_ell_y + N); + + vec_len + int_len + limbs_bytes + } +} + +fn build_digits_table( + table: &mut [Option; 255], + base: &[Integer], + digits: &[u8], + N: &Integer, +) { + for (i, digit) in digits.iter().copied().enumerate() { + if digit != 0 { + match &mut table[usize::from(digit - 1)] { + Some(out) => { + *out *= &base[i]; + *out %= N; + } + out @ None => *out = Some(base[i].clone()), + } + } + } +} + +#[cfg(test)] +mod test { + use rug::Integer; + + use super::MultiexpTable; + + #[test] + fn multiexp_works() { + let N = Integer::from(100000); + let s = Integer::from(3); + let t = Integer::from(7); + + let x_bits = 48; + let y_bits = 32; + + let table = MultiexpTable::build(&s, &t, x_bits, y_bits, N.clone()).unwrap(); + + let mut rng = rug::rand::RandState::new_mersenne_twister(); + + for _ in 0..100 { + let mut x = Integer::from(Integer::random_bits(x_bits, &mut rng)); + if rng.bits(1) == 1 { + x = -x + } + + let mut y = Integer::from(Integer::random_bits(y_bits, &mut rng)); + if rng.bits(1) == 1 { + y = -y + } + println!("x={x} y={y}"); + + let actual = table.prod_exp(&x, &y).unwrap(); + let expected = + (s.clone().pow_mod(&x, &N).unwrap() * t.clone().pow_mod(&y, &N).unwrap()) % &N; + assert_eq!(actual, expected); + } + } +} diff --git a/paillier-zk/src/no_small_factor.rs b/paillier-zk/src/no_small_factor.rs new file mode 100644 index 00000000..ff32dc3d --- /dev/null +++ b/paillier-zk/src/no_small_factor.rs @@ -0,0 +1,470 @@ +//! ZK-proof for factoring of a RSA modulus. Called Пfac or Rfac in the CGGMP21 +//! paper. +//! +//! ## Description +//! +//! A party P has a modulus `N = pq`. P wants to prove to a verifier V that p +//! and q are sufficiently large without disclosing p or q, with p and q each no +//! larger than `sqrt(N) * 2^l`, or equivalently no smaller than `sqrt(N) / +//! 2^l` +//! +//! ## Example +//! +//! ```rust +//! use rug::{Integer, Complete}; +//! use paillier_zk::no_small_factor::non_interactive as p; +//! # mod pregenerated { +//! # use super::*; +//! # paillier_zk::load_pregenerated_data!( +//! # verifier_aux: p::Aux, +//! # ); +//! # } +//! +//! # fn main() -> Result<(), Box> { +//! let shared_state = "some shared state"; +//! let mut rng = rand_core::OsRng; +//! # let mut rng = rand_dev::DevRng::new(); +//! +//! // 0. Setup: prover and verifier share common Ring-Pedersen parameters, and +//! // agree on the level of security +//! +//! let aux: p::Aux = pregenerated::verifier_aux(); +//! let security = p::SecurityParams { +//! l: 4, +//! epsilon: 128, +//! q: (Integer::ONE << 128_u32).complete(), +//! }; +//! +//! // 1. Prover prepares the data to obtain proof about +//! +//! let p = fast_paillier::utils::generate_safe_prime(&mut rng, 256); +//! let q = fast_paillier::utils::generate_safe_prime(&mut rng, 256); +//! let n = (&p * &q).complete(); +//! let n_root = n.sqrt_ref().complete(); +//! let data = p::Data { +//! n: &n, +//! n_root: &n_root, +//! }; +//! +//! // 2. Prover computes a non-interactive proof that both factors are large enough +//! +//! let proof = p::prove::( +//! &shared_state, +//! &aux, +//! data, +//! p::PrivateData { p: &p, q: &q }, +//! &security, +//! &mut rng, +//! )?; +//! +//! // 4. Prover sends this data to verifier +//! +//! # fn send(_: &Integer, _: &p::Proof) { } +//! send(data.n, &proof); +//! +//! // 5. Verifier receives the data and the proof and verifies it +//! +//! # let recv = || (data.n, proof); +//! let (n, proof) = recv(); +//! let n_root = n.sqrt_ref().complete();; +//! let data = p::Data { +//! n: &n, +//! n_root: &n_root, +//! }; +//! p::verify::(&shared_state, &aux, data, &security, &proof)?; +//! # Ok(()) } +//! ``` +//! +//! If the verification succeeded, verifier can continue communication with prover + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use rug::Integer; + +pub use crate::common::{Aux, InvalidProof}; + +/// 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, udigest::Digestable)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct SecurityParams { + /// l in paper, security parameter for bit size of plaintext: it needs to + /// differ from sqrt(n) not more than by 2^l + pub l: usize, + /// Epsilon in paper, slackness parameter + pub epsilon: usize, + /// q in paper. Security parameter for challenge + #[udigest(as = crate::common::encoding::Integer)] + pub q: Integer, +} + +/// Public data that both parties know +#[derive(Debug, Clone, Copy, udigest::Digestable)] +pub struct Data<'a> { + /// N0 - rsa modulus + #[udigest(as = &crate::common::encoding::Integer)] + pub n: &'a Integer, + /// A number close to square root of n + #[udigest(as = &crate::common::encoding::Integer)] + pub n_root: &'a Integer, +} + +/// Private data of prover +#[derive(Debug, Clone, Copy)] +pub struct PrivateData<'a> { + pub p: &'a Integer, + pub q: &'a Integer, +} + +/// Prover's data accompanying the commitment. Kept as state between rounds in +/// the interactive protocol. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PrivateCommitment { + pub alpha: Integer, + pub beta: Integer, + pub mu: Integer, + pub nu: Integer, + pub r: Integer, + pub x: Integer, + pub y: Integer, +} + +/// Prover's first message, obtained by [`interactive::commit`] +#[derive(Debug, Clone, udigest::Digestable)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Commitment { + #[udigest(as = crate::common::encoding::Integer)] + pub p: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub q: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub a: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub b: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub t: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub sigma: Integer, +} + +/// Verifier's challenge to prover. Can be obtained deterministically by +/// [`non_interactive::challenge`] or randomly by [`interactive::challenge`] +pub type Challenge = Integer; + +/// The ZK proof, computed by [`interactive::prove`] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Proof { + pub z1: Integer, + pub z2: Integer, + pub w1: Integer, + pub w2: Integer, + pub v: Integer, +} + +/// Interactive version of the proof +pub mod interactive { + use rand_core::RngCore; + use rug::{Complete, Integer}; + + use crate::{ + common::{fail_if, fail_if_ne, IntegerExt, InvalidProofReason}, + Error, + }; + + use super::{ + Aux, Challenge, Commitment, Data, InvalidProof, PrivateCommitment, PrivateData, Proof, + SecurityParams, + }; + + /// Create random commitment + pub fn commit( + aux: &Aux, + data: Data, + pdata: PrivateData, + security: &SecurityParams, + mut rng: R, + ) -> Result<(Commitment, PrivateCommitment), Error> { + let two_to_l = (Integer::ONE << security.l).complete(); + let two_to_l_plus_e = (Integer::ONE << (security.l + security.epsilon)).complete(); + let n_root_modulo = (&two_to_l_plus_e * data.n_root).complete(); + let l_n_circ_modulo = (&two_to_l * &aux.rsa_modulo).complete(); + let l_e_n_circ_modulo = (&two_to_l_plus_e * &aux.rsa_modulo).complete(); + let n_n_circ = (&aux.rsa_modulo * data.n).complete(); + + let alpha = Integer::from_rng_pm(&n_root_modulo, &mut rng); + let beta = Integer::from_rng_pm(&n_root_modulo, &mut rng); + let mu = Integer::from_rng_pm(&l_n_circ_modulo, &mut rng); + let nu = Integer::from_rng_pm(&l_n_circ_modulo, &mut rng); + let sigma = Integer::from_rng_pm(&(&two_to_l * &n_n_circ).complete(), &mut rng); + let r = Integer::from_rng_pm(&(&two_to_l_plus_e * &n_n_circ).complete(), &mut rng); + let x = Integer::from_rng_pm(&l_e_n_circ_modulo, &mut rng); + let y = Integer::from_rng_pm(&l_e_n_circ_modulo, &mut rng); + + let p = aux.combine(pdata.p, &mu)?; + let q = aux.combine(pdata.q, &nu)?; + let a = aux.combine(&alpha, &x)?; + let b = aux.combine(&beta, &y)?; + let t = aux.rsa_modulo.combine(&q, &alpha, &aux.t, &r)?; + + let commitment = Commitment { + p, + q, + a, + b, + t, + sigma, + }; + let private_commitment = PrivateCommitment { + alpha, + beta, + mu, + nu, + r, + x, + y, + }; + Ok((commitment, private_commitment)) + } + + /// Generate random challenge + /// + /// `security` parameter is used to generate challenge in correct range + pub fn challenge(security: &SecurityParams, rng: &mut R) -> Challenge { + Integer::from_rng_pm(&security.q, rng) + } + + /// Compute proof for given data and prior protocol values + pub fn prove( + pdata: PrivateData, + comm: &Commitment, + pcomm: &PrivateCommitment, + challenge: &Challenge, + ) -> Result { + let sigma_circ = (&comm.sigma - &pcomm.nu * pdata.p).complete(); + + Ok(Proof { + z1: (&pcomm.alpha + challenge * pdata.p).complete(), + z2: (&pcomm.beta + challenge * pdata.q).complete(), + w1: (&pcomm.x + challenge * &pcomm.mu).complete(), + w2: (&pcomm.y + challenge * &pcomm.nu).complete(), + v: &pcomm.r + challenge * sigma_circ, + }) + } + + /// Verify the proof + pub fn verify( + aux: &Aux, + data: Data, + commitment: &Commitment, + security: &SecurityParams, + challenge: &Challenge, + proof: &Proof, + ) -> Result<(), InvalidProof> { + // check 1 + { + let lhs = aux.combine(&proof.z1, &proof.w1)?; + let p_to_e = aux.pow_mod(&commitment.p, challenge)?; + let rhs = (&commitment.a * p_to_e).modulo(&aux.rsa_modulo); + fail_if_ne(InvalidProofReason::EqualityCheck(1), lhs, rhs)?; + } + // check 2 + { + let lhs = aux.combine(&proof.z2, &proof.w2)?; + let q_to_e = aux.pow_mod(&commitment.q, challenge)?; + let rhs = (&commitment.b * q_to_e).modulo(&aux.rsa_modulo); + fail_if_ne(InvalidProofReason::EqualityCheck(2), lhs, rhs)?; + } + // check 3 + { + let r = aux.combine(data.n, &commitment.sigma)?; + let q_to_z1 = aux.pow_mod(&commitment.q, &proof.z1)?; + let t_to_v = aux.pow_mod(&aux.t, &proof.v)?; + let lhs = (q_to_z1 * t_to_v).modulo(&aux.rsa_modulo); + let rhs = aux + .rsa_modulo + .combine(&commitment.t, Integer::ONE, &r, challenge)?; + fail_if_ne(InvalidProofReason::EqualityCheck(3), lhs, rhs)?; + } + let range = (Integer::from(1) << (security.l + security.epsilon)) * data.n_root; + // range check for z1 + fail_if(InvalidProofReason::RangeCheck(1), proof.z1.is_in_pm(&range))?; + // range check for z2 + fail_if(InvalidProofReason::RangeCheck(2), proof.z2.is_in_pm(&range))?; + + Ok(()) + } +} + +/// Non-interactive version of the proof +pub mod non_interactive { + use digest::Digest; + + pub use crate::{Error, InvalidProof}; + + pub use super::{Aux, Challenge, Data, PrivateData, SecurityParams}; + + /// The ZK proof, computed by [`prove`] + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct Proof { + commitment: super::Commitment, + proof: super::Proof, + } + + /// Compute proof for the given data, producing random commitment and + /// deriving determenistic challenge. + /// + /// Obtained from the above interactive proof via Fiat-Shamir heuristic. + pub fn prove( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + pdata: PrivateData, + security: &SecurityParams, + rng: &mut impl rand_core::RngCore, + ) -> Result { + let (commitment, pcomm) = super::interactive::commit(aux, data, pdata, security, rng)?; + let challenge = challenge::(shared_state, aux, data, &commitment, security); + let proof = super::interactive::prove(pdata, &commitment, &pcomm, &challenge)?; + Ok(Proof { commitment, proof }) + } + + /// Deterministically compute challenge based on prior known values in protocol + pub fn challenge( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + commitment: &super::Commitment, + security: &SecurityParams, + ) -> Challenge { + let tag = "paillier_zk.no_small_factor.ni_challenge"; + let seed = udigest::inline_struct!(tag { + shared_state, + security, + aux: aux.digest_public_data(), + data, + commitment, + }); + let mut rng = rand_hash::HashRng::::from_seed(&seed); + super::interactive::challenge(security, &mut rng) + } + + /// Verify the proof, deriving challenge independently from same data + pub fn verify( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + security: &SecurityParams, + proof: &Proof, + ) -> Result<(), InvalidProof> { + let challenge = challenge::(shared_state, aux, data, &proof.commitment, security); + super::interactive::verify( + aux, + data, + &proof.commitment, + security, + &challenge, + &proof.proof, + ) + } +} + +#[cfg(test)] +mod test { + use rug::{Complete, Integer}; + + use crate::common::test::generate_blum_prime; + use crate::common::InvalidProofReason; + + // If q > 2^epsilon, the proof will never pass. We can make l however small + // we wish though, provided the statement we want to prove holds + + #[test] + fn passing() { + type D = sha2::Sha256; + + let mut rng = rand_dev::DevRng::new(); + let p = generate_blum_prime(&mut rng, 256); + let q = generate_blum_prime(&mut rng, 256); + let n = (&p * &q).complete(); + let n_root = n.sqrt_ref().complete(); + let data = super::Data { + n: &n, + n_root: &n_root, + }; + let security = super::SecurityParams { + l: 64, + epsilon: 128, + q: (Integer::ONE << 128_u32).complete(), + }; + let aux = crate::common::test::aux(&mut rng); + let shared_state = "shared state"; + let proof = super::non_interactive::prove::( + &shared_state, + &aux, + data, + super::PrivateData { p: &p, q: &q }, + &security, + &mut rng, + ) + .unwrap(); + let r = super::non_interactive::verify::(&shared_state, &aux, data, &security, &proof); + match r { + Ok(()) => (), + Err(e) => panic!("Proof should not fail with {e:?}"), + } + } + + #[test] + fn failing() { + type D = sha2::Sha256; + + let mut rng = rand_dev::DevRng::new(); + let p = generate_blum_prime(&mut rng, 128); + let q = generate_blum_prime(&mut rng, 384); + let n = (&p * &q).complete(); + let n_root = n.sqrt_ref().complete(); + let data = super::Data { + n: &n, + n_root: &n_root, + }; + let security = super::SecurityParams { + l: 4, + epsilon: 128, + q: (Integer::ONE << 128_u32).complete(), + }; + let aux = crate::common::test::aux(&mut rng); + let shared_state = "shared state"; + let proof = super::non_interactive::prove::( + &shared_state, + &aux, + data, + super::PrivateData { p: &p, q: &q }, + &security, + &mut rng, + ) + .unwrap(); + let r = super::non_interactive::verify::(&shared_state, &aux, data, &security, &proof) + .expect_err("proof should not pass"); + match r.reason() { + InvalidProofReason::RangeCheck(2) => (), + e => panic!("Proof should not fail with {e:?}"), + } + } + + #[test] + fn test_sqrt() { + assert_eq!(Integer::from(1).sqrt(), Integer::from(1)); + assert_eq!(Integer::from(2).sqrt(), Integer::from(1)); + assert_eq!(Integer::from(3).sqrt(), Integer::from(1)); + assert_eq!(Integer::from(4).sqrt(), Integer::from(2)); + assert_eq!(Integer::from(5).sqrt(), Integer::from(2)); + assert_eq!(Integer::from(6).sqrt(), Integer::from(2)); + assert_eq!(Integer::from(7).sqrt(), Integer::from(2)); + assert_eq!(Integer::from(8).sqrt(), Integer::from(2)); + assert_eq!(Integer::from(9).sqrt(), Integer::from(3)); + } +} diff --git a/paillier-zk/src/paillier_affine_operation_in_range.rs b/paillier-zk/src/paillier_affine_operation_in_range.rs new file mode 100644 index 00000000..c9d63c90 --- /dev/null +++ b/paillier-zk/src/paillier_affine_operation_in_range.rs @@ -0,0 +1,645 @@ +//! ZK-proof of paillier operation with group commitment in range. Called Пaff-g +//! or Raff-g in the CGGMP21 paper. +//! +//! ## Description +//! +//! 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, +//! correspondingly, and P doesn't want to disclose none of the plaintexts +//! +//! Given: +//! - `key0`, `pkey0`, `key1`, `pkey1` - pairs of public and private keys in +//! paillier cryptosystem +//! - `nonce_y`, `nonce` - nonces in paillier encryption +//! - `x`, `y` - some numbers +//! - `q`, `g` such that ` = Zq*` - prime order group +//! - `C` is some ciphertext encrypted by `key0` +//! - `Y = key1.encrypt(y, nonce_y)` +//! - `X = g * x` +//! - `D = oadd(enc(y, nonce), omul(x, C))` where `enc`, `oadd` and `omul` are +//! paillier encryption, homomorphic addition and multiplication with `key0` +//! +//! Prove: +//! - `bitsize(abs(x)) <= l_x` +//! - `bitsize(abs(y)) <= l_y` +//! +//! Disclosing only: `key0`, `key1`, `C`, `D`, `Y`, `X` +//! +//! ## Example +//! +//! ```rust +//! use paillier_zk::{paillier_affine_operation_in_range as p, IntegerExt}; +//! use rug::{Integer, Complete}; +//! use generic_ec::{Point, curves::Secp256k1 as E}; +//! # mod pregenerated { +//! # use super::*; +//! # paillier_zk::load_pregenerated_data!( +//! # verifier_aux: p::Aux, +//! # someone_encryption_key0: fast_paillier::EncryptionKey, +//! # someone_encryption_key1: fast_paillier::EncryptionKey, +//! # ); +//! # } +//! +//! # fn main() -> Result<(), Box> { +//! // Prover and verifier have a shared protocol state +//! let shared_state = "some shared state"; +//! +//! let mut rng = rand_core::OsRng; +//! # let mut rng = rand_dev::DevRng::new(); +//! +//! // 0. Setup: prover and verifier share common Ring-Pedersen parameters: +//! +//! let aux: p::Aux = pregenerated::verifier_aux(); +//! let security = p::SecurityParams { +//! l_x: 256, +//! l_y: 848, +//! epsilon: 230, +//! q: (Integer::ONE << 128_u32).complete(), +//! }; +//! +//! // 1. Setup: prover prepares the paillier keys +//! +//! // C and D are encrypted by this key +//! let key0: fast_paillier::EncryptionKey = pregenerated::someone_encryption_key0(); +//! // Y is encrypted using this key +//! let key1: fast_paillier::EncryptionKey = pregenerated::someone_encryption_key1(); +//! +//! // C is some number encrypted using key0. Neither of parties +//! // need to know the plaintext +//! let ciphertext_c = Integer::gen_invertible(&key0.nn(), &mut rng); +//! +//! // 2. Setup: prover prepares all plaintexts +//! +//! // x in paper +//! let plaintext_x = Integer::from_rng_pm( +//! &(Integer::ONE << security.l_x).complete(), +//! &mut rng, +//! ); +//! // y in paper +//! let plaintext_y = Integer::from_rng_pm( +//! &(Integer::ONE << security.l_y).complete(), +//! &mut rng, +//! ); +//! +//! // 3. Setup: prover encrypts everything on correct keys and remembers some nonces +//! +//! // X in paper +//! let ciphertext_x = Point::::generator() * plaintext_x.to_scalar(); +//! // Y and ρ_y in paper +//! let (ciphertext_y, nonce_y) = key1.encrypt_with_random( +//! &mut rng, +//! &(plaintext_y.signed_modulo(key1.n())), +//! )?; +//! // nonce is ρ in paper +//! let (ciphertext_y_by_key1, nonce) = key0.encrypt_with_random( +//! &mut rng, +//! &(plaintext_y.signed_modulo(key0.n())) +//! )?; +//! // D in paper +//! let ciphertext_d = key0 +//! .oadd( +//! &key0.omul(&plaintext_x, &ciphertext_c)?, +//! &ciphertext_y_by_key1, +//! )?; +//! +//! // 4. Prover computes a non-interactive proof that plaintext_x and +//! // plaintext_y are at most `l_x` and `l_y` bits +//! +//! let data = p::Data { +//! key0: &key0, +//! key1: &key1, +//! c: &ciphertext_c, +//! d: &ciphertext_d, +//! x: &ciphertext_x, +//! y: &ciphertext_y, +//! }; +//! let pdata = p::PrivateData { +//! x: &plaintext_x, +//! y: &plaintext_y, +//! nonce: &nonce, +//! nonce_y: &nonce_y, +//! }; +//! let (commitment, proof) = +//! p::non_interactive::prove::( +//! &shared_state, +//! &aux, +//! data, +//! pdata, +//! &security, +//! &mut rng, +//! )?; +//! +//! // 5. Prover sends this data to verifier +//! +//! # use generic_ec::Curve; +//! # fn send(_: &p::Data, _: &p::Commitment, _: &p::Proof) { } +//! send(&data, &commitment, &proof); +//! +//! // 6. Verifier receives the data and the proof and verifies it +//! +//! # let recv = || (data, commitment, proof); +//! let (data, commitment, proof) = recv(); +//! let r = p::non_interactive::verify::( +//! &shared_state, +//! &aux, +//! data, +//! &commitment, +//! &security, +//! &proof, +//! )?; +//! # +//! # Ok(()) } +//! ``` +//! +//! If the verification succeeded, verifier can continue communication with prover + +use fast_paillier::{AnyEncryptionKey, Ciphertext, Nonce}; +use generic_ec::{Curve, Point}; +use rug::Integer; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +pub use crate::common::{Aux, InvalidProof}; + +/// 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, udigest::Digestable)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct SecurityParams { + /// l in paper, bit size of +-x + pub l_x: usize, + /// l' in paper, bit size of +-y + pub l_y: usize, + /// Epsilon in paper, slackness parameter + pub epsilon: usize, + /// q in paper. Security parameter for challenge + #[udigest(as = crate::common::encoding::Integer)] + pub q: Integer, +} + +/// Public data that both parties know +#[derive(Debug, Clone, Copy, udigest::Digestable)] +#[udigest(bound = "")] +pub struct Data<'a, C: Curve> { + /// N0 in paper, public key that C was encrypted on + #[udigest(as = crate::common::encoding::AnyEncryptionKey)] + pub key0: &'a dyn AnyEncryptionKey, + /// N1 in paper, public key that y -> Y was encrypted on + #[udigest(as = crate::common::encoding::AnyEncryptionKey)] + pub key1: &'a dyn AnyEncryptionKey, + /// C or C0 in paper, some data encrypted on N0 + #[udigest(as = &crate::common::encoding::Integer)] + pub c: &'a Ciphertext, + /// D or C in paper, result of affine transformation of C0 with x and y + #[udigest(as = &crate::common::encoding::Integer)] + pub d: &'a Integer, + /// Y in paper, y encrypted on N1 + #[udigest(as = &crate::common::encoding::Integer)] + pub y: &'a Ciphertext, + /// X in paper, obtained as g^x + pub x: &'a Point, +} + +/// Private data of prover +#[derive(Clone, Copy)] +pub struct PrivateData<'a> { + /// x or epsilon in paper, preimage of X + pub x: &'a Integer, + /// y or delta in paper, preimage of Y + pub y: &'a Integer, + /// rho in paper, nonce in encryption of y for additive action + pub nonce: &'a Nonce, + /// rho_y in paper, nonce in encryption of y to obtain Y + pub nonce_y: &'a Nonce, +} + +// As described in cggmp21 at page 35 +/// Prover's first message, obtained by [`interactive::commit`] +#[derive(Debug, Clone, udigest::Digestable)] +#[udigest(bound = "")] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct Commitment { + #[udigest(as = crate::common::encoding::Integer)] + pub a: Integer, + pub b_x: Point, + #[udigest(as = crate::common::encoding::Integer)] + pub b_y: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub e: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub s: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub f: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub t: Integer, +} + +/// Prover's data accompanying the commitment. Kept as state between rounds in +/// the interactive protocol. +#[derive(Clone)] +pub struct PrivateCommitment { + pub alpha: Integer, + pub beta: Integer, + pub r: Integer, + pub r_y: Integer, + pub gamma: Integer, + pub m: Integer, + pub delta: Integer, + pub mu: Integer, +} + +/// Verifier's challenge to prover. Can be obtained deterministically by +/// [`non_interactive::challenge`] or randomly by [`interactive::challenge`] +pub type Challenge = Integer; + +/// The ZK proof. Computed by [`interactive::prove`] or +/// [`non_interactive::prove`] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Proof { + pub z1: Integer, + pub z2: Integer, + pub z3: Integer, + pub z4: Integer, + pub w: Integer, + pub w_y: Integer, +} + +/// 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, Point}; + use rand_core::RngCore; + use rug::{Complete, Integer}; + + use crate::common::{fail_if, fail_if_ne, IntegerExt, InvalidProof, InvalidProofReason}; + use crate::Error; + + use super::*; + + /// Create random commitment + pub fn commit( + aux: &Aux, + data: Data, + pdata: PrivateData, + security: &SecurityParams, + mut rng: R, + ) -> Result<(Commitment, PrivateCommitment), Error> { + let two_to_l = (Integer::ONE << security.l_x).complete(); + let two_to_l_e = (Integer::ONE << (security.l_x + security.epsilon)).complete(); + let two_to_l_prime_e = (Integer::ONE << (security.l_y + security.epsilon)).complete(); + let hat_n_at_two_to_l_e = (&aux.rsa_modulo * &two_to_l_e).complete(); + let hat_n_at_two_to_l = (&aux.rsa_modulo * &two_to_l).complete(); + + let alpha = Integer::from_rng_pm(&two_to_l_e, &mut rng); + let beta = Integer::from_rng_pm(&two_to_l_prime_e, &mut rng); + let r = Integer::gen_invertible(data.key0.n(), &mut rng); + let r_y = Integer::gen_invertible(data.key1.n(), &mut rng); + let gamma = Integer::from_rng_pm(&hat_n_at_two_to_l_e, &mut rng); + let delta = Integer::from_rng_pm(&hat_n_at_two_to_l_e, &mut rng); + let m = Integer::from_rng_pm(&hat_n_at_two_to_l, &mut rng); + let mu = Integer::from_rng_pm(&hat_n_at_two_to_l, &mut rng); + + let beta_enc_key0 = data.key0.encrypt_with(&beta, &r)?; + let alpha_at_c = data.key0.omul(&alpha, data.c)?; + let a = data.key0.oadd(&alpha_at_c, &beta_enc_key0)?; + + let commitment = Commitment { + a, + b_x: Point::::generator() * alpha.to_scalar(), + b_y: data.key1.encrypt_with(&beta, &r_y)?, + e: aux.combine(&alpha, &gamma)?, + s: aux.combine(pdata.x, &m)?, + f: aux.combine(&beta, &delta)?, + t: aux.combine(pdata.y, &mu)?, + }; + let private_commitment = PrivateCommitment { + alpha, + beta, + r, + r_y, + gamma, + m, + delta, + mu, + }; + Ok((commitment, private_commitment)) + } + + /// Compute proof for given data and prior protocol values + pub fn prove( + data: Data, + pdata: PrivateData, + pcomm: &PrivateCommitment, + challenge: &Challenge, + ) -> Result { + Ok(Proof { + z1: (&pcomm.alpha + challenge * pdata.x).complete(), + z2: (&pcomm.beta + challenge * pdata.y).complete(), + z3: (&pcomm.gamma + challenge * &pcomm.m).complete(), + z4: (&pcomm.delta + challenge * &pcomm.mu).complete(), + w: data + .key0 + .n() + .combine(&pcomm.r, Integer::ONE, pdata.nonce, challenge)?, + w_y: data + .key1 + .n() + .combine(&pcomm.r_y, Integer::ONE, pdata.nonce_y, challenge)?, + }) + } + + /// Verify the proof + pub fn verify( + aux: &Aux, + data: Data, + commitment: &Commitment, + security: &SecurityParams, + challenge: &Challenge, + proof: &Proof, + ) -> Result<(), InvalidProof> { + // Five equality checks and two range checks + { + let lhs = { + let z1_at_c = data + .key0 + .omul(&proof.z1, data.c) + .map_err(|_| InvalidProofReason::PaillierOp)?; + let enc = data + .key0 + .encrypt_with(&proof.z2, &proof.w) + .map_err(|_| InvalidProofReason::PaillierEnc)?; + data.key0 + .oadd(&z1_at_c, &enc) + .map_err(|_| InvalidProofReason::PaillierOp)? + }; + let rhs = { + let e_at_d = data + .key0 + .omul(challenge, data.d) + .map_err(|_| InvalidProofReason::PaillierOp)?; + data.key0 + .oadd(&commitment.a, &e_at_d) + .map_err(|_| InvalidProofReason::PaillierOp)? + }; + fail_if_ne(InvalidProofReason::EqualityCheck(1), lhs, rhs)?; + } + { + let lhs = Point::::generator() * proof.z1.to_scalar(); + let rhs = commitment.b_x + data.x * challenge.to_scalar(); + fail_if_ne(InvalidProofReason::EqualityCheck(2), lhs, rhs)?; + } + { + let lhs = data + .key1 + .encrypt_with(&proof.z2, &proof.w_y) + .map_err(|_| InvalidProofReason::PaillierEnc)?; + let rhs = { + let e_at_y = data + .key1 + .omul(challenge, data.y) + .map_err(|_| InvalidProofReason::PaillierOp)?; + data.key1 + .oadd(&commitment.b_y, &e_at_y) + .map_err(|_| InvalidProofReason::PaillierOp)? + }; + fail_if_ne(InvalidProofReason::EqualityCheck(3), lhs, rhs)?; + } + { + let lhs = aux.combine(&proof.z1, &proof.z3)?; + let s_to_e = aux.pow_mod(&commitment.s, challenge)?; + let rhs = (&commitment.e * s_to_e).modulo(&aux.rsa_modulo); + fail_if_ne(InvalidProofReason::EqualityCheck(4), lhs, rhs)?; + } + { + let lhs = aux.combine(&proof.z2, &proof.z4)?; + let t_to_e = aux.pow_mod(&commitment.t, challenge)?; + let rhs = (&commitment.f * t_to_e).modulo(&aux.rsa_modulo); + fail_if_ne(InvalidProofReason::EqualityCheck(5), lhs, rhs)?; + } + fail_if( + InvalidProofReason::RangeCheck(6), + proof + .z1 + .is_in_pm(&(Integer::ONE << (security.l_x + security.epsilon)).complete()), + )?; + fail_if( + InvalidProofReason::RangeCheck(7), + proof + .z2 + .is_in_pm(&(Integer::ONE << (security.l_y + security.epsilon)).complete()), + )?; + Ok(()) + } + + /// Generate random challenge + pub fn challenge(security: &SecurityParams, rng: &mut R) -> Integer + where + R: RngCore, + { + Integer::from_rng_pm(&security.q, rng) + } +} + +/// The non-interactive version of proof. Completed in one round, for example +/// see the documentation of parent module. +pub mod non_interactive { + use digest::Digest; + use generic_ec::Curve; + + use crate::{Error, InvalidProof}; + + use super::{Aux, Challenge, Commitment, Data, PrivateData, Proof, SecurityParams}; + + /// Compute proof for the given data, producing random commitment and + /// deriving determenistic challenge. + /// + /// Obtained from the above interactive proof via Fiat-Shamir heuristic. + pub fn prove( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + pdata: PrivateData, + security: &SecurityParams, + rng: &mut impl rand_core::RngCore, + ) -> Result<(Commitment, Proof), Error> { + let (comm, pcomm) = super::interactive::commit(aux, data, pdata, security, rng)?; + let challenge = challenge::(shared_state, aux, data, &comm, security); + let proof = super::interactive::prove(data, pdata, &pcomm, &challenge)?; + Ok((comm, proof)) + } + + /// Verify the proof, deriving challenge independently from same data + pub fn verify( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + commitment: &Commitment, + security: &SecurityParams, + proof: &Proof, + ) -> Result<(), InvalidProof> { + let challenge = challenge::(shared_state, aux, data, commitment, security); + super::interactive::verify(aux, data, commitment, security, &challenge, proof) + } + + /// Deterministically compute challenge based on prior known values in protocol + pub fn challenge( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + commitment: &Commitment, + security: &SecurityParams, + ) -> Challenge { + let tag = "paillier_zk.paillier_affine_operation_in_range.ni_challenge"; + let aux = aux.digest_public_data(); + let seed = udigest::inline_struct!(tag { + shared_state, + aux, + security, + data, + commitment, + }); + let mut rng = rand_hash::HashRng::::from_seed(seed); + super::interactive::challenge(security, &mut rng) + } +} + +#[cfg(test)] +mod test { + use generic_ec::{Curve, Point}; + use rug::{Complete, Integer}; + use sha2::Digest; + + use crate::common::test::random_key; + use crate::common::{IntegerExt, InvalidProofReason}; + + fn run( + rng: &mut R, + security: super::SecurityParams, + x: Integer, + y: Integer, + ) -> Result<(), crate::common::InvalidProof> { + let dk0 = random_key(rng).unwrap(); + let dk1 = random_key(rng).unwrap(); + let ek0 = dk0.encryption_key().clone(); + let ek1 = dk1.encryption_key().clone(); + + let (c, _) = { + let plaintext = Integer::from_rng_pm(ek0.half_n(), rng); + ek0.encrypt_with_random(rng, &plaintext).unwrap() + }; + + let (y_enc_ek1, rho_y) = ek1.encrypt_with_random(rng, &y).unwrap(); + + let (y_enc_ek0, rho) = ek0.encrypt_with_random(rng, &y).unwrap(); + let x_at_c = ek0.omul(&x, &c).unwrap(); + let d = ek0.oadd(&x_at_c, &y_enc_ek0).unwrap(); + + let data = super::Data { + key0: &ek0, + key1: &ek1, + c: &c, + d: &d, + y: &y_enc_ek1, + x: &(x.to_scalar::() * Point::generator()), + }; + let pdata = super::PrivateData { + x: &x, + y: &y, + nonce: &rho, + nonce_y: &rho_y, + }; + + let aux = crate::common::test::aux(rng); + + let shared_state = "shared state"; + + let (commitment, proof) = + super::non_interactive::prove::(&shared_state, &aux, data, pdata, &security, rng) + .unwrap(); + super::non_interactive::verify::( + &shared_state, + &aux, + data, + &commitment, + &security, + &proof, + ) + } + + fn passing_test() { + let mut rng = rand_dev::DevRng::new(); + let security = super::SecurityParams { + l_x: 1024, + l_y: 1024, + epsilon: 300, + q: (Integer::ONE << 128_u32).into(), + }; + let x = Integer::from_rng_pm(&(Integer::ONE << security.l_x).complete(), &mut rng); + let y = Integer::from_rng_pm(&(Integer::ONE << security.l_y).complete(), &mut rng); + run::<_, C, D>(&mut rng, security, x, y).expect("proof failed"); + } + + fn failing_on_additive() { + let mut rng = rand_dev::DevRng::new(); + let security = super::SecurityParams { + l_x: 1024, + l_y: 1024, + epsilon: 300, + q: (Integer::ONE << 128_u32).complete(), + }; + let x = Integer::from_rng_pm(&(Integer::ONE << security.l_x).complete(), &mut rng); + let y = (Integer::ONE << (security.l_y + security.epsilon)).complete() + 1; + let r = run::<_, C, D>(&mut rng, security, x, y).expect_err("proof should not pass"); + match r.reason() { + InvalidProofReason::RangeCheck(7) => (), + e => panic!("proof should not fail with: {e:?}"), + } + } + + fn failing_on_multiplicative() { + let mut rng = rand_dev::DevRng::new(); + let security = super::SecurityParams { + l_x: 1024, + l_y: 1024, + epsilon: 300, + q: (Integer::ONE << 128_u32).complete(), + }; + let x = (Integer::ONE << (security.l_x + security.epsilon)).complete() + 1; + let y = Integer::from_rng_pm(&(Integer::ONE << security.l_y).complete(), &mut rng); + let r = run::<_, C, D>(&mut rng, security, x, y).expect_err("proof should not pass"); + match r.reason() { + InvalidProofReason::RangeCheck(6) => (), + e => panic!("proof should not fail with: {e:?}"), + } + } + + #[test] + fn passing_p256() { + passing_test::() + } + #[test] + fn failing_p256_add() { + failing_on_additive::() + } + #[test] + fn failing_p256_mul() { + failing_on_multiplicative::() + } + + #[test] + fn passing_million() { + passing_test::() + } + #[test] + fn failing_million_add() { + failing_on_additive::() + } + #[test] + fn failing_million_mul() { + failing_on_multiplicative::() + } +} diff --git a/paillier-zk/src/paillier_blum_modulus.rs b/paillier-zk/src/paillier_blum_modulus.rs new file mode 100644 index 00000000..fcf60fe1 --- /dev/null +++ b/paillier-zk/src/paillier_blum_modulus.rs @@ -0,0 +1,335 @@ +//! ZK-proof of Paillier-Blum modulus. Called Пmod or Rmod in the CGGMP21 paper. +//! +//! ## Description +//! A party P has a modulus `N = pq`, with p and q being Blum primes, and +//! `gcd(N, phi(N)) = 1`. P wants to prove that those equalities about N hold, +//! without disclosing p and q. +//! +//! ## Example +//! ```rust +//! # fn main() -> Result<(), Box> { +//! use rug::{Integer, Complete}; +//! let mut rng = rand_core::OsRng; +//! # let mut rng = rand_dev::DevRng::new(); +//! +//! // 0. Prover P derives two Blum primes and makes a Paillier-Blum modulus +//! let p = fast_paillier::utils::generate_safe_prime(&mut rng, 256); +//! let q = fast_paillier::utils::generate_safe_prime(&mut rng, 256); +//! let n = (&p * &q).complete(); +//! +//! // 1. P computes a non-interactive proof that `n` is a Paillier-Blum modulus: +//! use paillier_zk::paillier_blum_modulus as p; +//! +//! // Security parameter +//! const SECURITY: usize = 33; +//! // Verifier and prover share the same state +//! let shared_state = "some shared state"; +//! +//! let data = p::Data { n }; +//! let pdata = p::PrivateData { p, q }; +//! +//! let (commitment, proof) = +//! p::non_interactive::prove::<{SECURITY}, sha2::Sha256>( +//! &shared_state, +//! &data, +//! &pdata, +//! &mut rng, +//! )?; +//! +//! // 2. P sends `data, commitment, proof` to the verifier V +//! +//! # fn send(_: &p::Data, _: &p::Commitment, _: &p::Proof<{SECURITY}>) { } +//! send(&data, &commitment, &proof); +//! +//! // 3. V receives and verifies the proof: +//! +//! # let recv = || (data, commitment, proof); +//! let (data, commitment, proof) = recv(); +//! +//! p::non_interactive::verify::<{SECURITY}, sha2::Sha256>( +//! &shared_state, +//! &data, +//! &commitment, +//! &proof, +//! )?; +//! # Ok(()) } +//! ``` +//! If the verification succeeded, V can continue communication with P + +use rug::Integer; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Public data that both parties know: the Paillier-Blum modulus +#[derive(Debug, Clone, udigest::Digestable)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Data { + #[udigest(as = crate::common::encoding::Integer)] + pub n: Integer, +} + +/// Private data of prover +#[derive(Clone)] +pub struct PrivateData { + pub p: Integer, + pub q: Integer, +} + +/// Prover's first message, obtained by [`interactive::commit`] +#[derive(Debug, Clone, udigest::Digestable)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Commitment { + #[udigest(as = crate::common::encoding::Integer)] + pub w: Integer, +} + +/// Verifier's challenge to prover. Can be obtained deterministically by +/// [`non_interactive::challenge`] or randomly by [`interactive::challenge`] +/// +/// Consists of `M` singular challenges +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Challenge { + pub ys: [Integer; M], +} + +/// A part of proof. Having enough of those guarantees security +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ProofPoint { + pub x: Integer, + pub a: bool, + pub b: bool, + pub z: Integer, +} + +/// 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 { + #[cfg_attr( + // A trick to serialize arbitrary size arrays + feature = "serde", + serde(with = "serde_with::As::<[serde_with::Same; M]>") + )] + 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 rand_core::RngCore; + use rug::{Complete, Integer}; + + use crate::common::sqrt::{blum_sqrt, find_residue, sample_neg_jacobi}; + use crate::{BadExponent, Error, ErrorReason, InvalidProof, InvalidProofReason}; + + use super::{Challenge, Commitment, Data, PrivateData, Proof, ProofPoint}; + + /// Create random commitment + pub fn commit(Data { ref n }: &Data, rng: &mut R) -> Commitment { + Commitment { + w: sample_neg_jacobi(n, rng), + } + } + + /// Compute proof for given data and prior protocol values + pub fn prove( + Data { ref n }: &Data, + PrivateData { ref p, ref q }: &PrivateData, + Commitment { ref w }: &Commitment, + challenge: &Challenge, + ) -> Result, Error> { + let blum_sqrt = |x| blum_sqrt(&x, p, q, n); + let phi = (p - 1u8).complete() * (q - 1u8).complete(); + let n_inverse = n.invert_ref(&phi).ok_or(ErrorReason::Invert)?.into(); + + // We do an extra allocation as workaround while `array::try_map` is not stable + let points = challenge + .ys + .iter() + .map(|y| { + let z = y + .pow_mod_ref(&n_inverse, n) + .ok_or(BadExponent::undefined())? + .into(); + let (a, b, y_) = find_residue(y, w, p, q, n).ok_or(ErrorReason::FindResidue)?; + let x = blum_sqrt(blum_sqrt(y_)); + Ok(ProofPoint { x, a, b, z }) + }) + .collect::, ErrorReason>>()? + .try_into() + .map_err(|_| ErrorReason::Length)?; + Ok(Proof { points }) + } + + /// Verify the proof. If this succeeds, the relation Rmod holds with chance + /// `1/2^M` + pub fn verify( + data: &Data, + commitment: &Commitment, + challenge: &Challenge, + proof: &Proof, + ) -> Result<(), InvalidProof> { + if data.n.is_probably_prime(25) != rug::integer::IsPrime::No { + return Err(InvalidProofReason::ModulusIsPrime.into()); + } + if data.n.is_even() { + return Err(InvalidProofReason::ModulusIsEven.into()); + } + for (point, y) in proof.points.iter().zip(challenge.ys.iter()) { + if Integer::from( + point + .z + .pow_mod_ref(&data.n, &data.n) + .ok_or(InvalidProofReason::ModPow)?, + ) != *y + { + return Err(InvalidProofReason::IncorrectNthRoot.into()); + } + let y = y.clone(); + let y = if point.a { &data.n - y } else { y }; + let y = if point.b { + (y * &commitment.w).modulo(&data.n) + } else { + y + }; + if Integer::from( + point + .x + .pow_mod_ref(&4.into(), &data.n) + .ok_or(InvalidProofReason::ModPow)?, + ) != y + { + return Err(InvalidProofReason::IncorrectFourthRoot.into()); + } + } + Ok(()) + } + + /// Generate random challenge + /// + /// `data` parameter is used to generate challenge in correct range + pub fn challenge( + Data { ref n }: &Data, + rng: &mut R, + ) -> Challenge { + let ys = [(); M].map(|()| { + n.random_below_ref(&mut fast_paillier::utils::external_rand(rng)) + .into() + }); + Challenge { ys } + } +} + +/// The non-interactive version of proof. Completed in one round, for example +/// see the documentation of parent module. +pub mod non_interactive { + use digest::Digest; + + use crate::{Error, InvalidProof}; + + use super::{Challenge, Commitment, Data, PrivateData, Proof}; + + /// Compute proof for the given data, producing random commitment and + /// deriving determenistic challenge. + /// + /// Obtained from the above interactive proof via Fiat-Shamir heuristic. + pub fn prove( + shared_state: &impl udigest::Digestable, + data: &Data, + pdata: &PrivateData, + rng: &mut impl rand_core::RngCore, + ) -> Result<(Commitment, Proof), Error> { + let commitment = super::interactive::commit(data, rng); + let challenge = challenge::(shared_state, data, &commitment); + let proof = super::interactive::prove(data, pdata, &commitment, &challenge)?; + Ok((commitment, proof)) + } + + /// Verify the proof, deriving challenge independently from same data + pub fn verify( + shared_state: &impl udigest::Digestable, + data: &Data, + commitment: &Commitment, + proof: &Proof, + ) -> Result<(), InvalidProof> { + let challenge = challenge::(shared_state, data, commitment); + super::interactive::verify(data, commitment, &challenge, proof) + } + + /// Deterministically compute challenge based on prior known values in protocol + pub fn challenge( + shared_state: &impl udigest::Digestable, + data: &Data, + commitment: &Commitment, + ) -> Challenge { + let tag = "paillier_zk.blum_modulus.ni_challenge"; + let seed = udigest::inline_struct!(tag { + shared_state, + data, + commitment, + }); + let mut rng = rand_hash::HashRng::::from_seed(seed); + // since we can't use Default and Integer isn't copy, we initialize + // like this + let ys = [(); M].map(|()| { + data.n + .random_below_ref(&mut fast_paillier::utils::external_rand(&mut rng)) + .into() + }); + Challenge { ys } + } +} + +#[cfg(test)] +mod test { + use rug::Complete; + + use crate::common::test::{generate_blum_prime, generate_prime}; + + type D = sha2::Sha256; + + #[test] + fn passing() { + let mut rng = rand_dev::DevRng::new(); + let p = generate_blum_prime(&mut rng, 256); + let q = generate_blum_prime(&mut rng, 256); + let n = (&p * &q).complete(); + let data = super::Data { n }; + let pdata = super::PrivateData { p, q }; + let shared_state = "shared state"; + let (commitment, proof) = + super::non_interactive::prove::<65, D>(&shared_state, &data, &pdata, &mut rng).unwrap(); + let r = super::non_interactive::verify::<65, D>(&shared_state, &data, &commitment, &proof); + match r { + Ok(()) => (), + Err(e) => panic!("{e:?}"), + } + } + + #[test] + fn failing() { + let mut rng = rand_dev::DevRng::new(); + let p = generate_blum_prime(&mut rng, 256); + let q = loop { + // non blum prime + let q = generate_prime(&mut rng, 256); + if q.mod_u(4) == 1 { + break q; + } + }; + let n = (&p * &q).complete(); + let data = super::Data { n }; + let pdata = super::PrivateData { p, q }; + let shared_state = "shared state"; + let (commitment, proof) = + super::non_interactive::prove::<65, D>(&shared_state, &data, &pdata, &mut rng).unwrap(); + let r = super::non_interactive::verify::<65, D>(&shared_state, &data, &commitment, &proof); + if r.is_ok() { + panic!("proof should not pass"); + } + } +} diff --git a/paillier-zk/src/paillier_encryption_in_range.rs b/paillier-zk/src/paillier_encryption_in_range.rs new file mode 100644 index 00000000..8801b698 --- /dev/null +++ b/paillier-zk/src/paillier_encryption_in_range.rs @@ -0,0 +1,427 @@ +//! ZK-proof of paillier encryption in range. Called Пenc or Renc in the CGGMP21 +//! paper. +//! +//! ## Description +//! +//! A party P has `key`, `pkey` - public and private keys in paillier +//! cryptosystem. P also has `plaintext`, `nonce`, and +//! `ciphertext = key.encrypt_with(plaintext, nonce)`. +//! +//! P wants to prove that `plaintext` is at most `l` bits, without disclosing +//! it, the `pkey`, and `nonce` + +//! ## Example +//! +//! ``` +//! use paillier_zk::{paillier_encryption_in_range as p, IntegerExt}; +//! use rug::{Integer, Complete}; +//! # mod pregenerated { +//! # use super::*; +//! # paillier_zk::load_pregenerated_data!( +//! # verifier_aux: p::Aux, +//! # prover_decryption_key: fast_paillier::DecryptionKey, +//! # ); +//! # } +//! # fn main() -> Result<(), Box> { +//! +//! let shared_state = "some shared state"; +//! +//! let mut rng = rand_core::OsRng; +//! # let mut rng = rand_dev::DevRng::new(); +//! +//! // 0. Setup: prover and verifier share common Ring-Pedersen parameters: +//! +//! let aux: p::Aux = pregenerated::verifier_aux(); +//! let security = p::SecurityParams { +//! l: 1024, +//! epsilon: 128, +//! q: (Integer::ONE << 128_u32).into(), +//! }; +//! +//! // 1. Setup: prover prepares the paillier keys +//! +//! let private_key: fast_paillier::DecryptionKey = +//! pregenerated::prover_decryption_key(); +//! let key = private_key.encryption_key(); +//! +//! // 2. Setup: prover has some plaintext and encrypts it +//! +//! let plaintext = Integer::from_rng_pm(&(Integer::ONE << security.l).complete(), &mut rng); +//! let (ciphertext, nonce) = key.encrypt_with_random(&mut rng, &plaintext)?; +//! +//! // 3. Prover computes a non-interactive proof that plaintext is at most 1024 bits: +//! +//! let data = p::Data { key, ciphertext: &ciphertext }; +//! let (commitment, proof) = p::non_interactive::prove::( +//! &shared_state, +//! &aux, +//! data, +//! p::PrivateData { +//! plaintext: &plaintext, +//! nonce: &nonce, +//! }, +//! &security, +//! &mut rng, +//! )?; +//! +//! // 4. Prover sends this data to verifier +//! +//! # fn send(_: &p::Data, _: &p::Commitment, _: &p::Proof) { } +//! send(&data, &commitment, &proof); +//! +//! // 5. Verifier receives the data and the proof and verifies it +//! +//! # let recv = || (data, commitment, proof); +//! let (data, commitment, proof) = recv(); +//! p::non_interactive::verify::( +//! &shared_state, +//! &aux, +//! data, +//! &commitment, +//! &security, +//! &proof, +//! ); +//! # Ok(()) } +//! ``` +//! +//! If the verification succeeded, verifier can continue communication with prover + +use fast_paillier::{AnyEncryptionKey, Ciphertext, Nonce}; +use rug::Integer; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +pub use crate::common::Aux; +pub use crate::common::InvalidProof; + +/// 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, security parameter for bit size of plaintext: it needs to + /// be in range [-2^l; 2^l] or equivalently 2^l + pub l: usize, + /// Epsilon in paper, slackness parameter + pub epsilon: usize, + /// q in paper. Security parameter for challenge + pub q: Integer, +} + +/// Public data that both parties know +#[derive(Debug, Clone, Copy, udigest::Digestable)] +pub struct Data<'a> { + /// N0 in paper, public key that k -> K was encrypted on + #[udigest(as = crate::common::encoding::AnyEncryptionKey)] + pub key: &'a dyn AnyEncryptionKey, + /// K in paper + #[udigest(as = &crate::common::encoding::Integer)] + pub ciphertext: &'a Ciphertext, +} + +/// Private data of prover +#[derive(Clone, Copy)] +pub struct PrivateData<'a> { + /// k in paper, plaintext of K + pub plaintext: &'a Integer, + /// rho in paper, nonce of encryption k -> K + pub nonce: &'a Nonce, +} + +// As described in cggmp21 at page 33 +/// Prover's first message, obtained by [`interactive::commit`] +#[derive(Debug, Clone, udigest::Digestable)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Commitment { + #[udigest(as = crate::common::encoding::Integer)] + pub s: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub a: Integer, + #[udigest(as = crate::common::encoding::Integer)] + pub c: Integer, +} + +/// Prover's data accompanying the commitment. Kept as state between rounds in +/// the interactive protocol. +#[derive(Clone)] +pub struct PrivateCommitment { + pub alpha: Integer, + pub mu: Integer, + pub r: Integer, + pub gamma: Integer, +} + +/// Verifier's challenge to prover. Can be obtained deterministically by +/// [`non_interactive::challenge`] or randomly by [`interactive::challenge`] +pub type Challenge = Integer; + +// As described in cggmp21 at page 33 +/// The ZK proof. Computed by [`interactive::prove`] or +/// [`non_interactive::prove`] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Proof { + pub z1: Integer, + pub z2: Integer, + pub z3: Integer, +} + +/// 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 rand_core::RngCore; + use rug::{Complete, Integer}; + + use crate::{ + common::{fail_if, fail_if_ne, InvalidProofReason}, + BadExponent, Error, + }; + + use crate::common::{IntegerExt, InvalidProof}; + + use super::{ + Aux, Challenge, Commitment, Data, PrivateCommitment, PrivateData, Proof, SecurityParams, + }; + + /// Create random commitment + pub fn commit( + aux: &Aux, + data: Data, + pdata: PrivateData, + security: &SecurityParams, + rng: &mut R, + ) -> Result<(Commitment, PrivateCommitment), Error> { + let two_to_l_plus_e = (Integer::ONE << (security.l + security.epsilon)).complete(); + let hat_n_at_two_to_l = (Integer::ONE << security.l).complete() * &aux.rsa_modulo; + let hat_n_at_two_to_l_plus_e = + (Integer::ONE << (security.l + security.epsilon)).complete() * &aux.rsa_modulo; + + let alpha = Integer::from_rng_pm(&two_to_l_plus_e, rng); + let mu = Integer::from_rng_pm(&hat_n_at_two_to_l, rng); + let r = Integer::gen_invertible(data.key.n(), rng); + let gamma = Integer::from_rng_pm(&hat_n_at_two_to_l_plus_e, rng); + + let s = aux.combine(pdata.plaintext, &mu)?; + let a = data.key.encrypt_with(&alpha, &r)?; + let c = aux.combine(&alpha, &gamma)?; + + Ok(( + Commitment { s, a, c }, + PrivateCommitment { + alpha, + mu, + r, + gamma, + }, + )) + } + + /// Compute proof for given data and prior protocol values + pub fn prove( + data: Data, + pdata: PrivateData, + private_commitment: &PrivateCommitment, + challenge: &Challenge, + ) -> Result { + let z1 = (&private_commitment.alpha + (challenge * pdata.plaintext)).complete(); + let nonce_to_challenge_mod_n: Integer = pdata + .nonce + .pow_mod_ref(challenge, data.key.n()) + .ok_or(BadExponent::undefined())? + .into(); + let z2 = (&private_commitment.r * nonce_to_challenge_mod_n).modulo(data.key.n()); + let z3 = (&private_commitment.gamma + (challenge * &private_commitment.mu)).complete(); + Ok(Proof { z1, z2, z3 }) + } + + /// Verify the proof + pub fn verify( + aux: &Aux, + data: Data, + commitment: &Commitment, + security: &SecurityParams, + challenge: &Challenge, + proof: &Proof, + ) -> Result<(), InvalidProof> { + { + fail_if_ne( + InvalidProofReason::EqualityCheck(1), + &data.ciphertext.gcd_ref(data.key.n()).complete(), + Integer::ONE, + )?; + } + { + let lhs = data + .key + .encrypt_with(&proof.z1, &proof.z2) + .map_err(|_| InvalidProofReason::PaillierEnc)?; + let rhs = { + let e_at_k = data + .key + .omul(challenge, data.ciphertext) + .map_err(|_| InvalidProofReason::PaillierOp)?; + data.key + .oadd(&commitment.a, &e_at_k) + .map_err(|_| InvalidProofReason::PaillierOp)? + }; + fail_if_ne(InvalidProofReason::EqualityCheck(2), lhs, rhs)?; + } + + { + let lhs = aux.combine(&proof.z1, &proof.z3)?; + let s_to_e = aux.pow_mod(&commitment.s, challenge)?; + let rhs = (&commitment.c * s_to_e).modulo(&aux.rsa_modulo); + fail_if_ne(InvalidProofReason::EqualityCheck(3), lhs, rhs)?; + } + + fail_if( + InvalidProofReason::RangeCheck(4), + proof + .z1 + .is_in_pm(&(Integer::ONE << (security.l + security.epsilon)).complete()), + )?; + + Ok(()) + } + + /// Generate random challenge + /// + /// `security` parameter is used to generate challenge in correct range + pub fn challenge(security: &SecurityParams, rng: &mut R) -> Challenge { + Integer::from_rng_pm(&security.q, rng) + } +} + +/// The non-interactive version of proof. Completed in one round, for example +/// see the documentation of parent module. +pub mod non_interactive { + use digest::Digest; + + use crate::{Error, InvalidProof}; + + use super::{Aux, Challenge, Commitment, Data, PrivateData, Proof, SecurityParams}; + + /// Compute proof for the given data, producing random commitment and + /// deriving determenistic challenge. + /// + /// Obtained from the above interactive proof via Fiat-Shamir heuristic. + pub fn prove( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + pdata: PrivateData, + security: &SecurityParams, + rng: &mut impl rand_core::RngCore, + ) -> Result<(Commitment, Proof), Error> { + let (comm, pcomm) = super::interactive::commit(aux, data, pdata, security, rng)?; + let challenge = challenge::(shared_state, aux, data, &comm, security); + let proof = super::interactive::prove(data, pdata, &pcomm, &challenge)?; + Ok((comm, proof)) + } + + /// Deterministically compute challenge based on prior known values in protocol + pub fn challenge( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + commitment: &Commitment, + security: &SecurityParams, + ) -> Challenge { + let tag = "paillier_zk.encryption_in_range.ni_challenge"; + let seed = udigest::inline_struct!(tag { + shared_state, + aux: aux.digest_public_data(), + data, + commitment, + }); + let mut rng = rand_hash::HashRng::::from_seed(seed); + super::interactive::challenge(security, &mut rng) + } + + /// Verify the proof, deriving challenge independently from same data + pub fn verify( + shared_state: &impl udigest::Digestable, + aux: &Aux, + data: Data, + commitment: &Commitment, + security: &SecurityParams, + proof: &Proof, + ) -> Result<(), InvalidProof> { + let challenge = challenge::(shared_state, aux, data, commitment, security); + super::interactive::verify(aux, data, commitment, security, &challenge, proof) + } +} + +#[cfg(test)] +mod test { + use rug::{Complete, Integer}; + use sha2::Digest; + + use crate::common::{IntegerExt, InvalidProofReason}; + + fn run_with( + mut rng: &mut impl rand_core::CryptoRngCore, + security: super::SecurityParams, + plaintext: Integer, + ) -> Result<(), crate::common::InvalidProof> { + let aux = crate::common::test::aux(&mut rng); + let private_key = crate::common::test::random_key(&mut rng).unwrap(); + let key = private_key.encryption_key(); + let (ciphertext, nonce) = key.encrypt_with_random(&mut rng, &plaintext).unwrap(); + let data = super::Data { + key, + ciphertext: &ciphertext, + }; + let pdata = super::PrivateData { + plaintext: &plaintext, + nonce: &nonce, + }; + + let shared_state = "shared state"; + let (commitment, proof) = + super::non_interactive::prove::(&shared_state, &aux, data, pdata, &security, rng) + .unwrap(); + super::non_interactive::verify::( + &shared_state, + &aux, + data, + &commitment, + &security, + &proof, + ) + } + + #[test] + fn passing() { + let mut rng = rand_dev::DevRng::new(); + let security = super::SecurityParams { + l: 1024, + epsilon: 256, + q: (Integer::ONE << 128_u32).complete() - 1, + }; + let plaintext = Integer::from_rng_pm(&(Integer::ONE << security.l).complete(), &mut rng); + let r = run_with::(&mut rng, security, plaintext); + match r { + Ok(()) => (), + Err(e) => panic!("{e:?}"), + } + } + #[test] + fn failing() { + let mut rng = rand_dev::DevRng::new(); + let security = super::SecurityParams { + l: 1024, + epsilon: 256, + q: (Integer::ONE << 128_u32).complete() - 1, + }; + let plaintext = (Integer::ONE << (security.l + security.epsilon)).complete() + 1; + let r = run_with::(&mut rng, security, plaintext); + match r.map_err(|e| e.reason()) { + Ok(()) => panic!("proof should not pass"), + Err(InvalidProofReason::RangeCheck(_)) => (), + Err(e) => panic!("proof should not fail with {e:?}"), + } + } +} diff --git a/paillier-zk/test-data/prover_decryption_key.json b/paillier-zk/test-data/prover_decryption_key.json new file mode 100644 index 00000000..55a670f0 --- /dev/null +++ b/paillier-zk/test-data/prover_decryption_key.json @@ -0,0 +1,10 @@ +[ + { + "radix": 16, + "value": "9aa538791dc73d1d4e30f536ce91a17d75b3a6cf7b2d259dd2add6c6d8e75d0b02e7e1cfc4c29dc919dd985454f1c9437a141e0b2ea70a8dffaa8dc7df7827a96172d573cb2d90ec37e9b92a74eb876728d1d6131ebfb46b416d9354aa9fe21195bb7b765b08b41502ba084ccc358837601c6eccb96c1ede6673213fc225b91c12e1afdf4f156dda9fd59e8593df7ac0827a35390d9a5f618a565cd456d35245f2c628ceeb6d6f8b03b011ae4863916b1c5531c83666145524810ac5b565061b" + }, + { + "radix": 16, + "value": "a97a20082a2364b459de175054e57b1c687f194b3e56e5ea67ff4712e76516b6a1bf013d413173c20fa3c0a643ee187630600f9ab56c29dc4d67d3c3fc4f164fbc0320b081351db6add197dc6b58696e6b96521a7503cf29eea0597ca58410ee34193793b914e0de611e5d091d858d9dae9dabab3d80272ec65e74adaa47cde9be7db96a7e1c6424ad7791506f7af7d17839d20cc8e6c05e1ec93b6deb5732e3156a411cc70595cac3919fff505a9f71b2d05ab8a64a602dad82a784bca34aaf" + } +] \ No newline at end of file diff --git a/paillier-zk/test-data/prover_encryption_key.json b/paillier-zk/test-data/prover_encryption_key.json new file mode 100644 index 00000000..63929eac --- /dev/null +++ b/paillier-zk/test-data/prover_encryption_key.json @@ -0,0 +1,4 @@ +{ + "radix": 16, + "value": "6660d85e740e1c7d626988f3fbd6b1dc419d37e9010f75a03313fbc004b52075b5a7352cad85ed2e0aa51970fd2c7eb78de5028a33283f5e2f0f58bfd47751c8bf6ef60af8a660fda9e4de3820d512e1718ff6af861e247a26faaa0551c10acd0b47399ac8c09f97ff1eb2e4d249fb80d5b1d223e9adb47b25ee6573172b9d059a9e730059a5cae59c9d812b07cb6c80b7e5aa2ca7cda48a158335e079572a2c549dcf3492684ef5b3ed72da3b0b658a0f3eb8ff2126379f79b5340cef2ab49aead0d2442e04e2ef8172254c09a771c621fd6b481eea29cb160650b517ef1e2ddaa7f92e99fb88b2f6e2d3c66485572f3487d26a8bc08adf43ea7ca3624f56ae4ab0fba2eadeaa888b165e13805937e1f1397f8b22a53c6bfd86360704b429d5bbb345ca7fd7d95c6e5442f158de921aaccafa07f4c41311122e9bc8e0e87ee2973d5c885480e2dc77be1c30f116ab7fcba166abcfde1107c234e05d1ebcfb727e7b0a9d22d236554ec4e9c56ef7ed92fa0d21624a13a1471634f650eb03fa75" +} \ No newline at end of file diff --git a/paillier-zk/test-data/someone_encryption_key.json b/paillier-zk/test-data/someone_encryption_key.json new file mode 100644 index 00000000..1ae4e7b1 --- /dev/null +++ b/paillier-zk/test-data/someone_encryption_key.json @@ -0,0 +1,4 @@ +{ + "radix": 16, + "value": "a04a3d1cae08f3a43956462348abe1ed8bc5c2fc72ccc5584fc69cbbf4310ec57229a0f043d4aaaa71b33dec4ff8820640ff2d4be89872868744f73a3938432c14c4fc6918d05256751b3ef30df6407fc6beb2394a710e2c57489bf888a48f9985427337c816e13f354edb05a17181e271834f78481a60c55fb7311612ef855423b52b8976d2bac6219eaf1bdb4eb58992b3aef1a06ae9ca984935f829ec50ad1f3d572be12ae25f34b47e467b35db6e172af4041d1fe499e015b72f4e9973550652c1cba86b9bd6fdd4a269823940cf6a9837a8cc6f6ca06a326b74618d67be8958caafb7124551deb0da3725e808970248a420154efcfa67d33d488c03a8d5dbc27bb0f2dc74d4571c3ad2605a630fced42e26ae5a4e407265973bb37d93149ca14d584de3eeaf447d3690d7985c4a1d3d57522dc3f475a10ff6c3fa4c7ab1432c78446b25603f23228878c0691f7431afb991d306c80bd8125815d1f3c5ed74c65fb6a7303bddbdaf167a29db01f137e506c59adb28755b16b4b84252fc19" +} \ No newline at end of file diff --git a/paillier-zk/test-data/someone_encryption_key0.json b/paillier-zk/test-data/someone_encryption_key0.json new file mode 100644 index 00000000..1be46f62 --- /dev/null +++ b/paillier-zk/test-data/someone_encryption_key0.json @@ -0,0 +1,4 @@ +{ + "radix": 16, + "value": "73461ebbb771ccb13f8221bd603c24fc2994ff5648453d13665b7296e9742422f1accc6c12b13cde6c447bfa9f375d48635b0adc023e5a1ba113c4e461af2805374abe36e5613fe4aa8afb6ff07d6859d4af5a8c82dc3cfb70c5078f4e4f8406dc4ff1106e0f5c2e7f6e9fdff89510db129ae0d67f00bc3e7541e3fa145087d14a187fccb83778c0148e582312e20dac0f480a089e3f18e01f36cd5abdcc3befa8f440d1b498658165b2eb2007a182e0feddefbc829af28397dbe54a3f02c179d54776d4f6596d1e49a1f3df5a5f5a43a91d85a34f26e6fa0165f519dca0d35d6faa5749e8d971e71396cbb4fdd22578a15e1a52da2363ff8468e9cd08d7d5ff76e9c9bcb8ccd9f93574d43599b1959f5702025b029f2b95afe976a4675fc073e52042cec377f8cd68ae7b5cb0b8d74b90b2212e16e3122e6a9affeea91ae6e7246f3020fe66a941d093a034e9681f03bc4ffe71705b45306517ef9f57e14f8a15269be47c74ff84e0addc5efc2c3ed7b564fb1aa15b29540e09f38bbaabd845" +} \ No newline at end of file diff --git a/paillier-zk/test-data/someone_encryption_key1.json b/paillier-zk/test-data/someone_encryption_key1.json new file mode 100644 index 00000000..144a4bb9 --- /dev/null +++ b/paillier-zk/test-data/someone_encryption_key1.json @@ -0,0 +1,4 @@ +{ + "radix": 16, + "value": "7344ad458435f1da9394d9b1565b2b7e9e07b05f1f7dc2fa1779f20cc7eb0e879110a4623e13f8d9adb4e999383b23ddb0471dffb7342e8e9a8c038e0ed958d2324e1f1f40f001b77a59031fd867f58e7a1fa8e0125ccbc27ad0c778388dbabd18277145da57303024211bace1b8d5979e63793c0771d4ff910ebf2c622888c6aacd68e6f9e468f3d5bfcc3a943c4f17150357c441883d13da611839defee95db542c2245be89a2826f1a5c9d40e58b7ceba5129cea2de5737c1292b523b2d027eb582a9326eb71de1c8e01cb1a58437c8524e3322b766df79fe24f7c7560b3f9a5ec23e6438ef60ff567459d1c888ea3880a5a36f69c2a61073c1c9882b4e778f2b107e72f78215a94f1a14a9af068b8cb8b46c999a548cd83e3b8c4ba88053a4f690bd4059a6f05242c81b3d952f75c40920a337a6b5e368f2c301190fc61dfa597e21e93fa6f30572a7e00e5790b990632390ac14527c96e7eca1cf4ea0342cc3973761441de36c17e3c096ac83986e5c77acc4824e38489c01444b683181" +} \ No newline at end of file diff --git a/paillier-zk/test-data/verifier_aux.json b/paillier-zk/test-data/verifier_aux.json new file mode 100644 index 00000000..9d5ca469 --- /dev/null +++ b/paillier-zk/test-data/verifier_aux.json @@ -0,0 +1,14 @@ +{ + "s": { + "radix": 16, + "value": "19501d79a43e6e003ca72e2c99f2d4a2ff1ba6c3250d3e1a2b86cb7d818c6e1218f2e08b4af1d56f390394e3ac7cdb9e38bf06eb711585742278ac2dda709055b6a39457c5cba2c3a47f55828eb286219016194448a631f2287efab897fb27139b2ee92ddecc10245f0363b70b3216852a3e95ea8c27d3475f34ab60f54f21ca82b57117d7607cf8041a979d423bc948c55b1576ce69ce96563200a581a7230972e570099e57a54d641c4685a4ce2afc880f99c7a2194156d23dfcfb42db29b14a4beef9625b3a2e1b3009a53c0d03a908e57d73149c6a2a369469e0301b10792131ae83301e05def0bef69f6c4c74ada914bf489fe76068cf630d80cbdf3d2f" + }, + "t": { + "radix": 16, + "value": "61a268cee57c9fd2bd39d0146f59729d8be9cfba643518c03b8b1879242312337bccc20242031a662961498828152909557b0c764745fb896dbd53cb9322a6d7039049e3ff613e41e35d7e41516c770837261c710d13f60d1ec4e1743fc5e6bc2c0bb542634b3f230b2ee5900ebf924cf2b2258a2e1cc2cef48f6742509db347205fa83cd92b76d00497b7c7dc31853c746a1e8b2bc1e9cfecd294f8b35ad4acac48261b56785e2352d819ec3c0a69253b8668c47015f9858852d36f6bc2db2feec8d6225e315ce961eacee1d58e8a77d7cdde652dcef47daa36cdd3d948f5da9f2f5300523d9331f6a0f5aa8faea671ae9cc21bab4f2688aa2b8f41793afd7f" + }, + "rsa_modulo": { + "radix": 16, + "value": "7ee30703727ee967c71109af4f334d859d31478e3aa286a8fbcec6d3936f05a78c75214ba80a0cf35bc27c263360c2bc31dd51c8599651b810e69dcac2d5e5c504653a755c36a938c949c360b2584d2b09cd5142f26c0b352bc501983a324deba4f2099a99f0ad7d21cfa9429c61ecffd5f24049d6ad2da2374850ec1691c02c2b04282a8b4196cca7a697b4932a78297ed7c15c6dc5901a542e9dfc4444b60f478e287a6ee33ee509edfc62fe3d3890116c0ccf8e69034173b5f6374463b5a0dc2745768e2973ef2347bc6ab156145ab3a5fd770dc0e0a4ee70a02c5ef3cecc02f3b36d24e7788339e58609cd9dde436233b82597e4982ed0b89ab56eee6fcd" + } +} \ No newline at end of file From f2d1e112bb04f360fd581660aba7c5d5fa35495f Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Wed, 11 Dec 2024 18:16:51 +0100 Subject: [PATCH 2/2] Update paillier-zk repo address Signed-off-by: Denis Varlakov --- paillier-zk/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paillier-zk/Cargo.toml b/paillier-zk/Cargo.toml index 790892bb..f136298d 100644 --- a/paillier-zk/Cargo.toml +++ b/paillier-zk/Cargo.toml @@ -4,7 +4,7 @@ version = "0.4.2" edition = "2021" license = "MIT OR Apache-2.0" description = "ZK-proofs for Paillier encryption scheme" -repository = "https://github.com/LFDT-Lockness/paillier-zk" +repository = "https://github.com/LFDT-Lockness/cggmp21" categories = ["algorithms", "cryptography"] keywords = ["paillier", "zk-proofs", "zero-knowledge"]