From 37ec510ed9e0f7a32184fa62e52e6c2ee96bd513 Mon Sep 17 00:00:00 2001 From: Ben Savage Date: Mon, 18 Mar 2024 18:39:56 +1000 Subject: [PATCH 1/3] Verifying ZKPs part one --- .../ipa_prf/malicious_security/mod.rs | 1 + .../ipa_prf/malicious_security/prover.rs | 42 +++-- .../ipa_prf/malicious_security/verifier.rs | 144 ++++++++++++++++++ 3 files changed, 172 insertions(+), 15 deletions(-) create mode 100644 ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs diff --git a/ipa-core/src/protocol/ipa_prf/malicious_security/mod.rs b/ipa-core/src/protocol/ipa_prf/malicious_security/mod.rs index 0e7f6bf3a..607827b1a 100644 --- a/ipa-core/src/protocol/ipa_prf/malicious_security/mod.rs +++ b/ipa-core/src/protocol/ipa_prf/malicious_security/mod.rs @@ -1,2 +1,3 @@ pub mod lagrange; pub mod prover; +pub mod verifier; diff --git a/ipa-core/src/protocol/ipa_prf/malicious_security/prover.rs b/ipa-core/src/protocol/ipa_prf/malicious_security/prover.rs index 377838c3e..90000181e 100644 --- a/ipa-core/src/protocol/ipa_prf/malicious_security/prover.rs +++ b/ipa-core/src/protocol/ipa_prf/malicious_security/prover.rs @@ -3,7 +3,7 @@ use std::{ ops::{Add, Sub}, }; -use generic_array::{ArrayLength, GenericArray}; +use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; use typenum::{Diff, Sum, U1}; use crate::{ @@ -14,7 +14,22 @@ use crate::{ }; pub struct ZeroKnowledgeProof { - g: GenericArray, + pub g: GenericArray, +} + +impl ZeroKnowledgeProof +where + F: PrimeField, + N: ArrayLength, +{ + pub fn new(g: I) -> Self + where + I: IntoIterator, + { + ZeroKnowledgeProof { + g: g.into_iter().collect(), + } + } } #[derive(Debug)] @@ -23,8 +38,8 @@ pub struct ProofGenerator { v: Vec, } -type TwoNMinusOne = Diff, U1>; -type TwoNPlusOne = Sum, U1>; +pub type TwoNMinusOne = Diff, U1>; +pub type TwoNPlusOne = Sum, U1>; /// /// Distributed Zero Knowledge Proofs algorithm drawn from @@ -84,13 +99,11 @@ where zip(p, q) .map(|(a, b)| *a * *b) .chain(zip(p_extrapolated, q_extrapolated).map(|(a, b)| a * b)) - .collect::>() }); - let proof = ZeroKnowledgeProof { - g: extrapolated_points - .reduce(|acc, pts| zip(acc, pts).map(|(a, b)| a + b).collect()) - .unwrap(), - }; + let proof = ZeroKnowledgeProof::new(extrapolated_points.fold( + GenericArray::>::generate(|_| F::ZERO), + |acc, pts| zip(acc, pts).map(|(a, b)| a + b).collect(), + )); (proof, next_proof_generator) } @@ -118,14 +131,13 @@ where let p_extrapolated = lagrange_table.eval(&p); let q_extrapolated = lagrange_table.eval(&q); - ZeroKnowledgeProof { - g: zip( + ZeroKnowledgeProof::new( + zip( p.into_iter().chain(p_extrapolated), q.into_iter().chain(q_extrapolated), ) - .map(|(a, b)| a * b) - .collect(), - } + .map(|(a, b)| a * b), + ) } } diff --git a/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs b/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs new file mode 100644 index 000000000..6d084c3f2 --- /dev/null +++ b/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs @@ -0,0 +1,144 @@ +use std::{ + iter::zip, + ops::{Add, Sub}, +}; + +use generic_array::{ArrayLength, GenericArray}; +use typenum::{Diff, Sum, U1}; + +use super::prover::{TwoNMinusOne, ZeroKnowledgeProof}; +use crate::{ + ff::PrimeField, + protocol::ipa_prf::malicious_security::lagrange::{ + CanonicalLagrangeDenominator, LagrangeTable, + }, +}; + +pub struct ProofVerifier { + u_or_v: Vec, + out_share: F, +} + +/// +/// Distributed Zero Knowledge Proofs algorithm drawn from +/// `https://eprint.iacr.org/2023/909.pdf` +/// +#[allow(non_camel_case_types)] +impl ProofVerifier +where + F: PrimeField, +{ + pub fn new(u_or_v: Vec, out_share: F) -> Self { + Self { u_or_v, out_share } + } + + pub fn verify_proof<λ: ArrayLength>( + &self, + zkp: ZeroKnowledgeProof>, + r: F, + ) -> (F, ProofVerifier) + where + λ: ArrayLength + Add + Sub, + <λ as Add>::Output: Sub, + <<λ as Add>::Output as Sub>::Output: ArrayLength, + <λ as Sub>::Output: ArrayLength, + { + debug_assert_eq!(self.u_or_v.len() % λ::USIZE, 0); // We should pad with zeroes eventually + + let s = self.u_or_v.len() / λ::USIZE; + + assert!( + s > 1, + "When the output is this small, you should call `verify_final_proof`" + ); + + let denominator_g = CanonicalLagrangeDenominator::>::new(); + let lagrange_table_g = LagrangeTable::, U1>::new(&denominator_g, &r); + let g_r_share = lagrange_table_g.eval(&zkp.g)[0]; + let sum_share = (0..λ::USIZE).fold(F::ZERO, |acc, i| acc + zkp.g[i]); + + // Reveal `b_share` to one another to reconstruct `b` and check if `b = 0`. If the check doesn't pass, abort. + let b_share = sum_share - self.out_share; + + let denominator_p = CanonicalLagrangeDenominator::::new(); + let lagrange_table_p_r = LagrangeTable::::new(&denominator_p, &r); + let p_or_q_r = (0..s) + .map(|i| { + let start = i * λ::USIZE; + let end = start + λ::USIZE; + let p_or_q = &self.u_or_v[start..end]; + lagrange_table_p_r.eval(p_or_q)[0] + }) + .collect(); + ( + b_share, + ProofVerifier { + u_or_v: p_or_q_r, + out_share: g_r_share, + }, + ) + } +} + +#[cfg(all(test, unit_test))] +mod test { + use typenum::{U4, U7}; + + use super::ProofVerifier; + use crate::{ + ff::{Fp31, U128Conversions}, + protocol::ipa_prf::malicious_security::prover::ZeroKnowledgeProof, + }; + + #[test] + fn sample_proof() { + const U_1: [u128; 32] = [ + 0, 30, 0, 16, 0, 1, 0, 15, 0, 0, 0, 16, 0, 30, 0, 16, 29, 1, 1, 15, 0, 0, 1, 15, 2, 30, + 30, 16, 0, 0, 30, 16, + ]; + const OUT_1: u128 = 27; + const ZKP_1: [u128; 7] = [0, 0, 13, 17, 11, 25, 7]; + const R_1: u128 = 22; + + const EXPECTED_G_R_1: u128 = 0; + const EXPECTED_B_1: u128 = 3; + + const U_2: [u128; 8] = [0, 0, 26, 0, 7, 18, 24, 13]; + const ZKP_2: [u128; 7] = [11, 25, 17, 9, 22, 23, 3]; + const R_2: u128 = 17; + + const EXPECTED_G_R_2: u128 = 13; + const EXPECTED_B_2: u128 = 0; + + const U_3: [u128; 2] = [3, 3]; + + let pv_1: ProofVerifier = ProofVerifier::new( + U_1.into_iter() + .map(|x| Fp31::try_from(x).unwrap()) + .collect(), + Fp31::try_from(OUT_1).unwrap(), + ); + + // first iteration + let zkp_1 = ZeroKnowledgeProof::::new(ZKP_1.map(|x| Fp31::try_from(x).unwrap())); + + let (b_share_1, pv_2) = pv_1.verify_proof::(zkp_1, Fp31::try_from(R_1).unwrap()); + assert_eq!(b_share_1.as_u128(), EXPECTED_B_1); + assert_eq!( + pv_2.u_or_v.iter().map(Fp31::as_u128).collect::>(), + U_2, + ); + assert_eq!(pv_2.out_share.as_u128(), EXPECTED_G_R_1); + + // second iteration + let zkp_2 = ZeroKnowledgeProof::::new(ZKP_2.map(|x| Fp31::try_from(x).unwrap())); + + let (b_share_2, pv_3) = pv_2.verify_proof::(zkp_2, Fp31::try_from(R_2).unwrap()); + assert_eq!(b_share_2.as_u128(), EXPECTED_B_2); + assert_eq!( + pv_3.u_or_v.iter().map(Fp31::as_u128).collect::>(), + U_3, + ); + assert_eq!(pv_3.out_share.as_u128(), EXPECTED_G_R_2); + } +} From e98ac50e1476ecd8850c49682115daaba6604619 Mon Sep 17 00:00:00 2001 From: Ben Savage Date: Tue, 19 Mar 2024 01:07:18 +1000 Subject: [PATCH 2/3] Adding the final proof verification --- .../ipa_prf/malicious_security/verifier.rs | 138 ++++++++++++++++-- 1 file changed, 126 insertions(+), 12 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs b/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs index 6d084c3f2..08aa7b5a0 100644 --- a/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs +++ b/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs @@ -1,12 +1,9 @@ -use std::{ - iter::zip, - ops::{Add, Sub}, -}; +use std::ops::{Add, Sub}; -use generic_array::{ArrayLength, GenericArray}; -use typenum::{Diff, Sum, U1}; +use generic_array::ArrayLength; +use typenum::{Sum, U1}; -use super::prover::{TwoNMinusOne, ZeroKnowledgeProof}; +use super::prover::{TwoNMinusOne, TwoNPlusOne, ZeroKnowledgeProof}; use crate::{ ff::PrimeField, protocol::ipa_prf::malicious_security::lagrange::{ @@ -60,14 +57,14 @@ where // Reveal `b_share` to one another to reconstruct `b` and check if `b = 0`. If the check doesn't pass, abort. let b_share = sum_share - self.out_share; - let denominator_p = CanonicalLagrangeDenominator::::new(); - let lagrange_table_p_r = LagrangeTable::::new(&denominator_p, &r); + let denominator_p_or_q = CanonicalLagrangeDenominator::::new(); + let lagrange_table_p_or_q_r = LagrangeTable::::new(&denominator_p_or_q, &r); let p_or_q_r = (0..s) .map(|i| { let start = i * λ::USIZE; let end = start + λ::USIZE; let p_or_q = &self.u_or_v[start..end]; - lagrange_table_p_r.eval(p_or_q)[0] + lagrange_table_p_or_q_r.eval(p_or_q)[0] }) .collect(); ( @@ -78,11 +75,40 @@ where }, ) } + + pub fn verify_final_proof<λ>( + &self, + zkp: ZeroKnowledgeProof>, + r: F, + p_or_q_0: F, + ) -> (F, F) + where + λ: ArrayLength + Add + Add, + <λ as Add>::Output: Add, + <<λ as Add>::Output as Add>::Output: ArrayLength, + <λ as Add>::Output: ArrayLength, + { + assert_eq!(self.u_or_v.len(), λ::USIZE); // We should pad with zeroes eventually + + // We need a table of size `λ + 1` since we add a random point at x=0 + let denominator = CanonicalLagrangeDenominator::>::new(); + let lagrange_table = LagrangeTable::, U1>::new(&denominator, &r); + + let mut p_or_q = vec![p_or_q_0]; + p_or_q.extend_from_slice(&self.u_or_v); + let p_or_q_extrapolated = lagrange_table.eval(&p_or_q)[0]; + + let denominator_g = CanonicalLagrangeDenominator::>::new(); + let lagrange_table_g = LagrangeTable::, U1>::new(&denominator_g, &r); + let out_share = lagrange_table_g.eval(&zkp.g)[0]; + + (p_or_q_extrapolated, out_share) + } } #[cfg(all(test, unit_test))] mod test { - use typenum::{U4, U7}; + use typenum::{U2, U4, U5, U7}; use super::ProofVerifier; use crate::{ @@ -91,7 +117,7 @@ mod test { }; #[test] - fn sample_proof() { + fn sample_proof_u() { const U_1: [u128; 32] = [ 0, 30, 0, 16, 0, 1, 0, 15, 0, 0, 0, 16, 0, 30, 0, 16, 29, 1, 1, 15, 0, 0, 1, 15, 2, 30, 30, 16, 0, 0, 30, 16, @@ -110,7 +136,13 @@ mod test { const EXPECTED_G_R_2: u128 = 13; const EXPECTED_B_2: u128 = 0; + const ZKP_3: [u128; 5] = [21, 1, 6, 25, 1]; const U_3: [u128; 2] = [3, 3]; + const R_3: u128 = 30; + const P_RANDOM_WEIGHT: u128 = 12; + + const EXPECTED_P_FINAL: u128 = 30; + const EXPECTED_G_R_FINAL: u128 = 0; let pv_1: ProofVerifier = ProofVerifier::new( U_1.into_iter() @@ -140,5 +172,87 @@ mod test { U_3, ); assert_eq!(pv_3.out_share.as_u128(), EXPECTED_G_R_2); + + // final iteration + let zkp_3 = ZeroKnowledgeProof::::new(ZKP_3.map(|x| Fp31::try_from(x).unwrap())); + + let (p_final, out_share) = pv_3.verify_final_proof::( + zkp_3, + Fp31::try_from(R_3).unwrap(), + Fp31::try_from(P_RANDOM_WEIGHT).unwrap(), + ); + + assert_eq!(p_final.as_u128(), EXPECTED_P_FINAL); + assert_eq!(out_share.as_u128(), EXPECTED_G_R_FINAL); + } + + #[test] + fn sample_proof_v() { + const V_1: [u128; 32] = [ + 0, 0, 0, 30, 0, 0, 0, 1, 30, 30, 30, 30, 0, 0, 30, 30, 0, 30, 0, 30, 0, 0, 0, 1, 0, 0, + 1, 1, 0, 0, 1, 1, + ]; + const OUT_1: u128 = 0; + const ZKP_1: [u128; 7] = [0, 30, 16, 13, 25, 3, 6]; + const R_1: u128 = 22; + + const EXPECTED_G_R_1: u128 = 10; + const EXPECTED_B_1: u128 = 28; + + const V_2: [u128; 8] = [10, 21, 30, 28, 15, 21, 3, 3]; + const ZKP_2: [u128; 7] = [1, 12, 29, 30, 7, 7, 3]; + const R_2: u128 = 17; + + const EXPECTED_G_R_2: u128 = 12; + const EXPECTED_B_2: u128 = 0; + + const ZKP_3: [u128; 5] = [22, 14, 4, 20, 16]; + const V_3: [u128; 2] = [5, 24]; + const R_3: u128 = 30; + const Q_RANDOM_WEIGHT: u128 = 1; + + const EXPECTED_Q_FINAL: u128 = 12; + const EXPECTED_G_R_FINAL: u128 = 19; + + let pv_1: ProofVerifier = ProofVerifier::new( + V_1.into_iter() + .map(|x| Fp31::try_from(x).unwrap()) + .collect(), + Fp31::try_from(OUT_1).unwrap(), + ); + + // first iteration + let zkp_1 = ZeroKnowledgeProof::::new(ZKP_1.map(|x| Fp31::try_from(x).unwrap())); + + let (b_share_1, pv_2) = pv_1.verify_proof::(zkp_1, Fp31::try_from(R_1).unwrap()); + assert_eq!(b_share_1.as_u128(), EXPECTED_B_1); + assert_eq!( + pv_2.u_or_v.iter().map(Fp31::as_u128).collect::>(), + V_2, + ); + assert_eq!(pv_2.out_share.as_u128(), EXPECTED_G_R_1); + + // second iteration + let zkp_2 = ZeroKnowledgeProof::::new(ZKP_2.map(|x| Fp31::try_from(x).unwrap())); + + let (b_share_2, pv_3) = pv_2.verify_proof::(zkp_2, Fp31::try_from(R_2).unwrap()); + assert_eq!(b_share_2.as_u128(), EXPECTED_B_2); + assert_eq!( + pv_3.u_or_v.iter().map(Fp31::as_u128).collect::>(), + V_3, + ); + assert_eq!(pv_3.out_share.as_u128(), EXPECTED_G_R_2); + + // final iteration + let zkp_3 = ZeroKnowledgeProof::::new(ZKP_3.map(|x| Fp31::try_from(x).unwrap())); + + let (q_final, out_share) = pv_3.verify_final_proof::( + zkp_3, + Fp31::try_from(R_3).unwrap(), + Fp31::try_from(Q_RANDOM_WEIGHT).unwrap(), + ); + + assert_eq!(q_final.as_u128(), EXPECTED_Q_FINAL); + assert_eq!(out_share.as_u128(), EXPECTED_G_R_FINAL); } } From 514f7a108f97fd6c11dc17f5a295924413dd1d28 Mon Sep 17 00:00:00 2001 From: Ben Savage Date: Tue, 19 Mar 2024 01:36:24 +1000 Subject: [PATCH 3/3] clippy --- .../ipa_prf/malicious_security/verifier.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs b/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs index 08aa7b5a0..812f30db0 100644 --- a/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs +++ b/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs @@ -31,7 +31,7 @@ where pub fn verify_proof<λ: ArrayLength>( &self, - zkp: ZeroKnowledgeProof>, + zkp: &ZeroKnowledgeProof>, r: F, ) -> (F, ProofVerifier) where @@ -78,7 +78,7 @@ where pub fn verify_final_proof<λ>( &self, - zkp: ZeroKnowledgeProof>, + zkp: &ZeroKnowledgeProof>, r: F, p_or_q_0: F, ) -> (F, F) @@ -154,7 +154,7 @@ mod test { // first iteration let zkp_1 = ZeroKnowledgeProof::::new(ZKP_1.map(|x| Fp31::try_from(x).unwrap())); - let (b_share_1, pv_2) = pv_1.verify_proof::(zkp_1, Fp31::try_from(R_1).unwrap()); + let (b_share_1, pv_2) = pv_1.verify_proof::(&zkp_1, Fp31::try_from(R_1).unwrap()); assert_eq!(b_share_1.as_u128(), EXPECTED_B_1); assert_eq!( pv_2.u_or_v.iter().map(Fp31::as_u128).collect::>(), @@ -165,7 +165,7 @@ mod test { // second iteration let zkp_2 = ZeroKnowledgeProof::::new(ZKP_2.map(|x| Fp31::try_from(x).unwrap())); - let (b_share_2, pv_3) = pv_2.verify_proof::(zkp_2, Fp31::try_from(R_2).unwrap()); + let (b_share_2, pv_3) = pv_2.verify_proof::(&zkp_2, Fp31::try_from(R_2).unwrap()); assert_eq!(b_share_2.as_u128(), EXPECTED_B_2); assert_eq!( pv_3.u_or_v.iter().map(Fp31::as_u128).collect::>(), @@ -177,7 +177,7 @@ mod test { let zkp_3 = ZeroKnowledgeProof::::new(ZKP_3.map(|x| Fp31::try_from(x).unwrap())); let (p_final, out_share) = pv_3.verify_final_proof::( - zkp_3, + &zkp_3, Fp31::try_from(R_3).unwrap(), Fp31::try_from(P_RANDOM_WEIGHT).unwrap(), ); @@ -224,7 +224,7 @@ mod test { // first iteration let zkp_1 = ZeroKnowledgeProof::::new(ZKP_1.map(|x| Fp31::try_from(x).unwrap())); - let (b_share_1, pv_2) = pv_1.verify_proof::(zkp_1, Fp31::try_from(R_1).unwrap()); + let (b_share_1, pv_2) = pv_1.verify_proof::(&zkp_1, Fp31::try_from(R_1).unwrap()); assert_eq!(b_share_1.as_u128(), EXPECTED_B_1); assert_eq!( pv_2.u_or_v.iter().map(Fp31::as_u128).collect::>(), @@ -235,7 +235,7 @@ mod test { // second iteration let zkp_2 = ZeroKnowledgeProof::::new(ZKP_2.map(|x| Fp31::try_from(x).unwrap())); - let (b_share_2, pv_3) = pv_2.verify_proof::(zkp_2, Fp31::try_from(R_2).unwrap()); + let (b_share_2, pv_3) = pv_2.verify_proof::(&zkp_2, Fp31::try_from(R_2).unwrap()); assert_eq!(b_share_2.as_u128(), EXPECTED_B_2); assert_eq!( pv_3.u_or_v.iter().map(Fp31::as_u128).collect::>(), @@ -247,7 +247,7 @@ mod test { let zkp_3 = ZeroKnowledgeProof::::new(ZKP_3.map(|x| Fp31::try_from(x).unwrap())); let (q_final, out_share) = pv_3.verify_final_proof::( - zkp_3, + &zkp_3, Fp31::try_from(R_3).unwrap(), Fp31::try_from(Q_RANDOM_WEIGHT).unwrap(), );