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..812f30db0 --- /dev/null +++ b/ipa-core/src/protocol/ipa_prf/malicious_security/verifier.rs @@ -0,0 +1,258 @@ +use std::ops::{Add, Sub}; + +use generic_array::ArrayLength; +use typenum::{Sum, U1}; + +use super::prover::{TwoNMinusOne, TwoNPlusOne, 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_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_or_q_r.eval(p_or_q)[0] + }) + .collect(); + ( + b_share, + ProofVerifier { + u_or_v: p_or_q_r, + out_share: g_r_share, + }, + ) + } + + 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::{U2, U4, U5, U7}; + + use super::ProofVerifier; + use crate::{ + ff::{Fp31, U128Conversions}, + protocol::ipa_prf::malicious_security::prover::ZeroKnowledgeProof, + }; + + #[test] + 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, + ]; + 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 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() + .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); + + // 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); + } +}