diff --git a/src/add_party_message.rs b/src/add_party_message.rs index 893dfc1..e63170a 100644 --- a/src/add_party_message.rs +++ b/src/add_party_message.rs @@ -44,7 +44,7 @@ pub struct JoinMessage { /// environment variables for each party that they agree on. In this case, each new party generates /// it's own DlogStatements and submits it's proofs fn generate_h1_h2_n_tilde() -> (BigInt, BigInt, BigInt, BigInt, BigInt) { - let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + let (ek_tilde, dk_tilde) = Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE).keys(); let one = BigInt::one(); let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); let h1 = BigInt::sample_below(&ek_tilde.n); diff --git a/src/error.rs b/src/error.rs index d33cc1c..c4dda35 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,18 +15,28 @@ pub enum FsDkrError { #[error("Shares did not pass verification.")] PublicShareValidationError, - #[error("SizeMismatch error for the refresh message {refresh_message_index:?} - Fairness proof length: {fairness_proof_len:?}, Points Commited Length: {points_commited_len:?}, Points Encrypted Length: {points_encrypted_len:?}")] + #[error("SizeMismatch error for the refresh message {refresh_message_index:?} - pdl proof length: {pdl_proof_len:?}, Points Commited Length: {points_commited_len:?}, Points Encrypted Length: {points_encrypted_len:?}")] SizeMismatchError { refresh_message_index: usize, - fairness_proof_len: usize, + pdl_proof_len: usize, points_commited_len: usize, points_encrypted_len: usize, }, - #[error("Fairness proof verification failed, results - T_add_e_Y == z_G: {t_add_eq_z_g:?} - e_u_add_c_e == enc_z_w: {e_u_add_eq_z_w:?}")] - FairnessProof { - t_add_eq_z_g: bool, - e_u_add_eq_z_w: bool, + #[error("PDLwSlack proof verification failed, results: u1 == u1_test: {is_u1_eq:?}, u2 == u2_test: {is_u2_eq:?}, u3 == u3_test: {is_u3_eq:?}")] + PDLwSlackProof { + is_u1_eq: bool, + is_u2_eq: bool, + is_u3_eq: bool, + }, + + #[error("Range Proof failed for party: {party_index:?}")] + RangeProof { party_index: usize }, + + #[error("The Paillier moduli size of party: {party_index:?} is {moduli_size:?} bits, when it should be 2047-2048 bits")] + MouliTooSmall { + party_index: usize, + moduli_size: usize, }, #[error("Paillier verification proof failed for party {party_index:?}")] diff --git a/src/lib.rs b/src/lib.rs index b27dbdd..01a3fe6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,9 @@ pub mod add_party_message; pub mod error; -pub mod proof_of_fairness; +pub mod range_proofs; pub mod refresh_message; mod test; +pub mod zk_pdl_with_slack; + +pub(crate) const PAILLIER_KEY_SIZE: usize = 2048; diff --git a/src/proof_of_fairness.rs b/src/proof_of_fairness.rs deleted file mode 100644 index 1f6dd96..0000000 --- a/src/proof_of_fairness.rs +++ /dev/null @@ -1,197 +0,0 @@ -#![allow(non_snake_case)] -use crate::error::{FsDkrError, FsDkrResult}; - -use curv::BigInt; -use paillier::Paillier; -use paillier::{Add, EncryptWithChosenRandomness, Mul, RawCiphertext}; -use paillier::{EncryptionKey, Randomness, RawPlaintext}; - -use curv::arithmetic::{Modulo, Samplable}; -use curv::cryptographic_primitives::hashing::hash_sha256::HSha256; -use curv::cryptographic_primitives::hashing::traits::Hash; -use curv::elliptic::curves::traits::*; -use serde::{Deserialize, Serialize}; -use zeroize::Zeroize; - -/// non interactive proof of fairness, taken from - -/// Witness: x -/// -/// Statement: {c, Y} such that c = g^x * r^N mod N^2 and Y = x*G -/// -/// Protocol: -/// -/// 1. P picks random values u from Z_n, s from Z_n* -/// and computes e_u = g^u * s^N mod N^2 , T = u*G -/// 2. using Fiat-Shamir the parties computes a challenge e -/// 3. P sends z = u + ex , w = s* r^e mod N^2 -/// 4. V checks: -/// T = z*G - e*Y -/// e_u = g^z * w^N * c^{-e} mod N^2 -/// -/// note: we need u to hide ex : |u| > |ex| + SEC_PARAM, taking u from Z_n works assuming -/// n = 2048, |x| < 256, |e| < 256 - -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct FairnessProof

{ - pub e_u: BigInt, - pub T: P, - pub z: BigInt, - pub w: BigInt, -} - -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct FairnessWitness { - pub x: S, - pub r: BigInt, -} - -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] -pub struct FairnessStatement

{ - pub ek: EncryptionKey, - pub c: BigInt, - pub Y: P, -} - -impl

FairnessProof

-where - P: ECPoint + Clone + Zeroize, - P::Scalar: PartialEq + Clone + Zeroize, -{ - pub fn prove(witness: &FairnessWitness, statement: &FairnessStatement

) -> Self { - let u = BigInt::sample_below(&statement.ek.n); - let s = BigInt::sample_below(&statement.ek.n); - - let e_u = Paillier::encrypt_with_chosen_randomness( - &statement.ek, - RawPlaintext::from(u.clone()), - &Randomness(s.clone()), - ) - .0 - .into_owned(); - let u_fe: P::Scalar = ECScalar::from(&u); - let T = P::generator() * u_fe; - - let e = HSha256::create_hash(&[ - &T.bytes_compressed_to_big_int(), - &e_u, - &statement.c, - &statement.ek.n, - &statement.Y.bytes_compressed_to_big_int(), - ]); - - let z = u + &e * &witness.x.to_big_int(); - let r_x_e = BigInt::mod_pow(&witness.r, &e, &statement.ek.nn); - let w = BigInt::mod_mul(&r_x_e, &s, &statement.ek.nn); - FairnessProof { e_u, T, z, w } - } - - pub fn verify(&self, statement: &FairnessStatement

) -> FsDkrResult<()> - where - P: ECPoint + Clone + Zeroize, - P::Scalar: PartialEq + Clone + Zeroize, - { - let e = HSha256::create_hash(&[ - &self.T.bytes_compressed_to_big_int(), - &self.e_u, - &statement.c, - &statement.ek.n, - &statement.Y.bytes_compressed_to_big_int(), - ]); - - let enc_z_w = Paillier::encrypt_with_chosen_randomness( - &statement.ek, - RawPlaintext::from(self.z.clone()), - &Randomness(self.w.clone()), - ) - .0 - .into_owned(); - let c_e = Paillier::mul( - &statement.ek, - RawCiphertext::from(statement.c.clone()), - RawPlaintext::from(e.clone()), - ); - let e_u_add_c_e = Paillier::add(&statement.ek, RawCiphertext::from(self.e_u.clone()), c_e) - .0 - .into_owned(); - - let z_fe: P::Scalar = ECScalar::from(&self.z); - let z_G = P::generator() * z_fe; - let e_fe: P::Scalar = ECScalar::from(&e); - let e_Y = statement.Y.clone() * e_fe; - let T_add_e_Y = e_Y + self.T.clone(); - - match T_add_e_Y == z_G && e_u_add_c_e == enc_z_w { - true => Ok(()), - false => Err(FsDkrError::FairnessProof { - t_add_eq_z_g: T_add_e_Y == z_G, - e_u_add_eq_z_w: e_u_add_c_e == enc_z_w, - }), - } - } -} - -#[cfg(test)] -mod tests { - use crate::proof_of_fairness::{FairnessProof, FairnessStatement, FairnessWitness}; - use curv::arithmetic::{One, Samplable}; - use curv::elliptic::curves::secp256_k1::{FE, GE}; - use curv::elliptic::curves::traits::{ECPoint, ECScalar}; - use curv::BigInt; - use paillier::{ - EncryptWithChosenRandomness, KeyGeneration, Paillier, Randomness, RawPlaintext, - }; - - #[test] - fn test_fairness_proof() { - let (ek, _) = Paillier::keypair().keys(); - let x: FE = ECScalar::new_random(); - let x_bn = x.to_big_int(); - let r = BigInt::sample_below(&ek.n); - - let c = Paillier::encrypt_with_chosen_randomness( - &ek, - RawPlaintext::from(x_bn), - &Randomness(r.clone()), - ) - .0 - .into_owned(); - - let Y = GE::generator() * x; - - let witness = FairnessWitness { x, r }; - - let statement = FairnessStatement { ek, c, Y }; - - let proof = FairnessProof::prove(&witness, &statement); - let verify = proof.verify(&statement); - assert!(verify.is_ok()); - } - - #[should_panic] - #[test] - fn test_bad_fairness_proof() { - let (ek, _) = Paillier::keypair().keys(); - let x: FE = ECScalar::new_random(); - let x_bn = x.to_big_int(); - let r = BigInt::sample_below(&ek.n); - - let c = Paillier::encrypt_with_chosen_randomness( - &ek, - RawPlaintext::from(x_bn + BigInt::one()), - &Randomness(r.clone()), - ) - .0 - .into_owned(); - - let Y = GE::generator() * x; - - let witness = FairnessWitness { x, r }; - - let statement = FairnessStatement { ek, c, Y }; - - let proof = FairnessProof::prove(&witness, &statement); - let verify = proof.verify(&statement); - assert!(verify.is_ok()); - } -} diff --git a/src/range_proofs.rs b/src/range_proofs.rs new file mode 100644 index 0000000..b0b093c --- /dev/null +++ b/src/range_proofs.rs @@ -0,0 +1,732 @@ +#![allow(non_snake_case)] + +//! This file is a modified version of ING bank's range proofs implementation: +//! https://github.com/ing-bank/threshold-signatures/blob/master/src/algorithms/zkp.rs +//! +//! Zero knowledge range proofs for MtA protocol are implemented here. +//! Formal description can be found in Appendix A of https://eprint.iacr.org/2019/114.pdf +//! There are some deviations from the original specification: +//! 1) In Bob's proofs `gamma` is sampled from `[0;q^2 * N]` and `tau` from `[0;q^3 * N_tilde]`. +//! 2) A non-interactive version is implemented, with challenge `e` computed via Fiat-Shamir. + +use curv::arithmetic::traits::*; +use curv::cryptographic_primitives::hashing::hash_sha256::HSha256; +use curv::cryptographic_primitives::hashing::traits::Hash; +use curv::elliptic::curves::traits::{ECPoint, ECScalar}; +use curv::BigInt; + +use paillier::{EncryptionKey, Randomness}; +use zk_paillier::zkproofs::DLogStatement; + +use serde::{Deserialize, Serialize}; +use std::borrow::Borrow; +use std::marker::PhantomData; +use zeroize::Zeroize; + +/// Represents the first round of the interactive version of the proof +#[derive(Zeroize)] +#[zeroize(drop)] +struct AliceZkpRound1 { + alpha: BigInt, + beta: BigInt, + gamma: BigInt, + ro: BigInt, + z: BigInt, + u: BigInt, + w: BigInt, +} + +impl AliceZkpRound1 { + fn from( + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + a: &BigInt, + q: &BigInt, + ) -> Self { + assert!( + q.bit_length() <= 256, + "We use SHA256 so we don't currently support moduli bigger than 256" + ); + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let N_tilde = &dlog_statement.N; + let alpha = BigInt::sample_below(&q.pow(3)); + let beta = BigInt::from_paillier_key(alice_ek); + let gamma = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let ro = BigInt::sample_below(&(q * N_tilde)); + let z = (BigInt::mod_pow(h1, &a, N_tilde) * BigInt::mod_pow(h2, &ro, N_tilde)) % N_tilde; + let u = ((alpha.borrow() * &alice_ek.n + 1) + * BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) + % &alice_ek.nn; + let w = + (BigInt::mod_pow(h1, &alpha, N_tilde) * BigInt::mod_pow(h2, &gamma, N_tilde)) % N_tilde; + Self { + alpha, + beta, + gamma, + ro, + z, + u, + w, + } + } +} + +/// Represents the second round of the interactive version of the proof +struct AliceZkpRound2 { + s: BigInt, + s1: BigInt, + s2: BigInt, +} + +impl AliceZkpRound2 { + fn from( + alice_ek: &EncryptionKey, + round1: &AliceZkpRound1, + e: &BigInt, + a: &BigInt, + r: &BigInt, + ) -> Self { + Self { + s: (BigInt::mod_pow(r, &e, &alice_ek.n) * round1.beta.borrow()) % &alice_ek.n, + s1: (e * a) + round1.alpha.borrow(), + s2: (e * round1.ro.borrow()) + round1.gamma.borrow(), + } + } +} + +/// Alice's proof +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AliceProof

{ + z: BigInt, + e: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, + _phantom: PhantomData

, +} + +impl AliceProof

{ + /// verify Alice's proof using the proof and public keys + pub fn verify( + &self, + cipher: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + ) -> bool { + let N = &alice_ek.n; + let NN = &alice_ek.nn; + let N_tilde = &dlog_statement.N; + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let Gen = alice_ek.n.borrow() + 1; + + if self.s1 > P::Scalar::q().pow(3) { + return false; + } + + let z_e_inv = BigInt::mod_inv(&BigInt::mod_pow(&self.z, &self.e, N_tilde), N_tilde); + let z_e_inv = match z_e_inv { + // z must be invertible, yet the check is done here + None => return false, + Some(c) => c, + }; + + let w = (BigInt::mod_pow(h1, &self.s1, N_tilde) + * BigInt::mod_pow(h2, &self.s2, N_tilde) + * z_e_inv) + % N_tilde; + + let gs1 = (self.s1.borrow() * N + 1) % NN; + let cipher_e_inv = BigInt::mod_inv(&BigInt::mod_pow(&cipher, &self.e, NN), NN); + let cipher_e_inv = match cipher_e_inv { + None => return false, + Some(c) => c, + }; + + let u = (gs1 * BigInt::mod_pow(&self.s, N, NN) * cipher_e_inv) % NN; + + let e = HSha256::create_hash(&[N, &Gen, cipher, &self.z, &u, &w]); + if e != self.e { + return false; + } + + true + } + /// Create the proof using Alice's Paillier private keys and public ZKP setup. + /// Requires randomness used for encrypting Alice's secret a. + /// It is assumed that a curve order smaller than 2^256 is used.. + pub fn generate( + a: &BigInt, + cipher: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &BigInt, + ) -> Self { + let q = P::Scalar::q(); + assert!( + q.bit_length() <= 256, + "We use SHA256 so we don't currently support moduli bigger than 256" + ); + let round1 = AliceZkpRound1::from(alice_ek, dlog_statement, a, &q); + + let Gen = alice_ek.n.borrow() + 1; + let e = HSha256::create_hash(&[&alice_ek.n, &Gen, cipher, &round1.z, &round1.u, &round1.w]); + + let round2 = AliceZkpRound2::from(alice_ek, &round1, &e, a, r); + + Self { + z: round1.z.clone(), + e, + s: round2.s, + s1: round2.s1, + s2: round2.s2, + _phantom: PhantomData, + } + } +} + +/// Represents first round of the interactive version of the proof +struct BobZkpRound1 { + pub alpha: BigInt, + pub beta: BigInt, + pub gamma: BigInt, + pub ro: BigInt, + pub ro_prim: BigInt, + pub sigma: BigInt, + pub tau: BigInt, + pub z: BigInt, + pub z_prim: BigInt, + pub t: BigInt, + pub w: BigInt, + pub v: BigInt, + _phantom: PhantomData, +} + +impl Zeroize for BobZkpRound1 { + fn zeroize(&mut self) { + self.alpha.zeroize(); + self.beta.zeroize(); + self.gamma.zeroize(); + self.ro.zeroize(); + self.ro_prim.zeroize(); + self.sigma.zeroize(); + self.tau.zeroize(); + self.z.zeroize(); + self.z_prim.zeroize(); + self.t.zeroize(); + self.w.zeroize(); + self.v.zeroize(); + } +} + +impl Drop for BobZkpRound1 { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl BobZkpRound1 { + /// `b` - Bob's secret + /// `beta_prim` - randomly chosen in `MtA` by Bob + /// `a_encrypted` - Alice's secret encrypted by Alice + fn from( + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + b: &S, + beta_prim: &BigInt, + a_encrypted: &BigInt, + q: &BigInt, + ) -> Self { + assert!( + q.bit_length() <= 256, + "We use SHA256 so we don't currently support moduli bigger than 256" + ); + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let N_tilde = &dlog_statement.N; + let b_bn = b.to_big_int(); + + let alpha = BigInt::sample_below(&q.pow(3)); + let beta = BigInt::from_paillier_key(alice_ek); + let gamma = BigInt::sample_below(&(q.pow(2) * &alice_ek.n)); + let ro = BigInt::sample_below(&(q * N_tilde)); + let ro_prim = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let sigma = BigInt::sample_below(&(q * N_tilde)); + let tau = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let z = (BigInt::mod_pow(h1, &b_bn, N_tilde) * BigInt::mod_pow(h2, &ro, N_tilde)) % N_tilde; + let z_prim = (BigInt::mod_pow(h1, &alpha, N_tilde) + * BigInt::mod_pow(h2, &ro_prim, N_tilde)) + % N_tilde; + let t = (BigInt::mod_pow(h1, beta_prim, N_tilde) * BigInt::mod_pow(h2, &sigma, N_tilde)) + % N_tilde; + let w = + (BigInt::mod_pow(h1, &gamma, N_tilde) * BigInt::mod_pow(h2, &tau, N_tilde)) % N_tilde; + let v = (BigInt::mod_pow(a_encrypted, &alpha, &alice_ek.nn) + * (gamma.borrow() * &alice_ek.n + 1) + * BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) + % &alice_ek.nn; + Self { + alpha, + beta, + gamma, + ro, + ro_prim, + sigma, + tau, + z, + z_prim, + t, + w, + v, + _phantom: PhantomData, + } + } +} + +/// represents second round of the interactive version of the proof +struct BobZkpRound2 { + pub s: BigInt, + pub s1: BigInt, + pub s2: BigInt, + pub t1: BigInt, + pub t2: BigInt, + _phantom: PhantomData, +} + +impl BobZkpRound2 { + /// `e` - the challenge in interactive ZKP, the hash in non-interactive ZKP + /// `b` - Bob's secret + /// `beta_prim` - randomly chosen in `MtA` by Bob + /// `r` - randomness used by Bob on Alice's public Paillier key to encrypt `beta_prim` in `MtA` + fn from( + alice_ek: &EncryptionKey, + round1: &BobZkpRound1, + e: &BigInt, + b: &S, + beta_prim: &BigInt, + r: &Randomness, + ) -> Self { + let b_bn = b.to_big_int(); + Self { + s: (BigInt::mod_pow(r.0.borrow(), e, &alice_ek.n) * round1.beta.borrow()) % &alice_ek.n, + s1: (e * b_bn) + round1.alpha.borrow(), + s2: (e * round1.ro.borrow()) + round1.ro_prim.borrow(), + t1: (e * beta_prim) + round1.gamma.borrow(), + t2: (e * round1.sigma.borrow()) + round1.tau.borrow(), + _phantom: PhantomData, + } + } +} + +/// Additional fields in Bob's proof if MtA is run with check +pub struct BobCheck

{ + u: P, + X: P, +} + +/// Bob's regular proof +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct BobProof

{ + t: BigInt, + z: BigInt, + e: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, + t1: BigInt, + t2: BigInt, + _phantom: PhantomData

, +} + +#[allow(clippy::too_many_arguments)] +impl

BobProof

+where + P: ECPoint + Clone, + P::Scalar: Clone, +{ + pub fn verify( + &self, + a_enc: &BigInt, + mta_avc_out: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + check: Option<&BobCheck

>, + ) -> bool { + let N = &alice_ek.n; + let NN = &alice_ek.nn; + let N_tilde = &dlog_statement.N; + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + + if self.s1 > P::Scalar::q().pow(3) { + return false; + } + + let z_e_inv = BigInt::mod_inv(&BigInt::mod_pow(&self.z, &self.e, N_tilde), N_tilde); + let z_e_inv = match z_e_inv { + // z must be invertible, yet the check is done here + None => return false, + Some(c) => c, + }; + + let z_prim = (BigInt::mod_pow(h1, &self.s1, N_tilde) + * BigInt::mod_pow(h2, &self.s2, N_tilde) + * z_e_inv) + % N_tilde; + + let mta_e_inv = BigInt::mod_inv(&BigInt::mod_pow(mta_avc_out, &self.e, NN), NN); + let mta_e_inv = match mta_e_inv { + None => return false, + Some(c) => c, + }; + + let v = (BigInt::mod_pow(a_enc, &self.s1, NN) + * BigInt::mod_pow(&self.s, N, NN) + * (self.t1.borrow() * N + 1) + * mta_e_inv) + % NN; + + let t_e_inv = BigInt::mod_inv(&BigInt::mod_pow(&self.t, &self.e, N_tilde), N_tilde); + let t_e_inv = match t_e_inv { + None => return false, + Some(c) => c, + }; + + let w = (BigInt::mod_pow(h1, &self.t1, N_tilde) + * BigInt::mod_pow(h2, &self.t2, N_tilde) + * t_e_inv) + % N_tilde; + + let Gen = alice_ek.n.borrow() + 1; + let mut values_to_hash = vec![ + &alice_ek.n, + &Gen, + a_enc, + mta_avc_out, + &self.z, + &z_prim, + &self.t, + &v, + &w, + ]; + let e = match check { + Some(_) => { + let X_x_coor = check.unwrap().X.x_coor().unwrap(); + values_to_hash.push(&X_x_coor); + let X_y_coor = check.unwrap().X.y_coor().unwrap(); + values_to_hash.push(&X_y_coor); + let u_x_coor = check.unwrap().u.x_coor().unwrap(); + values_to_hash.push(&u_x_coor); + let u_y_coor = check.unwrap().u.y_coor().unwrap(); + values_to_hash.push(&u_y_coor); + HSha256::create_hash(&values_to_hash[..]) + } + None => HSha256::create_hash(&values_to_hash[..]), + }; + + if e != self.e { + return false; + } + + true + } + + pub fn generate( + a_encrypted: &BigInt, + mta_encrypted: &BigInt, + b: &P::Scalar, + beta_prim: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &Randomness, + check: bool, + ) -> (BobProof

, Option

) { + let round1 = BobZkpRound1::from( + alice_ek, + dlog_statement, + b, + beta_prim, + a_encrypted, + &P::Scalar::q(), + ); + + let Gen = alice_ek.n.borrow() + 1; + let mut values_to_hash = vec![ + &alice_ek.n, + &Gen, + a_encrypted, + mta_encrypted, + &round1.z, + &round1.z_prim, + &round1.t, + &round1.v, + &round1.w, + ]; + let mut check_u = None; + let e = if check { + let (X, u) = { + let ec_gen: P = ECPoint::generator(); + let alpha: P::Scalar = ECScalar::from(&round1.alpha); + (ec_gen.clone() * b.clone(), ec_gen.clone() * alpha) + }; + check_u = Some(u.clone()); + let X_x_coor = X.x_coor().unwrap(); + values_to_hash.push(&X_x_coor); + let X_y_coor = X.y_coor().unwrap(); + values_to_hash.push(&X_y_coor); + let u_x_coor = u.x_coor().unwrap(); + values_to_hash.push(&u_x_coor); + let u_y_coor = u.y_coor().unwrap(); + values_to_hash.push(&u_y_coor); + HSha256::create_hash(&values_to_hash[..]) + } else { + HSha256::create_hash(&values_to_hash[..]) + }; + + let round2 = BobZkpRound2::from(alice_ek, &round1, &e, b, beta_prim, r); + + ( + BobProof { + t: round1.t.clone(), + z: round1.z.clone(), + e, + s: round2.s, + s1: round2.s1, + s2: round2.s2, + t1: round2.t1, + t2: round2.t2, + _phantom: PhantomData, + }, + check_u, + ) + } +} + +/// Bob's extended proof, adds the knowledge of $`B = g^b \in \mathcal{G}`$ +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct BobProofExt { + proof: BobProof

, + u: P, +} + +#[allow(clippy::too_many_arguments)] +impl BobProofExt

+where + P: ECPoint + Clone, + P::Scalar: Clone, +{ + pub fn verify( + &self, + a_enc: &BigInt, + mta_avc_out: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + X: &P, + ) -> bool { + // check basic proof first + if !self.proof.verify( + a_enc, + mta_avc_out, + alice_ek, + dlog_statement, + Some(&BobCheck { + u: self.u.clone(), + X: X.clone(), + }), + ) { + return false; + } + + // fiddle with EC points + let (x1, x2) = { + let ec_gen: P = ECPoint::generator(); + let s1: P::Scalar = ECScalar::from(&self.proof.s1); + let e: P::Scalar = ECScalar::from(&self.proof.e); + (ec_gen * s1, (X.clone() * e) + self.u.clone()) + }; + + if x1 != x2 { + return false; + } + + true + } + + fn generate( + a_encrypted: &BigInt, + mta_encrypted: &BigInt, + b: &P::Scalar, + beta_prim: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &Randomness, + ) -> BobProofExt

{ + // proving a basic proof (with modified hash) + let (bob_proof, u) = BobProof::generate( + a_encrypted, + mta_encrypted, + b, + beta_prim, + alice_ek, + dlog_statement, + r, + true, + ); + + BobProofExt { + proof: bob_proof, + u: u.unwrap(), + } + } +} + +/// sample random value of an element of a multiplicative group +pub trait SampleFromMultiplicativeGroup { + fn from_modulo(N: &BigInt) -> BigInt; + fn from_paillier_key(ek: &EncryptionKey) -> BigInt; +} + +impl SampleFromMultiplicativeGroup for BigInt { + fn from_modulo(N: &BigInt) -> BigInt { + let One = BigInt::one(); + loop { + let r = Self::sample_below(N); + if r.gcd(N) == One { + return r; + } + } + } + + fn from_paillier_key(ek: &EncryptionKey) -> BigInt { + Self::from_modulo(ek.n.borrow()) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use curv::elliptic::curves::secp256_k1::{FE, GE}; + use paillier::traits::{Encrypt, EncryptWithChosenRandomness, KeyGeneration}; + use paillier::{Add, DecryptionKey, Mul, Paillier, RawCiphertext, RawPlaintext}; + + pub(crate) fn generate_init() -> (DLogStatement, EncryptionKey, DecryptionKey) { + let (ek_tilde, dk_tilde) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE).keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&ek_tilde.n); + let (xhi, _) = loop { + let xhi_ = BigInt::sample_below(&phi); + match BigInt::mod_inv(&xhi_, &phi) { + Some(inv) => break (xhi_, inv), + None => continue, + } + }; + let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); + + let (ek, dk) = Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE).keys(); + let dlog_statement = DLogStatement { + g: h1, + ni: h2, + N: ek_tilde.n, + }; + (dlog_statement, ek, dk) + } + + #[test] + fn alice_zkp() { + let (dlog_statement, ek, _) = generate_init(); + + // Alice's secret value + let a = FE::new_random().to_big_int(); + let r = BigInt::from_paillier_key(&ek); + let cipher = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(a.clone()), + &Randomness::from(&r), + ) + .0 + .clone() + .into_owned(); + + let alice_proof = AliceProof::::generate(&a, &cipher, &ek, &dlog_statement, &r); + + assert!(alice_proof.verify(&cipher, &ek, &dlog_statement)); + } + + #[test] + fn bob_zkp() { + let (dlog_statement, ek, _) = generate_init(); + + (0..5).for_each(|_| { + let alice_public_key = &ek; + + // run MtA protocol with different inputs + (0..5).for_each(|_| { + // Simulate Alice + let a = FE::new_random().to_big_int(); + let encrypted_a = Paillier::encrypt(alice_public_key, RawPlaintext::from(a)) + .0 + .clone() + .into_owned(); + + // Bob follows MtA + let b = FE::new_random(); + // E(a) * b + let b_times_enc_a = Paillier::mul( + alice_public_key, + RawCiphertext::from(encrypted_a.clone()), + RawPlaintext::from(&b.to_big_int()), + ); + let beta_prim = BigInt::sample_below(&alice_public_key.n); + let r = Randomness::sample(alice_public_key); + let enc_beta_prim = Paillier::encrypt_with_chosen_randomness( + alice_public_key, + RawPlaintext::from(&beta_prim), + &r, + ); + + let mta_out = Paillier::add(alice_public_key, b_times_enc_a, enc_beta_prim); + + let (bob_proof, _) = BobProof::::generate( + &encrypted_a, + &mta_out.0.clone().into_owned(), + &b, + &beta_prim, + alice_public_key, + &dlog_statement, + &r, + false, + ); + assert!(bob_proof.verify( + &encrypted_a, + &mta_out.0.clone().into_owned(), + alice_public_key, + &dlog_statement, + None + )); + + // Bob follows MtAwc + let ec_gen = GE::generator(); + let X = ec_gen * b; + let bob_proof = BobProofExt::generate( + &encrypted_a, + &mta_out.0.clone().into_owned(), + &b, + &beta_prim, + alice_public_key, + &dlog_statement, + &r, + ); + assert!(bob_proof.verify( + &encrypted_a, + &mta_out.0.clone().into_owned(), + alice_public_key, + &dlog_statement, + &X + )); + }); + }); + } +} diff --git a/src/refresh_message.rs b/src/refresh_message.rs index e26077a..e821cae 100644 --- a/src/refresh_message.rs +++ b/src/refresh_message.rs @@ -1,7 +1,8 @@ use crate::add_party_message::JoinMessage; use crate::error::{FsDkrError, FsDkrResult}; -use crate::proof_of_fairness::{FairnessProof, FairnessStatement, FairnessWitness}; -use curv::arithmetic::{Samplable, Zero}; +use crate::range_proofs::AliceProof; +use crate::zk_pdl_with_slack::{PDLwSlackProof, PDLwSlackStatement, PDLwSlackWitness}; +use curv::arithmetic::{BitManipulation, Samplable, Zero}; use curv::cryptographic_primitives::secret_sharing::feldman_vss::{ ShamirSecretSharing, VerifiableSS, }; @@ -23,7 +24,8 @@ use zk_paillier::zkproofs::{DLogStatement, NICorrectKeyProof, SALT_STRING}; #[derive(Clone, Deserialize, Serialize)] pub struct RefreshMessage

{ pub(crate) party_index: usize, - fairness_proof_vec: Vec>, + pdl_proof_vec: Vec>, + range_proofs: Vec>, coefficients_committed_vec: VerifiableSS

, pub(crate) points_committed_vec: Vec

, points_encrypted_vec: Vec, @@ -65,29 +67,46 @@ impl

RefreshMessage

{ }) .unzip(); - // generate proof of fairness for each {point_committed, point_encrypted} pair - let fairness_proof_vec: Vec<_> = (0..secret_shares.len()) + // generate PDL proofs for each {point_committed, point_encrypted} pair + let pdl_proof_vec: Vec<_> = (0..secret_shares.len()) .map(|i| { - let witness = FairnessWitness { + let witness = PDLwSlackWitness { x: secret_shares[i].clone(), r: randomness_vec[i].clone(), }; - let statement = FairnessStatement { + let statement = PDLwSlackStatement { + ciphertext: points_encrypted_vec[i].clone(), ek: local_key.paillier_key_vec[i].clone(), - c: points_encrypted_vec[i].clone(), - Y: points_committed_vec[i].clone(), + Q: points_committed_vec[i].clone(), + G: P::generator(), + h1: local_key.h1_h2_n_tilde_vec[i].g.clone(), + h2: local_key.h1_h2_n_tilde_vec[i].ni.clone(), + N_tilde: local_key.h1_h2_n_tilde_vec[i].N.clone(), }; - FairnessProof::prove(&witness, &statement) + PDLwSlackProof::prove(&witness, &statement) }) .collect(); - let (ek, dk) = Paillier::keypair().keys(); + let range_proofs = (0..secret_shares.len()) + .map(|i| { + AliceProof::generate( + &secret_shares[i].to_big_int(), + &points_encrypted_vec[i], + &local_key.paillier_key_vec[i], + &local_key.h1_h2_n_tilde_vec[i], + &randomness_vec[i], + ) + }) + .collect(); + + let (ek, dk) = Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE).keys(); let dk_correctness_proof = NICorrectKeyProof::proof(&dk, None); ( RefreshMessage { party_index: local_key.i as usize, - fairness_proof_vec, + pdl_proof_vec, + range_proofs, coefficients_committed_vec: vss_scheme, points_committed_vec, points_encrypted_vec, @@ -115,20 +134,20 @@ impl

RefreshMessage

{ } // check all vectors are of same length - let reference_len = refresh_messages[0].fairness_proof_vec.len(); + let reference_len = refresh_messages[0].pdl_proof_vec.len(); for (k, refresh_message) in refresh_messages.iter().enumerate() { - let fairness_proof_len = refresh_message.fairness_proof_vec.len(); + let pdl_proof_len = refresh_message.pdl_proof_vec.len(); let points_commited_len = refresh_message.points_committed_vec.len(); let points_encrypted_len = refresh_message.points_encrypted_vec.len(); - if !(fairness_proof_len == reference_len + if !(pdl_proof_len == reference_len && points_commited_len == reference_len && points_encrypted_len == reference_len) { return Err(FsDkrError::SizeMismatchError { refresh_message_index: k, - fairness_proof_len, + pdl_proof_len, points_commited_len, points_encrypted_len, }); @@ -234,15 +253,25 @@ impl

RefreshMessage

{ local_key.n as usize, )?; - let mut statement: FairnessStatement

; for refresh_message in refresh_messages.iter() { for i in 0..(local_key.n as usize) { - statement = FairnessStatement { + let statement = PDLwSlackStatement { + ciphertext: refresh_message.points_encrypted_vec[i].clone(), ek: local_key.paillier_key_vec[i].clone(), - c: refresh_message.points_encrypted_vec[i].clone(), - Y: refresh_message.points_committed_vec[i].clone(), + Q: refresh_message.points_committed_vec[i].clone(), + G: P::generator(), + h1: local_key.h1_h2_n_tilde_vec[i].g.clone(), + h2: local_key.h1_h2_n_tilde_vec[i].ni.clone(), + N_tilde: local_key.h1_h2_n_tilde_vec[i].N.clone(), }; - refresh_message.fairness_proof_vec[i].verify(&statement)?; + refresh_message.pdl_proof_vec[i].verify(&statement)?; + if !refresh_message.range_proofs[i].verify( + &statement.ciphertext, + &statement.ek, + &local_key.h1_h2_n_tilde_vec[i], + ) { + return Err(FsDkrError::RangeProof { party_index: i }); + } } } @@ -264,6 +293,13 @@ impl

RefreshMessage

{ party_index: refresh_message.party_index, }); } + let n_length = refresh_message.ek.n.bit_length(); + if n_length > crate::PAILLIER_KEY_SIZE || n_length < crate::PAILLIER_KEY_SIZE - 1 { + return Err(FsDkrError::MouliTooSmall { + party_index: refresh_message.party_index, + moduli_size: n_length, + }); + } // if the proof checks, we add the new paillier public key to the key local_key.paillier_key_vec[refresh_message.party_index - 1] = @@ -293,6 +329,14 @@ impl

RefreshMessage

{ return Err(FsDkrError::DLogProofValidation { party_index }); } + let n_length = join_message.ek.n.bit_length(); + if n_length > crate::PAILLIER_KEY_SIZE || n_length < crate::PAILLIER_KEY_SIZE - 1 { + return Err(FsDkrError::MouliTooSmall { + party_index: join_message.get_party_index()?, + moduli_size: n_length, + }); + } + // if the proof checks, we add the new paillier public key to the key local_key.paillier_key_vec[party_index - 1] = join_message.ek.clone(); } diff --git a/src/zk_pdl_with_slack.rs b/src/zk_pdl_with_slack.rs new file mode 100644 index 0000000..af4a97a --- /dev/null +++ b/src/zk_pdl_with_slack.rs @@ -0,0 +1,318 @@ +#![allow(non_snake_case)] + +//! We use the proof as given in proof PIi in https://eprint.iacr.org/2016/013.pdf. +//! This proof ws taken from the proof 6.3 (left side ) in https://www.cs.unc.edu/~reiter/papers/2004/IJIS.pdf +//! +//! Statement: (c, pk, Q, G) +//! witness (x, r) such that Q = xG, c = Enc(pk, x, r) +//! note that because of the range proof, the proof has a slack in the range: x in [-q^3, q^3] + +use crate::error::{FsDkrError, FsDkrResult}; +use curv::arithmetic::traits::*; +use curv::cryptographic_primitives::hashing::hash_sha256::HSha256; +use curv::cryptographic_primitives::hashing::traits::Hash; +use curv::elliptic::curves::traits::ECPoint; +use curv::elliptic::curves::traits::ECScalar; +use curv::BigInt; +use paillier::EncryptionKey; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PDLwSlackStatement { + pub ciphertext: BigInt, + pub ek: EncryptionKey, + pub Q: P, + pub G: P, + pub h1: BigInt, + pub h2: BigInt, + pub N_tilde: BigInt, +} +#[derive(Clone)] +pub struct PDLwSlackWitness { + pub x: S, + pub r: BigInt, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PDLwSlackProof

{ + z: BigInt, + u1: P, + u2: BigInt, + u3: BigInt, + s1: BigInt, + s2: BigInt, + s3: BigInt, +} + +impl

PDLwSlackProof

+where + P: ECPoint + Clone, +{ + pub fn prove(witness: &PDLwSlackWitness, statement: &PDLwSlackStatement

) -> Self { + let q3 = P::Scalar::q().pow(3); + let q_N_tilde = P::Scalar::q() * &statement.N_tilde; + let q3_N_tilde = &q3 * &statement.N_tilde; + + let alpha = BigInt::sample_below(&q3); + let one = BigInt::one(); + let beta = BigInt::sample_range(&one, &(&statement.ek.n - &one)); + let rho = BigInt::sample_below(&q_N_tilde); + let gamma = BigInt::sample_below(&q3_N_tilde); + + let z = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &witness.x.to_big_int(), + &rho, + ); + let u1 = statement.G.clone() * ::from(&alpha); + let u2 = commitment_unknown_order( + &(&statement.ek.n + BigInt::one()), + &beta, + &statement.ek.nn, + &alpha, + &statement.ek.n, + ); + let u3 = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &alpha, + &gamma, + ); + + let e = HSha256::create_hash(&[ + &statement.G.bytes_compressed_to_big_int(), + &statement.Q.bytes_compressed_to_big_int(), + &statement.ciphertext, + &z, + &u1.bytes_compressed_to_big_int(), + &u2, + &u3, + ]); + + let s1 = &e * witness.x.to_big_int() + alpha; + let s2 = commitment_unknown_order(&witness.r, &beta, &statement.ek.n, &e, &BigInt::one()); + let s3 = &e * rho + gamma; + + PDLwSlackProof { + z, + u1, + u2, + u3, + s1, + s2, + s3, + } + } + + pub fn verify(&self, statement: &PDLwSlackStatement

) -> FsDkrResult<()> { + let e = HSha256::create_hash(&[ + &statement.G.bytes_compressed_to_big_int(), + &statement.Q.bytes_compressed_to_big_int(), + &statement.ciphertext, + &self.z, + &self.u1.bytes_compressed_to_big_int(), + &self.u2, + &self.u3, + ]); + let g_s1 = statement.G.clone() * ECScalar::from(&self.s1); + let e_fe_neg = ECScalar::from(&(P::Scalar::q() - &e)); + let y_minus_e = statement.Q.clone() * e_fe_neg; + let u1_test = g_s1 + y_minus_e; + + let u2_test_tmp = commitment_unknown_order( + &(&statement.ek.n + BigInt::one()), + &self.s2, + &statement.ek.nn, + &self.s1, + &statement.ek.n, + ); + let u2_test = commitment_unknown_order( + &u2_test_tmp, + &statement.ciphertext, + &statement.ek.nn, + &BigInt::one(), + &(-&e), + ); + + let u3_test_tmp = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &self.s1, + &self.s3, + ); + let u3_test = commitment_unknown_order( + &u3_test_tmp, + &self.z, + &statement.N_tilde, + &BigInt::one(), + &(-&e), + ); + if &self.u1 == &u1_test && &self.u2 == &u2_test && &self.u3 == &u3_test { + Ok(()) + } else { + Err(FsDkrError::PDLwSlackProof { + is_u1_eq: self.u1 == u1_test, + is_u2_eq: self.u2 == u2_test, + is_u3_eq: self.u3 == u3_test, + }) + } + } +} + +pub fn commitment_unknown_order( + h1: &BigInt, + h2: &BigInt, + N_tilde: &BigInt, + x: &BigInt, + r: &BigInt, +) -> BigInt { + let h1_x = BigInt::mod_pow(h1, &x, &N_tilde); + let h2_r = { + if r < &BigInt::zero() { + let h2_inv = BigInt::mod_inv(h2, &N_tilde).unwrap(); + BigInt::mod_pow(&h2_inv, &(-r), &N_tilde) + } else { + BigInt::mod_pow(h2, &r, &N_tilde) + } + }; + let com = BigInt::mod_mul(&h1_x, &h2_r, &N_tilde); + com +} + +#[cfg(test)] +mod test { + use super::*; + use curv::elliptic::curves::secp256_k1::{FE, GE}; + use curv::BigInt; + use paillier::core::Randomness; + use paillier::traits::{EncryptWithChosenRandomness, KeyGeneration}; + use paillier::Paillier; + use paillier::RawPlaintext; + use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement}; + + #[test] + fn test_zk_pdl_with_slack() { + // N_tilde, h1, h2 generation + let (ek_tilde, dk_tilde) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE).keys(); + // note: safe primes should be used: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let S = BigInt::from(2).pow(256 as u32); + let xhi = BigInt::sample_below(&S); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + let statement = DLogStatement { + N: ek_tilde.n.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); + + // generate the scalar secret and Paillier encrypt it + let (ek, _dk) = Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE).keys(); + // note: safe primes should be used here as well: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let randomness = Randomness::sample(&ek); + let x: FE = ECScalar::new_random(); + + let Q = GE::generator() * &x; + + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_big_int().clone()), + &randomness, + ) + .0 + .into_owned(); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: c, + ek, + Q, + G: GE::generator(), + h1, + h2, + N_tilde: ek_tilde.n, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; + + let proof = PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); + // verify h1,h2, N_tilde + let setup_result = composite_dlog_proof.verify(&statement); + assert!(setup_result.is_ok()); + let result = proof.verify(&pdl_w_slack_statement); + assert!(result.is_ok()); + } + + #[test] + #[should_panic] + fn test_zk_pdl_with_slack_soundness() { + // N_tilde, h1, h2 generation + let (ek_tilde, dk_tilde) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE).keys(); + // note: safe primes should be used: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let S = BigInt::from(2).pow(256 as u32); + let xhi = BigInt::sample_below(&S); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + let statement = DLogStatement { + N: ek_tilde.n.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); + + // generate the scalar secret and Paillier encrypt it + let (ek, _dk) = Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE).keys(); + // note: safe primes should be used here as well: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let randomness = Randomness::sample(&ek); + let x: FE = ECScalar::new_random(); + + let Q = GE::generator() * &x; + + // here we encrypt x + 1 instead of x: + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_big_int().clone() + BigInt::one()), + &randomness, + ) + .0 + .into_owned(); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: c, + ek, + Q, + G: GE::generator(), + h1, + h2, + N_tilde: ek_tilde.n, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; + + let proof = PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); + // verify h1,h2, N_tilde + let setup_result = composite_dlog_proof.verify(&statement); + assert!(setup_result.is_ok()); + let result = proof.verify(&pdl_w_slack_statement); + assert!(result.is_ok()); + } +}