diff --git a/Cargo.toml b/Cargo.toml index bc402de..8a143bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [workspace] resolver = "2" members = [ - "evm-vrfier", +# "evm-vrfier", "w3f-plonk-common", "w3f-ring-proof", - "w3f-ring-vrf-snark", +# "w3f-ring-vrf-snark", ] [workspace.dependencies] diff --git a/w3f-plonk-common/src/kzg_acc.rs b/w3f-plonk-common/src/kzg_acc.rs new file mode 100644 index 0000000..772917e --- /dev/null +++ b/w3f-plonk-common/src/kzg_acc.rs @@ -0,0 +1,141 @@ +use crate::piop::VerifierPiop; +use crate::verifier::Challenges; +use crate::{ColumnsCommited, ColumnsEvaluated, Proof}; +use ark_ec::pairing::Pairing; +use ark_ec::{CurveGroup, VariableBaseMSM}; +use ark_ff::{PrimeField, Zero}; +use ark_std::rand::Rng; +use w3f_pcs::pcs::kzg::params::KzgVerifierKey; +use w3f_pcs::pcs::kzg::{AccumulatedOpening, KZG}; +use w3f_pcs::pcs::PCS; + +// Accumulates KZG openning claims for Plonk proofs. +// Somewhat similar to https://eprint.iacr.org/2020/499.pdf, section 8. +// With a difference that this accumulates opennings lazily, +// and runs `2` MSMs of size `O(k)` at the final stage, +// that gives an asymptotic saving (thanks to Pippenger) +// at the cost of linear accumulator size. + +pub struct KzgAccumulator { + // acc_points[0] = G1 + acc_points: Vec, + // acc_scalars[0] = 0 + acc_scalars: Vec, + kzg_proofs: Vec, + randomizers: Vec, + kzg_vk: KzgVerifierKey, +} + +impl KzgAccumulator { + pub fn new(kzg_vk: KzgVerifierKey) -> Self { + //TODO: capacity + Self { + acc_points: vec![kzg_vk.g1], + acc_scalars: vec![E::ScalarField::zero()], + kzg_proofs: vec![], + randomizers: vec![], + kzg_vk, + } + } + + // `p(z) = v <=> q(X) = p(X)-v / X-z <=> q(X)(X-z) = p(X)-v <=> q(X)X = p(X) + q(X)z - v`. + // Raising + // `p(X) -> p(tau)G1 =: C`, + // `q(X) -> q(tau)G1 =: pi`, + // `X -> tau.G2`, + // `v -> v.G1`, + // and taking the pairing gives: + // `e(pi, tau.G2) + e(C + z.pi - v.G1, -G2) = 0`. + + // Combining `k` such equations using random coefficients `ri` results in + // `e(agg_pi, tau.G2) + e(acc, -G2) = 0`, where + + // `agg_pi := r1.pi_1 + ... + rk.pi_k` and + + // `acc := sum[r1.(C1 + z1.pi_1 - v1.G1), i = 1,...,k] = + // = -(r1.v1 + ... + rk.vk).G1 + (r1.C1 + ... + rk.Ck) + (r1.z1.pi_1 + ... + rk.zk.pi_k)`. + + // `agg_pi` is a `k`-MSM. + // In a common case when a batch proof `pi_i` attests opennings of multiple (`ni`) commitments at the same point `zi`, + // `Ci = ai_1.Ci_1 + ... + ai_ni.Ci_ni, i = 1,...,k`, + // `acc` is a `1+k+(n1+...+nk)`-MSM. + + /// Accumulates + pub fn accumulate( + &mut self, + piop: Piop, + proof: Proof, Commitments, Evaluations>, + challenges: Challenges, + rng: &mut R, + ) where + F: PrimeField, + E: Pairing, + Piop: VerifierPiop as PCS>::C>, + Commitments: ColumnsCommited as PCS>::C>, + Evaluations: ColumnsEvaluated, + { + let r = F::rand(rng); + let r2 = r.square(); + let zeta = challenges.zeta; + + // TODO: it could be a method unless `to_vec(self)` + let q_zeta = piop.evaluate_q_at_zeta(&challenges.alphas, proof.lin_at_zeta_omega); + let mut columns_at_zeta = proof.columns_at_zeta.to_vec(); + columns_at_zeta.push(q_zeta); + let agg_at_zeta: F = columns_at_zeta + .into_iter() + .zip(challenges.nus.iter()) + .map(|(y, r)| y * r) + .sum(); + + let zeta_omega = zeta * piop.domain_evaluated().omega(); + let lin_comm = piop.lin_poly_commitment(&challenges.alphas); + + // Openning at `z` + // TODO: try to get rid of the commitment wrapper in flonk + self.acc_points.extend( + piop.precommitted_columns() + .iter() + .map(|c| c.0) + .collect::>(), + ); + self.acc_points.extend( + proof + .column_commitments + .to_vec() + .iter() + .map(|c| c.0) + .collect::>(), + ); + self.acc_points.push(proof.quotient_commitment.clone().0); + self.acc_scalars + .extend(challenges.nus.iter().map(|nu| *nu * r).collect::>()); // numbers should match here + + self.acc_points.push(proof.agg_at_zeta_proof); + self.acc_scalars.push(zeta * r); + self.acc_scalars[0] -= agg_at_zeta * r; + + // Openning at `z.w` + // TODO: see above + self.acc_points + .extend(lin_comm.1.iter().map(|c| c.0).collect::>()); + self.acc_scalars + .extend(lin_comm.0.into_iter().map(|c| c * r2).collect::>()); + self.acc_points.push(proof.lin_at_zeta_omega_proof); + self.acc_scalars.push(zeta_omega * r2); + self.acc_scalars[0] -= proof.lin_at_zeta_omega * r2; + + self.kzg_proofs.push(proof.agg_at_zeta_proof); + self.kzg_proofs.push(proof.lin_at_zeta_omega_proof); + self.randomizers.push(r); + self.randomizers.push(r2); + } + + pub fn verify(&self) -> bool { + let acc = (-E::G1::msm(&self.acc_points, &self.acc_scalars).unwrap()).into_affine(); + let proof = E::G1::msm(&self.kzg_proofs, &self.randomizers) + .unwrap() + .into_affine(); + KZG::::verify_accumulated(AccumulatedOpening { acc, proof }, &self.kzg_vk) + } +} diff --git a/w3f-plonk-common/src/lib.rs b/w3f-plonk-common/src/lib.rs index 87208d9..44db952 100644 --- a/w3f-plonk-common/src/lib.rs +++ b/w3f-plonk-common/src/lib.rs @@ -9,6 +9,7 @@ use w3f_pcs::pcs::{Commitment, PCS}; pub mod domain; pub mod gadgets; +pub mod kzg_acc; pub mod piop; pub mod prover; pub mod test_helpers; diff --git a/w3f-plonk-common/src/piop.rs b/w3f-plonk-common/src/piop.rs index df37e9b..81e5333 100644 --- a/w3f-plonk-common/src/piop.rs +++ b/w3f-plonk-common/src/piop.rs @@ -69,7 +69,7 @@ pub trait VerifierPiop> { } // Commitment to the aggregated linearization polynomial without the constant term. - fn lin_poly_commitment(&self, agg_coeffs: &[F]) -> C; + fn lin_poly_commitment(&self, agg_coeffs: &[F]) -> (Vec, Vec); fn domain_evaluated(&self) -> &EvaluatedDomain; } diff --git a/w3f-plonk-common/src/verifier.rs b/w3f-plonk-common/src/verifier.rs index 184c01e..7df1928 100644 --- a/w3f-plonk-common/src/verifier.rs +++ b/w3f-plonk-common/src/verifier.rs @@ -11,7 +11,7 @@ use crate::{ColumnsCommited, ColumnsEvaluated, Proof}; pub struct PlonkVerifier, T: PlonkTranscript> { // Polynomial commitment scheme verifier's key. - pcs_vk: CS::VK, + pub pcs_vk: CS::VK, // Transcript, // initialized with the public parameters and the commitments to the precommitted columns. transcript_prelude: T, @@ -64,6 +64,7 @@ impl, T: PlonkTranscript> PlonkVerifier>(domain_size: usize) { + fn _test_ring_proof>( + domain_size: usize, + batch_size: usize, + ) -> ( + RingVerifier, + Vec<(EdwardsAffine, RingProof)>, + ) { let rng = &mut test_rng(); let (pcs_params, piop_params) = setup::<_, CS>(rng, domain_size); - - let max_keyset_size = piop_params.keyset_part_size; - let keyset_size: usize = rng.gen_range(0..max_keyset_size); + let keyset_size = piop_params.keyset_part_size; let pks = random_vec::(keyset_size, rng); - let k = rng.gen_range(0..keyset_size); // prover's secret index - let pk = pks[k].clone(); - let (prover_key, verifier_key) = index::<_, CS, _>(&pcs_params, &piop_params, &pks); - // PROOF generation - let secret = Fr::rand(rng); // prover's secret scalar - let result = piop_params.h.mul(secret) + pk; - let ring_prover = RingProver::init( - prover_key, - piop_params.clone(), - k, - ArkTranscript::new(b"w3f-ring-proof-test"), - ); let t_prove = start_timer!(|| "Prove"); - let proof = ring_prover.prove(secret); + let claims: Vec<(EdwardsAffine, RingProof)> = (0..batch_size) + .map(|_| { + let prover_idx = rng.gen_range(0..keyset_size); + let prover = RingProver::init( + prover_key.clone(), + piop_params.clone(), + prover_idx, + ArkTranscript::new(b"w3f-ring-proof-test"), + ); + let prover_pk = pks[prover_idx].clone(); + let blinding_factor = Fr::rand(rng); + let blinded_pk = prover_pk + piop_params.h.mul(blinding_factor); + let blinded_pk = blinded_pk.into_affine(); + let proof = prover.prove(blinding_factor); + (blinded_pk, proof) + }) + .collect(); end_timer!(t_prove); let ring_verifier = RingVerifier::init( @@ -99,9 +107,10 @@ mod tests { ArkTranscript::new(b"w3f-ring-proof-test"), ); let t_verify = start_timer!(|| "Verify"); - let res = ring_verifier.verify(proof, result.into_affine()); + let (blinded_pks, proofs) = claims.iter().cloned().unzip(); + assert!(ring_verifier.verify_batch(proofs, blinded_pks)); end_timer!(t_verify); - assert!(res); + (ring_verifier, claims) } #[test] @@ -145,12 +154,17 @@ mod tests { } #[test] + // cargo test test_ring_proof_kzg --release --features="print-trace" -- --show-output fn test_ring_proof_kzg() { - _test_ring_proof::>(2usize.pow(10)); + let (verifier, claims) = _test_ring_proof::>(2usize.pow(10), 10); + let t_verify_batch = start_timer!(|| "Verify Batch KZG"); + let (blinded_pks, proofs) = claims.into_iter().unzip(); + assert!(verifier.verify_batch_kzg(proofs, blinded_pks)); + end_timer!(t_verify_batch); } #[test] fn test_ring_proof_id() { - _test_ring_proof::(2usize.pow(10)); + _test_ring_proof::(2usize.pow(10), 1); } } diff --git a/w3f-ring-proof/src/piop/mod.rs b/w3f-ring-proof/src/piop/mod.rs index a4d2c71..29cd41a 100644 --- a/w3f-ring-proof/src/piop/mod.rs +++ b/w3f-ring-proof/src/piop/mod.rs @@ -127,6 +127,7 @@ impl> FixedColumns { } // #[derive(CanonicalSerialize, CanonicalDeserialize)] +#[derive(Clone)] pub struct ProverKey, G: AffineRepr> { pub(crate) pcs_ck: CS::CK, pub(crate) fixed_columns: FixedColumns, diff --git a/w3f-ring-proof/src/piop/verifier.rs b/w3f-ring-proof/src/piop/verifier.rs index ea6ea24..8eb663f 100644 --- a/w3f-ring-proof/src/piop/verifier.rs +++ b/w3f-ring-proof/src/piop/verifier.rs @@ -123,7 +123,7 @@ impl, Jubjub: TECurveConfig> Veri .concat() } - fn lin_poly_commitment(&self, agg_coeffs: &[F]) -> C { + fn lin_poly_commitment(&self, agg_coeffs: &[F]) -> (Vec, Vec) { assert_eq!(agg_coeffs.len(), Self::N_CONSTRAINTS); let inner_prod_acc = self.witness_columns_committed.inn_prod_acc.clone(); @@ -138,9 +138,9 @@ impl, Jubjub: TECurveConfig> Veri cond_add_x_coeff += agg_coeffs[2] * c_acc_x; cond_add_y_coeff += agg_coeffs[2] * c_acc_y; - C::combine( - &[inner_prod_coeff, cond_add_x_coeff, cond_add_y_coeff], - &[inner_prod_acc.clone(), cond_add_acc_x, cond_add_acc_y], + ( + vec![inner_prod_coeff, cond_add_x_coeff, cond_add_y_coeff], + vec![inner_prod_acc.clone(), cond_add_acc_x, cond_add_acc_y], ) } diff --git a/w3f-ring-proof/src/ring_verifier.rs b/w3f-ring-proof/src/ring_verifier.rs index 324adc2..4f38cac 100644 --- a/w3f-ring-proof/src/ring_verifier.rs +++ b/w3f-ring-proof/src/ring_verifier.rs @@ -1,8 +1,10 @@ +use ark_ec::pairing::Pairing; use ark_ec::twisted_edwards::{Affine, TECurveConfig}; use ark_ec::CurveGroup; use ark_ff::PrimeField; +use w3f_pcs::pcs::kzg::KZG; use w3f_pcs::pcs::{RawVerifierKey, PCS}; - +use w3f_plonk_common::kzg_acc::KzgAccumulator; use w3f_plonk_common::piop::VerifierPiop; use w3f_plonk_common::transcript::PlonkTranscript; use w3f_plonk_common::verifier::PlonkVerifier; @@ -71,4 +73,56 @@ where pub fn piop_params(&self) -> &PiopParams { &self.piop_params } + + pub fn verify_batch( + &self, + proofs: Vec>, + results: Vec>, + ) -> bool { + for (proof, result) in proofs.into_iter().zip(results) { + let res = self.verify(proof, result); + if !res { + return false; + } + } + true + } +} + +impl RingVerifier, Jubjub, T> +where + E: Pairing, + Jubjub: TECurveConfig, + T: PlonkTranscript>, +{ + // Verifies a batch of proofs against the same ring. + pub fn verify_batch_kzg( + &self, + proofs: Vec>>, + results: Vec>, + ) -> bool { + let mut acc = KzgAccumulator::::new(self.plonk_verifier.pcs_vk.clone()); + for (proof, result) in proofs.into_iter().zip(results) { + let (challenges, mut rng) = self.plonk_verifier.restore_challenges( + &result, + &proof, + // '1' accounts for the quotient polynomial that is aggregated together with the columns + PiopVerifier:: as PCS<_>>::C, Affine>::N_COLUMNS + 1, + PiopVerifier:: as PCS<_>>::C, Affine>::N_CONSTRAINTS, + ); + let seed = self.piop_params.seed; + let seed_plus_result = (seed + result).into_affine(); + let domain_at_zeta = self.piop_params.domain.evaluate(challenges.zeta); + let piop = PiopVerifier::<_, _, Affine>::init( + domain_at_zeta, + self.fixed_columns_committed.clone(), + proof.column_commitments.clone(), + proof.columns_at_zeta.clone(), + (seed.x, seed.y), + (seed_plus_result.x, seed_plus_result.y), + ); + acc.accumulate(piop, proof, challenges, &mut rng); + } + acc.verify() + } }