From fe9a488f63fd8a131119cd3e40ec4d80dcf4690a Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 26 Mar 2024 10:54:13 +0100 Subject: [PATCH] Compute Decider's CM challenges in Groth16 circuit, link G16 & KZG proofs in Onchain Decider, refactor CommitmentScheme trait (#79) * Compute Decider's CM challenges in Groth16 circuit, link G16 & KZG proofs in Onchain Decider, refactor CommitmentScheme trait - Refactor commitment package - Refactor `Commitment` trait and the kzg, ipa, pedersen impls - Add methods to prove & verify given challenges (not computing them in-method) - Add KZG challenges computation in decider_eth_circuit - Add cmE & cmW KZG proving & verification in DeciderEth - Link Decider's Groth16 proof & KZG proofs data - Fix point to bytes arkworks inconsistency - Patch ark_curves to use a cherry-picked version with bn254::constraints & grumpkin for v0.4.0 (once arkworks v0.5.0 is released this will no longer be needed) * DeciderEthCircuit: Add check eval=p(c) for E & W The check is temporary disabled due https://github.com/privacy-scaling-explorations/folding-schemes/issues/80, but the public inputs and logic are there, to be able to continue the other parts development while issue #80 is solved. --- Cargo.toml | 9 +- folding-schemes/Cargo.toml | 6 +- folding-schemes/examples/utils.rs | 14 +- folding-schemes/src/commitment/ipa.rs | 243 ++++++----- folding-schemes/src/commitment/kzg.rs | 195 +++++---- folding-schemes/src/commitment/mod.rs | 183 ++++---- folding-schemes/src/commitment/pedersen.rs | 141 +++++-- folding-schemes/src/folding/hypernova/cccs.rs | 6 +- .../src/folding/hypernova/circuit.rs | 11 +- .../src/folding/hypernova/lcccs.rs | 12 +- .../src/folding/hypernova/nimfs.rs | 17 +- .../src/folding/hypernova/utils.rs | 8 +- folding-schemes/src/folding/nova/circuits.rs | 2 +- folding-schemes/src/folding/nova/cyclefold.rs | 48 ++- .../src/folding/nova/decider_eth.rs | 274 +++++++++--- .../src/folding/nova/decider_eth_circuit.rs | 398 ++++++++++++++---- folding-schemes/src/folding/nova/mod.rs | 154 ++++--- folding-schemes/src/folding/nova/nifs.rs | 125 +++--- .../src/folding/protogalaxy/folding.rs | 17 +- folding-schemes/src/lib.rs | 60 ++- folding-schemes/src/utils/vec.rs | 10 + solidity-verifiers/src/verifiers/mod.rs | 24 +- 22 files changed, 1287 insertions(+), 670 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e95c28..349b199 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,10 +6,15 @@ members = [ ] resolver = "2" +[patch.crates-io] # The following patch is to use a version of ark-r1cs-std compatible with # v0.4.0 but that includes a cherry-picked commit from after v0.4.0 which fixes # the in-circuit scalar multiplication of the zero point. The commit is from # https://github.com/arkworks-rs/r1cs-std/pull/124, without including other # changes done between v0.4.0 and this fix which would break compatibility. -[patch.crates-io] -ark-r1cs-std = { git = "https://github.com/arnaucube/ark-r1cs-std-cherry-picked/" } \ No newline at end of file +ark-r1cs-std = { git = "https://github.com/arnaucube/ark-r1cs-std-cherry-picked/" } +# patch ark_curves to use a cherry-picked version which contains +# bn254::constraints & grumpkin for v0.4.0 (once arkworks v0.5.0 is released +# this will no longer be needed) +ark-bn254 = { git = "https://github.com/arnaucube/ark-curves-cherry-picked", branch="cherry-pick"} +ark-grumpkin = { git = "https://github.com/arnaucube/ark-curves-cherry-picked", branch="cherry-pick"} diff --git a/folding-schemes/Cargo.toml b/folding-schemes/Cargo.toml index c1bd195..2734256 100644 --- a/folding-schemes/Cargo.toml +++ b/folding-schemes/Cargo.toml @@ -26,9 +26,8 @@ espresso_subroutines = {git="https://github.com/EspressoSystems/hyperplonk", pac [dev-dependencies] ark-pallas = {version="0.4.0", features=["r1cs"]} ark-vesta = {version="0.4.0", features=["r1cs"]} -ark-bn254 = "0.4.0" -ark-mnt4-298 = {version="0.4.0", features=["r1cs"]} -ark-mnt6-298 = {version="0.4.0", features=["r1cs"]} +ark-bn254 = {version="0.4.0", features=["r1cs"]} +ark-grumpkin = {version="0.4.0", features=["r1cs"]} ark-groth16 = { version = "^0.4.0" } rand = "0.8.5" tracing = { version = "0.1", default-features = false, features = [ "attributes" ] } @@ -45,4 +44,3 @@ parallel = [ "ark-crypto-primitives/parallel", "ark-r1cs-std/parallel", ] - diff --git a/folding-schemes/examples/utils.rs b/folding-schemes/examples/utils.rs index c1bdc93..66b3624 100644 --- a/folding-schemes/examples/utils.rs +++ b/folding-schemes/examples/utils.rs @@ -6,7 +6,7 @@ use ark_pallas::{constraints::GVar, Fr, Projective}; use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; -use folding_schemes::commitment::pedersen::Pedersen; +use folding_schemes::commitment::{pedersen::Pedersen, CommitmentScheme}; use folding_schemes::folding::nova::{get_r1cs, ProverParams, VerifierParams}; use folding_schemes::frontend::FCircuit; use folding_schemes::transcript::poseidon::poseidon_test_config; @@ -27,17 +27,17 @@ pub(crate) fn test_nova_setup>( // get the CM & CF_CM len let (r1cs, cf_r1cs) = get_r1cs::(&poseidon_config, F_circuit).unwrap(); - let cm_len = r1cs.A.n_rows; - let cf_cm_len = cf_r1cs.A.n_rows; + let cf_len = r1cs.A.n_rows; + let cf_cf_len = cf_r1cs.A.n_rows; - let pedersen_params = Pedersen::::new_params(&mut rng, cm_len); - let cf_pedersen_params = Pedersen::::new_params(&mut rng, cf_cm_len); + let (pedersen_params, _) = Pedersen::::setup(&mut rng, cf_len).unwrap(); + let (cf_pedersen_params, _) = Pedersen::::setup(&mut rng, cf_cf_len).unwrap(); let prover_params = ProverParams::, Pedersen> { poseidon_config: poseidon_config.clone(), - cm_params: pedersen_params, - cf_cm_params: cf_pedersen_params, + cs_params: pedersen_params, + cf_cs_params: cf_pedersen_params, }; let verifier_params = VerifierParams:: { poseidon_config: poseidon_config.clone(), diff --git a/folding-schemes/src/commitment/ipa.rs b/folding-schemes/src/commitment/ipa.rs index 6d1ba28..c403a31 100644 --- a/folding-schemes/src/commitment/ipa.rs +++ b/folding-schemes/src/commitment/ipa.rs @@ -18,15 +18,11 @@ use ark_r1cs_std::{ ToBitsGadget, }; use ark_relations::r1cs::{Namespace, SynthesisError}; -use ark_std::{ - cfg_iter, - rand::{Rng, RngCore}, - UniformRand, Zero, -}; +use ark_std::{cfg_iter, rand::RngCore, UniformRand, Zero}; use core::{borrow::Borrow, marker::PhantomData}; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; -use super::{pedersen::Params as PedersenParams, CommitmentProver}; +use super::{pedersen::Params as PedersenParams, CommitmentScheme}; use crate::transcript::Transcript; use crate::utils::{ powers_of, @@ -34,13 +30,6 @@ use crate::utils::{ }; use crate::Error; -/// IPA implements the Inner Product Argument protocol. The `H` parameter indicates if to use the -/// commitment in hiding mode or not. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct IPA { - _c: PhantomData, -} - #[derive(Debug, Clone, Eq, PartialEq)] pub struct Proof { a: C::ScalarField, @@ -50,22 +39,35 @@ pub struct Proof { R: Vec, } -impl IPA { - pub fn new_params(rng: &mut R, max: usize) -> PedersenParams { - let generators: Vec = std::iter::repeat_with(|| C::Affine::rand(rng)) - .take(max.next_power_of_two()) +/// IPA implements the Inner Product Argument protocol following the CommitmentScheme trait. The +/// `H` parameter indicates if to use the commitment in hiding mode or not. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct IPA { + _c: PhantomData, +} + +/// Implements the CommitmentScheme trait for IPA +impl CommitmentScheme for IPA { + type ProverParams = PedersenParams; + type VerifierParams = PedersenParams; + type Proof = (Proof, C::ScalarField, C::ScalarField); // (proof, v=p(x), r=blinding factor) + type ProverChallenge = (C::ScalarField, C, Vec); + type Challenge = (C::ScalarField, C, Vec); + + fn setup( + mut rng: impl RngCore, + len: usize, + ) -> Result<(Self::ProverParams, Self::VerifierParams), Error> { + let generators: Vec = std::iter::repeat_with(|| C::Affine::rand(&mut rng)) + .take(len.next_power_of_two()) .collect(); - PedersenParams:: { - h: C::rand(rng), + let p = PedersenParams:: { + h: C::rand(&mut rng), generators, - } + }; + Ok((p.clone(), p)) } -} -// Implement the CommitmentProver trait for IPA -impl CommitmentProver for IPA { - type Params = PedersenParams; - type Proof = Proof; fn commit( params: &PedersenParams, a: &[C::ScalarField], @@ -74,6 +76,10 @@ impl CommitmentProver for IPA { if params.generators.len() < a.len() { return Err(Error::PedersenParamsLen(params.generators.len(), a.len())); } + if !H && (!r.is_zero()) { + return Err(Error::BlindingNotZero); + } + // h⋅r + // use msm_unchecked because we already ensured at the if that lengths match if !H { @@ -83,16 +89,19 @@ impl CommitmentProver for IPA { } fn prove( - params: &Self::Params, + params: &Self::ProverParams, transcript: &mut impl Transcript, - P: &C, // commitment - a: &[C::ScalarField], - x: &C::ScalarField, + P: &C, // commitment + a: &[C::ScalarField], // vector + blind: &C::ScalarField, rng: Option<&mut dyn RngCore>, ) -> Result { if !a.len().is_power_of_two() { return Err(Error::NotPowerOfTwo("a".to_string(), a.len())); } + if !H && (!blind.is_zero()) { + return Err(Error::BlindingNotZero); + } let d = a.len(); let k = (f64::from(d as u32).log2()) as usize; @@ -116,11 +125,14 @@ impl CommitmentProver for IPA { } transcript.absorb_point(P)?; + let x = transcript.get_challenge(); // challenge value at which we evaluate let s = transcript.get_challenge(); let U = C::generator().mul(s); let mut a = a.to_owned(); - let mut b = powers_of(*x, d); + let mut b = powers_of(x, d); + let v = inner_prod(&a, &b)?; + let mut G = params.generators.clone(); let mut L: Vec = vec![C::zero(); k]; @@ -180,40 +192,41 @@ impl CommitmentProver for IPA { return Err(Error::NotExpectedLength(G.len(), 1)); } - Ok(Self::Proof { - a: a[0], - l: l.clone(), - r: r.clone(), - L, - R, - }) + Ok(( + Proof { + a: a[0], + l: l.clone(), + r: r.clone(), + L, + R, + }, + v, // evaluation at challenge, v=p(x) + *blind, // blind factor + )) } -} -impl IPA { - #[allow(clippy::too_many_arguments)] - pub fn verify( - params: &PedersenParams, + fn prove_with_challenge( + _params: &Self::ProverParams, + _challenge: Self::ProverChallenge, + _a: &[C::ScalarField], // vector + _blind: &C::ScalarField, + _rng: Option<&mut dyn RngCore>, + ) -> Result { + // not supported because the prover logic computes challenges as it advances on the logic + Err(Error::NotSupported("IPA::prove_with_challenge".to_string())) + } + + fn verify( + params: &Self::VerifierParams, transcript: &mut impl Transcript, - x: C::ScalarField, // evaluation point - v: C::ScalarField, // value at evaluation point - P: C, // commitment - p: Proof, - r: C::ScalarField, // blinding factor - k: usize, // k = log2(d), where d is the degree of the committed polynomial - ) -> Result { - if !H && (!p.l.is_empty() || !p.r.is_empty()) { - return Err(Error::CommitmentVerificationFail); - } - if H && (p.l.len() != k || p.r.len() != k) { - return Err(Error::CommitmentVerificationFail); - } - if p.L.len() != k || p.R.len() != k { - return Err(Error::CommitmentVerificationFail); - } + P: &C, // commitment + proof: &Self::Proof, + ) -> Result<(), Error> { + let (p, _r) = (proof.0.clone(), proof.1); + let k = p.L.len(); - // absorbs & get challenges - transcript.absorb_point(&P)?; + transcript.absorb_point(P)?; + let x = transcript.get_challenge(); // challenge value at which we evaluate let s = transcript.get_challenge(); let U = C::generator().mul(s); let mut u: Vec = vec![C::ScalarField::zero(); k]; @@ -222,8 +235,35 @@ impl IPA { transcript.absorb_point(&p.R[i])?; u[i] = transcript.get_challenge(); } + let challenge = (x, U, u); + + Self::verify_with_challenge(params, challenge, P, proof) + } + + fn verify_with_challenge( + params: &Self::VerifierParams, + challenge: Self::Challenge, + P: &C, // commitment + proof: &Self::Proof, + ) -> Result<(), Error> { + let (p, v, r) = (proof.0.clone(), proof.1, proof.2); + let (x, U, u) = challenge; + + let k = p.L.len(); + if p.R.len() != k { + return Err(Error::CommitmentVerificationFail); + } + if !H && (!r.is_zero()) { + return Err(Error::BlindingNotZero); + } + if !H && (!p.l.is_empty() || !p.r.is_empty()) { + return Err(Error::CommitmentVerificationFail); + } + if H && (p.l.len() != k || p.r.len() != k) { + return Err(Error::CommitmentVerificationFail); + } - let P = P + U.mul(v); + let P = *P + U.mul(v); // where v=p(x) let mut q_0 = P; let mut r = r; @@ -262,7 +302,10 @@ impl IPA { G.mul(p.a) + U.mul(p.a * b) }; - Ok(q_0 == q_1) + if q_0 != q_1 { + return Err(Error::CommitmentVerificationFail); + } + Ok(()) } } @@ -452,7 +495,7 @@ where pub fn verify( g: &Vec, // params.generators h: &GC, // params.h - x: &NonNativeFieldVar>, // evaluation point + x: &NonNativeFieldVar>, // evaluation point, challenge v: &NonNativeFieldVar>, // value at evaluation point P: &GC, // commitment p: &ProofVar, @@ -475,7 +518,6 @@ where } // compute b & G from s - // let d: usize = 2_u64.pow(K as u32) as usize; let s = build_s_gadget(u, &u_invs, K)?; // b = = let b = s_b_inner_gadget(u, x)?; @@ -539,7 +581,7 @@ mod tests { const d: usize = 2_u64.pow(k as u32) as usize; // setup params - let params = IPA::::new_params(&mut rng, d); + let (params, _) = IPA::::setup(&mut rng, d).unwrap(); let poseidon_config = poseidon_test_config::(); // init Prover's transcript @@ -547,38 +589,28 @@ mod tests { // init Verifier's transcript let mut transcript_v = PoseidonTranscript::::new(&poseidon_config); + // a is the vector that we're committing let a: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) .take(d) .collect(); - let r_blind: Fr = Fr::rand(&mut rng); + let r_blind: Fr = if hiding { + Fr::rand(&mut rng) + } else { + Fr::zero() + }; let cm = IPA::::commit(¶ms, &a, &r_blind).unwrap(); - // evaluation point - let x = Fr::rand(&mut rng); - let proof = IPA::::prove( ¶ms, &mut transcript_p, &cm, &a, - &x, + &r_blind, Some(&mut rng), ) .unwrap(); - let b = powers_of(x, d); - let v = inner_prod(&a, &b).unwrap(); - assert!(IPA::::verify( - ¶ms, - &mut transcript_v, - x, - v, - cm, - proof, - r_blind, - k, - ) - .unwrap()); + IPA::::verify(¶ms, &mut transcript_v, &cm, &proof).unwrap(); } #[test] @@ -589,11 +621,11 @@ mod tests { fn test_ipa_gadget_opt() { let mut rng = ark_std::test_rng(); - const k: usize = 4; + const k: usize = 3; const d: usize = 2_u64.pow(k as u32) as usize; // setup params - let params = IPA::::new_params(&mut rng, d); + let (params, _) = IPA::::setup(&mut rng, d).unwrap(); let poseidon_config = poseidon_test_config::(); // init Prover's transcript @@ -605,57 +637,49 @@ mod tests { .take(d / 2) .collect(); a.extend(vec![Fr::zero(); d / 2]); - let r_blind: Fr = Fr::rand(&mut rng); + let r_blind: Fr = if hiding { + Fr::rand(&mut rng) + } else { + Fr::zero() + }; let cm = IPA::::commit(¶ms, &a, &r_blind).unwrap(); - // evaluation point - let x = Fr::rand(&mut rng); - let proof = IPA::::prove( ¶ms, &mut transcript_p, &cm, &a, - &x, + &r_blind, Some(&mut rng), ) .unwrap(); - let b = powers_of(x, d); - let v = inner_prod(&a, &b).unwrap(); - assert!(IPA::::verify( - ¶ms, - &mut transcript_v, - x, - v, - cm, - proof.clone(), - r_blind, - k, - ) - .unwrap()); + IPA::::verify(¶ms, &mut transcript_v, &cm, &proof).unwrap(); // circuit let cs = ConstraintSystem::::new_ref(); let mut transcript_v = PoseidonTranscript::::new(&poseidon_config); transcript_v.absorb_point(&cm).unwrap(); + let challenge = transcript_v.get_challenge(); // challenge value at which we evaluate let s = transcript_v.get_challenge(); let U = Projective::generator().mul(s); let mut u: Vec = vec![Fr::zero(); k]; for i in (0..k).rev() { - transcript_v.absorb_point(&proof.L[i]).unwrap(); - transcript_v.absorb_point(&proof.R[i]).unwrap(); + transcript_v.absorb_point(&proof.0.L[i]).unwrap(); + transcript_v.absorb_point(&proof.0.R[i]).unwrap(); u[i] = transcript_v.get_challenge(); } // prepare inputs let gVar = Vec::::new_constant(cs.clone(), params.generators).unwrap(); let hVar = GVar::new_constant(cs.clone(), params.h).unwrap(); - let xVar = NonNativeFieldVar::::new_witness(cs.clone(), || Ok(x)).unwrap(); - let vVar = NonNativeFieldVar::::new_witness(cs.clone(), || Ok(v)).unwrap(); + let challengeVar = + NonNativeFieldVar::::new_witness(cs.clone(), || Ok(challenge)).unwrap(); + let vVar = NonNativeFieldVar::::new_witness(cs.clone(), || Ok(proof.1)).unwrap(); let cmVar = GVar::new_witness(cs.clone(), || Ok(cm)).unwrap(); - let proofVar = ProofVar::::new_witness(cs.clone(), || Ok(proof)).unwrap(); + let proofVar = + ProofVar::::new_witness(cs.clone(), || Ok(proof.0)).unwrap(); let r_blindVar = NonNativeFieldVar::::new_witness(cs.clone(), || Ok(r_blind)).unwrap(); let uVar_vec = Vec::>::new_witness(cs.clone(), || Ok(u)).unwrap(); @@ -663,10 +687,9 @@ mod tests { let UVar = GVar::new_witness(cs.clone(), || Ok(U)).unwrap(); let v = IPAGadget::::verify::( - // &mut transcriptVar, &gVar, &hVar, - &xVar, + &challengeVar, &vVar, &cmVar, &proofVar, diff --git a/folding-schemes/src/commitment/kzg.rs b/folding-schemes/src/commitment/kzg.rs index 8e4e25b..3fef0a7 100644 --- a/folding-schemes/src/commitment/kzg.rs +++ b/folding-schemes/src/commitment/kzg.rs @@ -1,30 +1,28 @@ /// Adaptation of the prover methods and structs from arkworks/poly-commit's KZG10 implementation -/// into the CommitmentProver trait. +/// into the CommitmentScheme trait. /// /// The motivation to do so, is that we want to be able to use KZG / Pedersen for committing to /// vectors indistinctly, and the arkworks KZG10 implementation contains all the methods under the /// same trait, which requires the Pairing trait, where the prover does not need access to the /// Pairing but only to G1. -/// For our case, we want the folding schemes prover to be agnostic to pairings, since in the -/// non-ethereum cases we may use non-pairing-friendly curves with Pedersen commitments, so the -/// trait & types that we use should not depend on the Pairing type for the prover. Therefore, we -/// separate the CommitmentSchemeProver from the setup and verify phases, so the prover can be -/// defined without depending on pairings. use ark_ec::{pairing::Pairing, CurveGroup, VariableBaseMSM}; use ark_ff::PrimeField; use ark_poly::{ univariate::{DenseOrSparsePolynomial, DensePolynomial}, - DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial, + DenseUVPolynomial, Polynomial, }; -use ark_poly_commit::kzg10::{VerifierKey, KZG10}; -use ark_std::rand::{Rng, RngCore}; +use ark_poly_commit::kzg10::{ + Commitment as KZG10Commitment, Proof as KZG10Proof, VerifierKey, KZG10, +}; +use ark_std::rand::RngCore; use ark_std::{borrow::Cow, fmt::Debug}; use ark_std::{One, Zero}; use core::marker::PhantomData; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use super::CommitmentProver; +use super::CommitmentScheme; use crate::transcript::Transcript; +use crate::utils::vec::poly_from_vec; use crate::Error; /// ProverKey defines a similar struct as in ark_poly_commit::kzg10::Powers, but instead of @@ -35,22 +33,40 @@ pub struct ProverKey<'a, C: CurveGroup> { pub powers_of_g: Cow<'a, [C::Affine]>, } -#[derive(Debug)] -pub struct KZGSetup { - _p: PhantomData

, +#[derive(Debug, Clone, Default, Eq, PartialEq)] +pub struct Proof { + pub eval: C::ScalarField, + pub proof: C, +} + +/// KZG implements the CommitmentScheme trait for the KZG commitment scheme. +#[derive(Debug, Clone, Default, Eq, PartialEq)] +pub struct KZG<'a, E: Pairing, const H: bool = false> { + _a: PhantomData<&'a ()>, + _e: PhantomData, } -impl<'a, P> KZGSetup

+impl<'a, E, const H: bool> CommitmentScheme for KZG<'a, E, H> where - P: Pairing, + E: Pairing, { + type ProverParams = ProverKey<'a, E::G1>; + type VerifierParams = VerifierKey; + type Proof = Proof; + type ProverChallenge = E::ScalarField; + type Challenge = E::ScalarField; + /// setup returns the tuple (ProverKey, VerifierKey). For real world deployments the setup must /// be computed in the most trustless way possible, usually through a MPC ceremony. - pub fn setup(rng: &mut R, len: usize) -> (ProverKey<'a, P::G1>, VerifierKey

) { + fn setup( + mut rng: impl RngCore, + len: usize, + ) -> Result<(Self::ProverParams, Self::VerifierParams), Error> { let len = len.next_power_of_two(); - let universal_params = KZG10::>::setup(len, false, rng) - .expect("Setup failed"); + let universal_params = + KZG10::>::setup(len, false, &mut rng) + .expect("Setup failed"); let powers_of_g = universal_params.powers_of_g[..=len].to_vec(); - let powers = ProverKey:: { + let powers = ProverKey:: { powers_of_g: ark_std::borrow::Cow::Owned(powers_of_g), }; let vk = VerifierKey { @@ -61,33 +77,18 @@ where prepared_h: universal_params.prepared_h.clone(), prepared_beta_h: universal_params.prepared_beta_h.clone(), }; - (powers, vk) + Ok((powers, vk)) } -} -/// KZGProver implements the CommitmentProver trait for the KZG commitment scheme. -#[derive(Debug, Clone, Default, Eq, PartialEq)] -pub struct KZGProver<'a, C: CurveGroup, const H: bool = false> { - _a: PhantomData<&'a ()>, - _c: PhantomData, -} -impl<'a, C, const H: bool> CommitmentProver for KZGProver<'a, C, H> -where - C: CurveGroup, -{ - type Params = ProverKey<'a, C>; - /// Proof is a tuple containing (evaluation, proof) - type Proof = (C::ScalarField, C); - - /// commit implements the CommitmentProver commit interface, adapting the implementation from + /// commit implements the CommitmentScheme commit interface, adapting the implementation from /// https://github.com/arkworks-rs/poly-commit/tree/c724fa666e935bbba8db5a1421603bab542e15ab/poly-commit/src/kzg10/mod.rs#L178 /// with the main difference being the removal of the blinding factors and the no-dependency to /// the Pairing trait. fn commit( - params: &Self::Params, - v: &[C::ScalarField], - _blind: &C::ScalarField, - ) -> Result { + params: &Self::ProverParams, + v: &[E::ScalarField], + _blind: &E::ScalarField, + ) -> Result { if !_blind.is_zero() || H { return Err(Error::NotSupportedYet("hiding".to_string())); } @@ -97,23 +98,35 @@ where let (num_leading_zeros, plain_coeffs) = skip_first_zero_coeffs_and_convert_to_bigints(&polynomial); - let commitment = ::msm_bigint( + let commitment = ::msm_bigint( ¶ms.powers_of_g[num_leading_zeros..], &plain_coeffs, ); Ok(commitment) } - /// prove implements the CommitmentProver prove interface, adapting the implementation from + /// prove implements the CommitmentScheme prove interface, adapting the implementation from /// https://github.com/arkworks-rs/poly-commit/tree/c724fa666e935bbba8db5a1421603bab542e15ab/poly-commit/src/kzg10/mod.rs#L307 /// with the main difference being the removal of the blinding factors and the no-dependency to /// the Pairing trait. fn prove( - params: &Self::Params, - transcript: &mut impl Transcript, - cm: &C, - v: &[C::ScalarField], - _blind: &C::ScalarField, + params: &Self::ProverParams, + transcript: &mut impl Transcript, + cm: &E::G1, + v: &[E::ScalarField], + _blind: &E::ScalarField, + _rng: Option<&mut dyn RngCore>, + ) -> Result { + transcript.absorb_point(cm)?; + let challenge = transcript.get_challenge(); + Self::prove_with_challenge(params, challenge, v, _blind, _rng) + } + + fn prove_with_challenge( + params: &Self::ProverParams, + challenge: Self::ProverChallenge, + v: &[E::ScalarField], + _blind: &E::ScalarField, _rng: Option<&mut dyn RngCore>, ) -> Result { if !_blind.is_zero() || H { @@ -123,15 +136,12 @@ where let polynomial = poly_from_vec(v.to_vec())?; check_degree_is_too_large(polynomial.degree(), params.powers_of_g.len())?; - transcript.absorb_point(cm)?; - let challenge = transcript.get_challenge(); - // Compute q(x) = (p(x) - p(z)) / (x-z). Observe that this quotient does not change with z // because p(z) is the remainder term. We can therefore omit p(z) when computing the // quotient. - let divisor = DensePolynomial::::from_coefficients_vec(vec![ + let divisor = DensePolynomial::::from_coefficients_vec(vec![ -challenge, - C::ScalarField::one(), + E::ScalarField::one(), ]); let (witness_poly, remainder_poly) = DenseOrSparsePolynomial::from(&polynomial) .divide_with_q_and_r(&DenseOrSparsePolynomial::from(&divisor)) @@ -139,25 +149,61 @@ where // polynomial is constructed right before and is set to not be zero. And the `.unwrap` // should not give an error. .unwrap(); - let evaluation = remainder_poly[0]; + + let eval = if remainder_poly.is_zero() { + E::ScalarField::zero() + } else { + remainder_poly[0] + }; check_degree_is_too_large(witness_poly.degree(), params.powers_of_g.len())?; let (num_leading_zeros, witness_coeffs) = skip_first_zero_coeffs_and_convert_to_bigints(&witness_poly); - let proof = ::msm_bigint( + let proof = ::msm_bigint( ¶ms.powers_of_g[num_leading_zeros..], &witness_coeffs, ); - Ok((evaluation, proof)) + Ok(Proof { eval, proof }) } -} -/// returns the interpolated polynomial of degree=v.len().next_power_of_two(), which passes through all -/// the given elements of v. -fn poly_from_vec(v: Vec) -> Result, Error> { - let D = GeneralEvaluationDomain::::new(v.len()).ok_or(Error::NewDomainFail)?; - Ok(Evaluations::from_vec_and_domain(v, D).interpolate()) + fn verify( + params: &Self::VerifierParams, + transcript: &mut impl Transcript, + cm: &E::G1, + proof: &Self::Proof, + ) -> Result<(), Error> { + transcript.absorb_point(cm)?; + let challenge = transcript.get_challenge(); + Self::verify_with_challenge(params, challenge, cm, proof) + } + + fn verify_with_challenge( + params: &Self::VerifierParams, + challenge: Self::Challenge, + cm: &E::G1, + proof: &Self::Proof, + ) -> Result<(), Error> { + if H { + return Err(Error::NotSupportedYet("hiding".to_string())); + } + + // verify the KZG proof using arkworks method + let v = KZG10::>::check( + params, // vk + &KZG10Commitment(cm.into_affine()), + challenge, + proof.eval, + &KZG10Proof:: { + w: proof.proof.into_affine(), + random_v: None, + }, + )?; + if !v { + return Err(Error::CommitmentVerificationFail); + } + Ok(()) + } } fn check_degree_is_too_large( @@ -195,7 +241,6 @@ fn convert_to_bigints(p: &[F]) -> Vec { #[cfg(test)] mod tests { use ark_bn254::{Bn254, Fr, G1Projective as G1}; - use ark_poly_commit::kzg10::{Commitment as KZG10Commitment, Proof as KZG10Proof, KZG10}; use ark_std::{test_rng, UniformRand}; use super::*; @@ -203,35 +248,21 @@ mod tests { #[test] fn test_kzg_commitment_scheme() { - let rng = &mut test_rng(); + let mut rng = &mut test_rng(); let poseidon_config = poseidon_test_config::(); let transcript_p = &mut PoseidonTranscript::::new(&poseidon_config); let transcript_v = &mut PoseidonTranscript::::new(&poseidon_config); let n = 10; - let (pk, vk): (ProverKey, VerifierKey) = KZGSetup::::setup(rng, n); + let (pk, vk): (ProverKey, VerifierKey) = + KZG::::setup(&mut rng, n).unwrap(); let v: Vec = std::iter::repeat_with(|| Fr::rand(rng)).take(n).collect(); - let cm = KZGProver::::commit(&pk, &v, &Fr::zero()).unwrap(); + let cm = KZG::::commit(&pk, &v, &Fr::zero()).unwrap(); - let (eval, proof) = - KZGProver::::prove(&pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap(); + let proof = KZG::::prove(&pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap(); // verify the proof: - // get evaluation challenge - transcript_v.absorb_point(&cm).unwrap(); - let challenge = transcript_v.get_challenge(); - // verify the KZG proof using arkworks method - assert!(KZG10::>::check( - &vk, - &KZG10Commitment(cm.into_affine()), - challenge, - eval, - &KZG10Proof:: { - w: proof.into_affine(), - random_v: None, - }, - ) - .unwrap()); + KZG::::verify(&vk, transcript_v, &cm, &proof).unwrap(); } } diff --git a/folding-schemes/src/commitment/mod.rs b/folding-schemes/src/commitment/mod.rs index 8623189..993375c 100644 --- a/folding-schemes/src/commitment/mod.rs +++ b/folding-schemes/src/commitment/mod.rs @@ -9,25 +9,60 @@ pub mod ipa; pub mod kzg; pub mod pedersen; -/// CommitmentProver defines the vector commitment scheme prover trait. Where `H` indicates if to -/// use the commitment in hiding mode or not. -pub trait CommitmentProver: Clone + Debug { - type Params: Clone + Debug; +/// CommitmentScheme defines the vector commitment scheme trait. Where `H` indicates if to use the +/// commitment in hiding mode or not. +pub trait CommitmentScheme: Clone + Debug { + type ProverParams: Clone + Debug; + type VerifierParams: Clone + Debug; type Proof: Clone + Debug; + type ProverChallenge: Clone + Debug; + type Challenge: Clone + Debug; + + fn setup( + rng: impl RngCore, + len: usize, + ) -> Result<(Self::ProverParams, Self::VerifierParams), Error>; fn commit( - params: &Self::Params, + params: &Self::ProverParams, v: &[C::ScalarField], blind: &C::ScalarField, ) -> Result; + fn prove( - params: &Self::Params, + params: &Self::ProverParams, transcript: &mut impl Transcript, cm: &C, v: &[C::ScalarField], blind: &C::ScalarField, rng: Option<&mut dyn RngCore>, ) -> Result; + + /// same as `prove` but instead of providing a Transcript to use, providing the already + /// computed challenge + fn prove_with_challenge( + params: &Self::ProverParams, + challenge: Self::ProverChallenge, + v: &[C::ScalarField], + blind: &C::ScalarField, + rng: Option<&mut dyn RngCore>, + ) -> Result; + + fn verify( + params: &Self::VerifierParams, + transcript: &mut impl Transcript, + cm: &C, + proof: &Self::Proof, + ) -> Result<(), Error>; + + /// same as `verify` but instead of providing a Transcript to use, providing the already + /// computed challenge + fn verify_with_challenge( + params: &Self::VerifierParams, + challenge: Self::Challenge, + cm: &C, + proof: &Self::Proof, + ) -> Result<(), Error>; } #[cfg(test)] @@ -35,58 +70,23 @@ mod tests { use super::*; use ark_bn254::{Bn254, Fr, G1Projective as G1}; use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; - use ark_poly::univariate::DensePolynomial; - use ark_poly_commit::kzg10::{ - Commitment as KZG10Commitment, Proof as KZG10Proof, VerifierKey, KZG10, - }; + use ark_poly_commit::kzg10::VerifierKey; use ark_std::Zero; use ark_std::{test_rng, UniformRand}; - use super::kzg::{KZGProver, KZGSetup, ProverKey}; + use super::ipa::IPA; + use super::kzg::{ProverKey, KZG}; use super::pedersen::Pedersen; use crate::transcript::{ poseidon::{poseidon_test_config, PoseidonTranscript}, Transcript, }; - // Computes the commitment of the two vectors using the given CommitmentProver, then computes - // their random linear combination, and returns it together with the proof of it. - fn commit_rlc_and_prove>( - poseidon_config: &PoseidonConfig, - params: &CP::Params, - r: C::ScalarField, - v_1: &[C::ScalarField], - v_2: &[C::ScalarField], - ) -> Result<(C, CP::Proof), Error> - where - ::ScalarField: Absorb, - { - let cm_1 = CP::commit(params, v_1, &C::ScalarField::zero())?; - let cm_2 = CP::commit(params, v_2, &C::ScalarField::zero())?; - - // random linear combination of the commitment and the witness (vector v) - let cm_3 = cm_1 + cm_2.mul(r); - let v_3: Vec = v_1.iter().zip(v_2).map(|(a, b)| *a + (r * b)).collect(); - - let transcript = &mut PoseidonTranscript::::new(poseidon_config); - let proof = CP::prove( - params, - transcript, - &cm_3, - &v_3, - &C::ScalarField::zero(), - None, - ) - .unwrap(); - - Ok((cm_3, proof)) - } - #[test] - fn test_homomorphic_property_using_CommitmentProver_trait() { - let rng = &mut test_rng(); + fn test_homomorphic_property_using_Commitment_trait() { + let mut rng = &mut test_rng(); let poseidon_config = poseidon_test_config::(); - let n: usize = 100; + let n: usize = 128; // set random vector for the test let v_1: Vec = std::iter::repeat_with(|| Fr::rand(rng)).take(n).collect(); @@ -95,45 +95,74 @@ mod tests { let r = Fr::rand(rng); // setup params for Pedersen & KZG - let pedersen_params = Pedersen::::new_params(rng, n); + let (pedersen_params, _) = Pedersen::::setup(&mut rng, n).unwrap(); let (kzg_pk, kzg_vk): (ProverKey, VerifierKey) = - KZGSetup::::setup(rng, n); + KZG::::setup(rng, n).unwrap(); - // Pedersen commit the two vectors and return their random linear combination and proof - let (pedersen_cm, pedersen_proof) = commit_rlc_and_prove::>( + // test with Pedersen + test_homomorphic_property_using_Commitment_trait_opt::>( &poseidon_config, &pedersen_params, + &pedersen_params, + r, + &v_1, + &v_2, + ); + // test with IPA + test_homomorphic_property_using_Commitment_trait_opt::>( + &poseidon_config, + &pedersen_params, + &pedersen_params, r, &v_1, &v_2, + ); + // test with KZG + test_homomorphic_property_using_Commitment_trait_opt::>( + &poseidon_config, + &kzg_pk, + &kzg_vk, + r, + &v_1, + &v_2, + ); + } + + fn test_homomorphic_property_using_Commitment_trait_opt< + C: CurveGroup, + CS: CommitmentScheme, + >( + poseidon_config: &PoseidonConfig, + prover_params: &CS::ProverParams, + verifier_params: &CS::VerifierParams, + r: C::ScalarField, + v_1: &[C::ScalarField], + v_2: &[C::ScalarField], + ) where + ::ScalarField: Absorb, + { + // compute the commitment of the two vectors using the given CommitmentScheme + let cm_1 = CS::commit(prover_params, v_1, &C::ScalarField::zero()).unwrap(); + let cm_2 = CS::commit(prover_params, v_2, &C::ScalarField::zero()).unwrap(); + + // random linear combination of the commitments and their witnesses (vectors v_i) + let cm_3 = cm_1 + cm_2.mul(r); + let v_3: Vec = v_1.iter().zip(v_2).map(|(a, b)| *a + (r * b)).collect(); + + // compute the proof of the cm_3 + let transcript_p = &mut PoseidonTranscript::::new(poseidon_config); + let proof = CS::prove( + prover_params, + transcript_p, + &cm_3, + &v_3, + &C::ScalarField::zero(), + None, ) .unwrap(); - // KZG commit the two vectors and return their random linear combination and proof - let (kzg_cm, kzg_proof) = - commit_rlc_and_prove::>(&poseidon_config, &kzg_pk, r, &v_1, &v_2) - .unwrap(); - - // verify Pedersen - let transcript_v = &mut PoseidonTranscript::::new(&poseidon_config); - Pedersen::::verify(&pedersen_params, transcript_v, pedersen_cm, pedersen_proof) - .unwrap(); - - // verify KZG - let transcript_v = &mut PoseidonTranscript::::new(&poseidon_config); - transcript_v.absorb_point(&kzg_cm).unwrap(); - let challenge = transcript_v.get_challenge(); - // verify the KZG proof using arkworks method - assert!(KZG10::>::check( - &kzg_vk, - &KZG10Commitment(kzg_cm.into_affine()), - challenge, - kzg_proof.0, // eval - &KZG10Proof:: { - w: kzg_proof.1.into_affine(), // proof - random_v: None, - }, - ) - .unwrap()); + // verify the opening proof + let transcript_v = &mut PoseidonTranscript::::new(poseidon_config); + CS::verify(verifier_params, transcript_v, &cm_3, &proof).unwrap(); } } diff --git a/folding-schemes/src/commitment/pedersen.rs b/folding-schemes/src/commitment/pedersen.rs index 69a7d01..59177ad 100644 --- a/folding-schemes/src/commitment/pedersen.rs +++ b/folding-schemes/src/commitment/pedersen.rs @@ -3,13 +3,10 @@ use ark_ff::Field; use ark_r1cs_std::{boolean::Boolean, groups::GroupOpsBounds, prelude::CurveVar}; use ark_relations::r1cs::SynthesisError; use ark_std::Zero; -use ark_std::{ - rand::{Rng, RngCore}, - UniformRand, -}; +use ark_std::{rand::RngCore, UniformRand}; use core::marker::PhantomData; -use super::CommitmentProver; +use super::CommitmentScheme; use crate::transcript::Transcript; use crate::utils::vec::{vec_add, vec_scalar_mul}; use crate::Error; @@ -18,7 +15,7 @@ use crate::Error; pub struct Proof { pub R: C, pub u: Vec, - pub r_u: C::ScalarField, + pub r_u: C::ScalarField, // blind } #[derive(Debug, Clone, Eq, PartialEq)] @@ -32,30 +29,40 @@ pub struct Pedersen { _c: PhantomData, } -impl Pedersen { - pub fn new_params(rng: &mut R, max: usize) -> Params { - let generators: Vec = std::iter::repeat_with(|| C::Affine::rand(rng)) - .take(max.next_power_of_two()) +/// Implements the CommitmentScheme trait for Pedersen commitments +impl CommitmentScheme for Pedersen { + type ProverParams = Params; + type VerifierParams = Params; + type Proof = Proof; + type ProverChallenge = (C::ScalarField, Vec, C, C::ScalarField); + type Challenge = C::ScalarField; + + fn setup( + mut rng: impl RngCore, + len: usize, + ) -> Result<(Self::ProverParams, Self::VerifierParams), Error> { + let generators: Vec = std::iter::repeat_with(|| C::Affine::rand(&mut rng)) + .take(len.next_power_of_two()) .collect(); - Params:: { - h: C::rand(rng), + let p = Params:: { + h: C::rand(&mut rng), generators, - } + }; + Ok((p.clone(), p)) } -} -// implement the CommitmentProver trait for Pedersen -impl CommitmentProver for Pedersen { - type Params = Params; - type Proof = Proof; fn commit( - params: &Self::Params, + params: &Self::ProverParams, v: &[C::ScalarField], r: &C::ScalarField, // blinding factor ) -> Result { if params.generators.len() < v.len() { return Err(Error::PedersenParamsLen(params.generators.len(), v.len())); } + if !H && (!r.is_zero()) { + return Err(Error::BlindingNotZero); + } + // h⋅r + // use msm_unchecked because we already ensured at the if that lengths match if !H { @@ -65,17 +72,13 @@ impl CommitmentProver for Pedersen { } fn prove( - params: &Params, + params: &Self::ProverParams, transcript: &mut impl Transcript, cm: &C, v: &[C::ScalarField], r: &C::ScalarField, // blinding factor _rng: Option<&mut dyn RngCore>, ) -> Result { - if params.generators.len() < v.len() { - return Err(Error::PedersenParamsLen(params.generators.len(), v.len())); - } - transcript.absorb_point(cm)?; let r1 = transcript.get_challenge(); let d = transcript.get_challenges(v.len()); @@ -90,6 +93,25 @@ impl CommitmentProver for Pedersen { transcript.absorb_point(&R)?; let e = transcript.get_challenge(); + let challenge = (r1, d, R, e); + Self::prove_with_challenge(params, challenge, v, r, _rng) + } + + fn prove_with_challenge( + params: &Self::ProverParams, + challenge: Self::ProverChallenge, + v: &[C::ScalarField], // vector + r: &C::ScalarField, // blinding factor + _rng: Option<&mut dyn RngCore>, + ) -> Result { + if params.generators.len() < v.len() { + return Err(Error::PedersenParamsLen(params.generators.len(), v.len())); + } + if !H && (!r.is_zero()) { + return Err(Error::BlindingNotZero); + } + let (r1, d, R, e): (C::ScalarField, Vec, C, C::ScalarField) = challenge; + // u = d + v⋅e let u = vec_add(&vec_scalar_mul(v, &e), &d)?; // r_u = e⋅r + r_1 @@ -100,14 +122,26 @@ impl CommitmentProver for Pedersen { Ok(Self::Proof { R, u, r_u }) } -} -impl Pedersen { - pub fn verify( - params: &Params, + fn verify( + params: &Self::VerifierParams, transcript: &mut impl Transcript, - cm: C, - proof: Proof, + cm: &C, + proof: &Proof, + ) -> Result<(), Error> { + transcript.absorb_point(cm)?; + transcript.get_challenge(); // r_1 + transcript.get_challenges(proof.u.len()); // d + transcript.absorb_point(&proof.R)?; + let e = transcript.get_challenge(); + Self::verify_with_challenge(params, e, cm, proof) + } + + fn verify_with_challenge( + params: &Self::VerifierParams, + challenge: Self::Challenge, + cm: &C, + proof: &Proof, ) -> Result<(), Error> { if params.generators.len() < proof.u.len() { return Err(Error::PedersenParamsLen( @@ -115,12 +149,11 @@ impl Pedersen { proof.u.len(), )); } + if !H && (!proof.r_u.is_zero()) { + return Err(Error::BlindingNotZero); + } - transcript.absorb_point(&cm)?; - transcript.get_challenge(); // r_1 - transcript.get_challenges(proof.u.len()); // d - transcript.absorb_point(&proof.R)?; - let e = transcript.get_challenge(); + let e = challenge; // check that: R + cm⋅e == h⋅r_u + let lhs = proof.R + cm.mul(e); @@ -186,12 +219,16 @@ mod tests { use crate::transcript::poseidon::{poseidon_test_config, PoseidonTranscript}; #[test] - fn test_pedersen_vector() { + fn test_pedersen() { + test_pedersen_opt::(); + test_pedersen_opt::(); + } + fn test_pedersen_opt() { let mut rng = ark_std::test_rng(); let n: usize = 10; // setup params - let params = Pedersen::::new_params(&mut rng, n); + let (params, _) = Pedersen::::setup(&mut rng, n).unwrap(); let poseidon_config = poseidon_test_config::(); // init Prover's transcript @@ -202,11 +239,17 @@ mod tests { let v: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) .take(n) .collect(); - let r: Fr = Fr::rand(&mut rng); - let cm = Pedersen::::commit(¶ms, &v, &r).unwrap(); + // blinding factor + let r: Fr = if hiding { + Fr::rand(&mut rng) + } else { + Fr::zero() + }; + let cm = Pedersen::::commit(¶ms, &v, &r).unwrap(); let proof = - Pedersen::::prove(¶ms, &mut transcript_p, &cm, &v, &r, None).unwrap(); - Pedersen::::verify(¶ms, &mut transcript_v, cm, proof).unwrap(); + Pedersen::::prove(¶ms, &mut transcript_p, &cm, &v, &r, None) + .unwrap(); + Pedersen::::verify(¶ms, &mut transcript_v, &cm, &proof).unwrap(); } #[test] @@ -217,15 +260,20 @@ mod tests { fn test_pedersen_circuit_opt() { let mut rng = ark_std::test_rng(); - let n: usize = 16; + let n: usize = 8; // setup params - let params = Pedersen::::new_params(&mut rng, n); + let (params, _) = Pedersen::::setup(&mut rng, n).unwrap(); let v: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) .take(n) .collect(); - let r: Fr = Fr::rand(&mut rng); - let cm = Pedersen::::commit(¶ms, &v, &r).unwrap(); + // blinding factor + let r: Fr = if hiding { + Fr::rand(&mut rng) + } else { + Fr::zero() + }; + let cm = Pedersen::::commit(¶ms, &v, &r).unwrap(); let v_bits: Vec> = v.iter().map(|val| val.into_bigint().to_bits_le()).collect(); let r_bits: Vec = r.into_bigint().to_bits_le(); @@ -246,7 +294,8 @@ mod tests { let expected_cmVar = GVar::new_witness(cs.clone(), || Ok(cm)).unwrap(); // use the gadget - let cmVar = PedersenGadget::::commit(hVar, gVar, vVar, rVar).unwrap(); + let cmVar = + PedersenGadget::::commit(hVar, gVar, vVar, rVar).unwrap(); cmVar.enforce_equal(&expected_cmVar).unwrap(); } } diff --git a/folding-schemes/src/folding/hypernova/cccs.rs b/folding-schemes/src/folding/hypernova/cccs.rs index 77d9354..ad3b140 100644 --- a/folding-schemes/src/folding/hypernova/cccs.rs +++ b/folding-schemes/src/folding/hypernova/cccs.rs @@ -11,7 +11,7 @@ use super::utils::compute_sum_Mz; use crate::ccs::CCS; use crate::commitment::{ pedersen::{Params as PedersenParams, Pedersen}, - CommitmentProver, + CommitmentScheme, }; use crate::utils::hypercube::BooleanHypercube; use crate::utils::mle::matrix_to_mle; @@ -44,7 +44,7 @@ impl CCS { ) -> Result<(CCCS, Witness), Error> { let w: Vec = z[(1 + self.l)..].to_vec(); let r_w = C::ScalarField::rand(rng); - let C = Pedersen::::commit(pedersen_params, &w, &r_w)?; + let C = Pedersen::::commit(pedersen_params, &w, &r_w)?; Ok(( CCCS:: { @@ -112,7 +112,7 @@ impl CCCS { ) -> Result<(), Error> { // check that C is the commitment of w. Notice that this is not verifying a Pedersen // opening, but checking that the commitment comes from committing to the witness. - if self.C != Pedersen::::commit(pedersen_params, &w.w, &w.r_w)? { + if self.C != Pedersen::::commit(pedersen_params, &w.w, &w.r_w)? { return Err(Error::NotSatisfied); } diff --git a/folding-schemes/src/folding/hypernova/circuit.rs b/folding-schemes/src/folding/hypernova/circuit.rs index b74f783..7973345 100644 --- a/folding-schemes/src/folding/hypernova/circuit.rs +++ b/folding-schemes/src/folding/hypernova/circuit.rs @@ -157,7 +157,7 @@ mod tests { tests::{get_test_ccs, get_test_z}, CCS, }, - commitment::pedersen::Pedersen, + commitment::{pedersen::Pedersen, CommitmentScheme}, folding::hypernova::utils::{ compute_c_from_sigmas_and_thetas, compute_sigmas_and_thetas, sum_ci_mul_prod_thetaj, sum_muls_gamma_pows_eq_sigma, @@ -180,7 +180,8 @@ mod tests { let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); // Initialize a multifolding object - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1).unwrap(); let sigmas_thetas = compute_sigmas_and_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime); @@ -224,7 +225,8 @@ mod tests { let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); // Initialize a multifolding object - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1).unwrap(); let sigmas_thetas = compute_sigmas_and_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime); @@ -267,7 +269,8 @@ mod tests { let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); // Initialize a multifolding object - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1).unwrap(); let sigmas_thetas = compute_sigmas_and_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime); diff --git a/folding-schemes/src/folding/hypernova/lcccs.rs b/folding-schemes/src/folding/hypernova/lcccs.rs index 9656b32..e7da6c2 100644 --- a/folding-schemes/src/folding/hypernova/lcccs.rs +++ b/folding-schemes/src/folding/hypernova/lcccs.rs @@ -10,7 +10,7 @@ use super::utils::{compute_all_sum_Mz_evals, compute_sum_Mz}; use crate::ccs::CCS; use crate::commitment::{ pedersen::{Params as PedersenParams, Pedersen}, - CommitmentProver, + CommitmentScheme, }; use crate::utils::mle::{matrix_to_mle, vec_to_mle}; use crate::utils::virtual_polynomial::VirtualPolynomial; @@ -46,7 +46,7 @@ impl CCS { ) -> Result<(LCCCS, Witness), Error> { let w: Vec = z[(1 + self.l)..].to_vec(); let r_w = C::ScalarField::rand(rng); - let C = Pedersen::::commit(pedersen_params, &w, &r_w)?; + let C = Pedersen::::commit(pedersen_params, &w, &r_w)?; let r_x: Vec = (0..self.s).map(|_| C::ScalarField::rand(rng)).collect(); let v = self.compute_v_j(z, &r_x); @@ -97,7 +97,7 @@ impl LCCCS { ) -> Result<(), Error> { // check that C is the commitment of w. Notice that this is not verifying a Pedersen // opening, but checking that the Commitment comes from committing to the witness. - if self.C != Pedersen::::commit(pedersen_params, &w.w, &w.r_w)? { + if self.C != Pedersen::::commit(pedersen_params, &w.w, &w.r_w)? { return Err(Error::NotSatisfied); } @@ -131,7 +131,8 @@ pub mod tests { let z = get_test_z(3); ccs.check_relation(&z.clone()).unwrap(); - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); let (lcccs, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z).unwrap(); // with our test vector coming from R1CS, v should have length 3 assert_eq!(lcccs.v.len(), 3); @@ -161,7 +162,8 @@ pub mod tests { bad_z[3] = Fr::zero(); assert!(ccs.check_relation(&bad_z.clone()).is_err()); - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); // Compute v_j with the right z let (lcccs, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z).unwrap(); // with our test vector coming from R1CS, v should have length 3 diff --git a/folding-schemes/src/folding/hypernova/nimfs.rs b/folding-schemes/src/folding/hypernova/nimfs.rs index f8694f5..8da8955 100644 --- a/folding-schemes/src/folding/hypernova/nimfs.rs +++ b/folding-schemes/src/folding/hypernova/nimfs.rs @@ -378,7 +378,7 @@ pub mod tests { use ark_std::test_rng; use ark_std::UniformRand; - use crate::commitment::pedersen::Pedersen; + use crate::commitment::{pedersen::Pedersen, CommitmentScheme}; use ark_pallas::{Fr, Projective}; #[test] @@ -395,7 +395,8 @@ pub mod tests { let sigmas_thetas = compute_sigmas_and_thetas(&ccs, &[z1.clone()], &[z2.clone()], &r_x_prime); - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); let (lcccs, w1) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1).unwrap(); let (cccs, w2) = ccs.to_cccs(&mut rng, &pedersen_params, &z2).unwrap(); @@ -430,7 +431,8 @@ pub mod tests { // Create a basic CCS circuit let ccs = get_test_ccs::(); - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); // Generate a satisfying witness let z_1 = get_test_z(3); @@ -489,7 +491,8 @@ pub mod tests { let ccs = get_test_ccs::(); - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); // LCCCS witness let z_1 = get_test_z(2); @@ -557,7 +560,8 @@ pub mod tests { // Create a basic CCS circuit let ccs = get_test_ccs::(); - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); let mu = 10; let nu = 15; @@ -639,7 +643,8 @@ pub mod tests { // Create a basic CCS circuit let ccs = get_test_ccs::(); - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); let poseidon_config = poseidon_test_config::(); // Prover's transcript diff --git a/folding-schemes/src/folding/hypernova/utils.rs b/folding-schemes/src/folding/hypernova/utils.rs index 219a417..0594a69 100644 --- a/folding-schemes/src/folding/hypernova/utils.rs +++ b/folding-schemes/src/folding/hypernova/utils.rs @@ -199,7 +199,7 @@ pub mod tests { use ark_std::Zero; use crate::ccs::tests::{get_test_ccs, get_test_z}; - use crate::commitment::pedersen::Pedersen; + use crate::commitment::{pedersen::Pedersen, CommitmentScheme}; use crate::utils::multilinear_polynomial::tests::fix_last_variables; use crate::utils::virtual_polynomial::eq_eval; @@ -290,7 +290,8 @@ pub mod tests { let r_x_prime: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); // Initialize a multifolding object - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1).unwrap(); let sigmas_thetas = @@ -333,7 +334,8 @@ pub mod tests { let beta: Vec = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect(); // Initialize a multifolding object - let pedersen_params = Pedersen::::new_params(&mut rng, ccs.n - ccs.l - 1); + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); let (lcccs_instance, _) = ccs.to_lcccs(&mut rng, &pedersen_params, &z1).unwrap(); let mut sum_v_j_gamma = Fr::zero(); diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index e3a890c..764c269 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -509,8 +509,8 @@ where #[cfg(test)] pub mod tests { use super::*; + use ark_bn254::{Fr, G1Projective as Projective}; use ark_ff::BigInteger; - use ark_pallas::{Fr, Projective}; use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; diff --git a/folding-schemes/src/folding/nova/cyclefold.rs b/folding-schemes/src/folding/nova/cyclefold.rs index 59d81b3..2ec000e 100644 --- a/folding-schemes/src/folding/nova/cyclefold.rs +++ b/folding-schemes/src/folding/nova/cyclefold.rs @@ -175,7 +175,7 @@ where } } -/// ChallengeGadget computes the RO challenge used for the CycleFold instances NIFS, it contains a +/// CycleFoldChallengeGadget computes the RO challenge used for the CycleFold instances NIFS, it contains a /// rust-native and a in-circuit compatible versions. pub struct CycleFoldChallengeGadget>> { _c: PhantomData, // Nova's Curve2, the one used for the CycleFold circuit @@ -252,19 +252,15 @@ where .collect::>>>(); let input: Vec>> = [ - U_i.cmE.to_bytes()?, + pointvar_to_bytes(U_i.cmE)?, U_i.u.to_bytes()?, - U_i.cmW.to_bytes()?, + pointvar_to_bytes(U_i.cmW)?, U_i_x_bytes, - u_i.cmE.to_bytes()?, + pointvar_to_bytes(u_i.cmE)?, u_i.u.to_bytes()?, - u_i.cmW.to_bytes()?, + pointvar_to_bytes(u_i.cmW)?, u_i_x_bytes, - cmT.to_bytes()?, - // TODO instead of bytes, use field elements, but needs x,y coordinates from - // u_i.{cmE,cmW}, U_i.{cmE,cmW}, cmT. Depends exposing x,y coordinates of GC. Issue to - // keep track of this: - // https://github.com/privacy-scaling-explorations/folding-schemes/issues/44 + pointvar_to_bytes(cmT)?, ] .concat(); sponge.absorb(&input)?; @@ -273,17 +269,26 @@ where } } -/// returns the bytes being compatible with the ark_r1cs_std `.to_bytes` approach +/// returns the bytes being compatible with the pointvar_to_bytes method. +/// These methods are temporary once arkworks has the fix to prevent different to_bytes behaviour +/// across different curves. Eg, in pasta and bn254: pasta returns 65 bytes both native and gadget, +/// whereas bn254 returns 64 bytes native and 65 in gadget, also the penultimate byte is different +/// natively than in gadget. fn point_to_bytes(p: C) -> Result, Error> { let l = p.uncompressed_size(); let mut b = Vec::new(); p.serialize_uncompressed(&mut b)?; - b[l - 1] = 0; if p.is_zero() { b[l / 2] = 1; b[l - 1] = 1; } - Ok(b) + Ok(b[..63].to_vec()) +} +fn pointvar_to_bytes>>( + p: GC, +) -> Result>>, SynthesisError> { + let b = p.to_bytes()?; + Ok(b[..63].to_vec()) } /// CycleFoldCircuit contains the constraints that check the correct fold of the committed @@ -350,8 +355,8 @@ where #[cfg(test)] pub mod tests { use super::*; + use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective}; use ark_ff::BigInteger; - use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; @@ -468,6 +473,21 @@ pub mod tests { assert!(cs.is_satisfied().unwrap()); } + #[test] + fn test_point_bytes() { + let mut rng = ark_std::test_rng(); + + let p = Projective::rand(&mut rng); + let p_bytes = point_to_bytes(p).unwrap(); + + let cs = ConstraintSystem::::new_ref(); + let pVar = GVar::new_witness(cs.clone(), || Ok(p)).unwrap(); + assert_eq!(pVar.value().unwrap(), p); + + let p_bytesVar = &pointvar_to_bytes(pVar).unwrap(); + assert_eq!(p_bytesVar.value().unwrap(), p_bytes); + } + #[test] fn test_cyclefold_challenge_gadget() { let mut rng = ark_std::test_rng(); diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index 4b735e2..1b8261a 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -1,47 +1,76 @@ /// This file implements the onchain (Ethereum's EVM) decider. -use ark_crypto_primitives::sponge::Absorb; +use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; use ark_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; +use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::fields::nonnative::params::OptimizationType; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar}; use ark_snark::SNARK; -use ark_std::rand::CryptoRng; -use ark_std::rand::RngCore; +use ark_std::rand::{CryptoRng, RngCore}; +use ark_std::Zero; use core::marker::PhantomData; -pub use super::decider_eth_circuit::DeciderEthCircuit; -use crate::commitment::{pedersen::Params as PedersenParams, CommitmentProver}; +pub use super::decider_eth_circuit::{DeciderEthCircuit, KZGChallengesGadget}; +use super::{ + circuits::{ChallengeGadget, CF2}, + nifs::NIFS, + CommittedInstance, Nova, Witness, +}; +use crate::commitment::{ + kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme, +}; use crate::folding::circuits::nonnative::point_to_nonnative_limbs_custom_opt; -use crate::folding::nova::{circuits::CF2, CommittedInstance, Nova}; use crate::frontend::FCircuit; use crate::Error; use crate::{Decider as DeciderTrait, FoldingScheme}; -use ark_r1cs_std::fields::nonnative::params::OptimizationType; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Proof +where + C1: CurveGroup, + CS1: CommitmentScheme, + S: SNARK, +{ + snark_proof: S::Proof, + kzg_proofs: [CS1::Proof; 2], + // cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are + // checked in-circuit + cmT: C1, + r: C1::ScalarField, + // the KZG challenges are provided by the prover, but in-circuit they are checked to match + // the in-circuit computed computed ones. + kzg_challenges: [C1::ScalarField; 2], +} /// Onchain Decider, for ethereum use cases #[derive(Clone, Debug)] -pub struct Decider { +pub struct Decider { _c1: PhantomData, _gc1: PhantomData, _c2: PhantomData, _gc2: PhantomData, _fc: PhantomData, - _cp1: PhantomData, - _cp2: PhantomData, + _cs1: PhantomData, + _cs2: PhantomData, _s: PhantomData, _fs: PhantomData, } -impl DeciderTrait - for Decider +impl DeciderTrait + for Decider where C1: CurveGroup, C2: CurveGroup, GC1: CurveVar>, GC2: CurveVar>, FC: FCircuit, - CP1: CommitmentProver, - // enforce that the CP2 is Pedersen commitment, since we're at Ethereum's EVM decider - CP2: CommitmentProver>, + CS1: CommitmentScheme< + C1, + ProverChallenge = C1::ScalarField, + Challenge = C1::ScalarField, + Proof = KZGProof, + >, // KZG commitment, where challenge is C1::Fr elem + // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider + CS2: CommitmentScheme>, S: SNARK, FS: FoldingScheme, ::BaseField: PrimeField, @@ -51,84 +80,192 @@ where C1: CurveGroup, for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>, // constrain FS into Nova, since this is a Decider specifically for Nova - Nova: From, + Nova: From, { - type ProverParam = S::ProvingKey; - type Proof = S::Proof; - type VerifierParam = S::VerifyingKey; + type ProverParam = ( + PoseidonConfig, + S::ProvingKey, + CS1::ProverParams, + ); + type Proof = Proof; + type VerifierParam = (S::VerifyingKey, CS1::VerifierParams); type PublicInput = Vec; type CommittedInstanceWithWitness = (); type CommittedInstance = CommittedInstance; fn prove( - pp: &Self::ProverParam, + pp: Self::ProverParam, mut rng: impl RngCore + CryptoRng, folding_scheme: FS, ) -> Result { - let circuit = - DeciderEthCircuit::::from_nova::(folding_scheme.into()); + let (poseidon_config, snark_pk, cs_pk): ( + PoseidonConfig, + S::ProvingKey, + CS1::ProverParams, + ) = pp; - S::prove(pp, circuit.clone(), &mut rng).map_err(|e| Error::Other(e.to_string())) + let circuit = DeciderEthCircuit::::from_nova::( + folding_scheme.into(), + )?; + + let snark_proof = S::prove(&snark_pk, circuit.clone(), &mut rng) + .map_err(|e| Error::Other(e.to_string()))?; + + let U_i = circuit + .U_i + .clone() + .ok_or(Error::MissingValue("U_i".to_string()))?; + let W_i = circuit + .W_i + .clone() + .ok_or(Error::MissingValue("W_i".to_string()))?; + let u_i = circuit + .u_i + .clone() + .ok_or(Error::MissingValue("u_i".to_string()))?; + let w_i = circuit + .w_i + .clone() + .ok_or(Error::MissingValue("w_i".to_string()))?; + + // compute NIFS.P((U_d, W_d), (u_d, w_d)) = (U_{d+1}, W_{d+1}, cmT) + let (T, cmT) = NIFS::::compute_cmT(&cs_pk, &circuit.r1cs, &w_i, &u_i, &W_i, &U_i)?; + let r_bits = ChallengeGadget::::get_challenge_native( + &poseidon_config, + U_i.clone(), + u_i.clone(), + cmT, + )?; + let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; + let (W_i1, _): (Witness, CommittedInstance) = + NIFS::::fold_instances(r_Fr, &W_i, &U_i, &w_i, &u_i, &T, cmT)?; + + // get the challenges that have been already computed when preparing the circuit inputs in + // the above `from_nova` call + let challenge_W = circuit + .kzg_c_W + .ok_or(Error::MissingValue("kzg_c_W".to_string()))?; + let challenge_E = circuit + .kzg_c_E + .ok_or(Error::MissingValue("kzg_c_E".to_string()))?; + + // generate KZG proofs + let U_cmW_proof = CS1::prove_with_challenge( + &cs_pk, + challenge_W, + &W_i1.W, + &C1::ScalarField::zero(), + None, + )?; + let U_cmE_proof = CS1::prove_with_challenge( + &cs_pk, + challenge_E, + &W_i1.E, + &C1::ScalarField::zero(), + None, + )?; + + Ok(Self::Proof { + snark_proof, + kzg_proofs: [U_cmW_proof, U_cmE_proof], + cmT, + r: r_Fr, + kzg_challenges: [challenge_W, challenge_E], + }) } fn verify( - vp: &Self::VerifierParam, + vp: Self::VerifierParam, i: C1::ScalarField, z_0: Vec, z_i: Vec, running_instance: &Self::CommittedInstance, + incoming_instance: &Self::CommittedInstance, proof: Self::Proof, ) -> Result { - let (cmE_x, cmE_y) = point_to_nonnative_limbs_custom_opt::( - running_instance.cmE, - OptimizationType::Constraints, - )?; - let (cmW_x, cmW_y) = point_to_nonnative_limbs_custom_opt::( - running_instance.cmW, - OptimizationType::Constraints, - )?; + let (snark_vk, cs_vk): (S::VerifyingKey, CS1::VerifierParams) = vp; + + // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT) + let U = NIFS::::verify(proof.r, running_instance, incoming_instance, &proof.cmT); + + let (cmE_x, cmE_y) = + point_to_nonnative_limbs_custom_opt::(U.cmE, OptimizationType::Constraints)?; + let (cmW_x, cmW_y) = + point_to_nonnative_limbs_custom_opt::(U.cmW, OptimizationType::Constraints)?; + let (cmT_x, cmT_y) = + point_to_nonnative_limbs_custom_opt::(proof.cmT, OptimizationType::Constraints)?; + let public_input: Vec = vec![ vec![i], z_0, z_i, - vec![running_instance.u], - running_instance.x.clone(), + vec![U.u], + U.x.clone(), cmE_x, cmE_y, cmW_x, cmW_y, + proof.kzg_challenges.to_vec(), + vec![ + proof.kzg_proofs[0].eval, // eval_W + proof.kzg_proofs[1].eval, // eval_E + ], + cmT_x, + cmT_y, + vec![proof.r], ] .concat(); - S::verify(vp, &public_input, &proof).map_err(|e| Error::Other(e.to_string())) + + let snark_v = S::verify(&snark_vk, &public_input, &proof.snark_proof) + .map_err(|e| Error::Other(e.to_string()))?; + if !snark_v { + return Err(Error::SNARKVerificationFail); + } + + // we're at the Ethereum EVM case, so the CS1 is KZG commitments + CS1::verify_with_challenge( + &cs_vk, + proof.kzg_challenges[0], + &U.cmW, + &proof.kzg_proofs[0], + )?; + CS1::verify_with_challenge( + &cs_vk, + proof.kzg_challenges[1], + &U.cmE, + &proof.kzg_proofs[1], + )?; + + Ok(true) } } #[cfg(test)] pub mod tests { use super::*; + use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; use ark_groth16::Groth16; - use ark_mnt4_298::{constraints::G1Var as GVar, Fr, G1Projective as Projective, MNT4_298}; - use ark_mnt6_298::{constraints::G1Var as GVar2, G1Projective as Projective2}; + use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; + use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey; use std::time::Instant; + use crate::commitment::kzg::{ProverKey as KZGProverKey, KZG}; use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::{get_pedersen_params_len, ProverParams}; + use crate::folding::nova::{get_cs_params_len, ProverParams}; use crate::frontend::tests::CubicFCircuit; use crate::transcript::poseidon::poseidon_test_config; - // Note: since we're testing a big circuit, this test takes a bit more of computation and time, - // do not run in the normal CI. - // To run the test use `--ignored` flag, eg. `cargo test -- --ignored` #[test] - #[ignore] fn test_decider() { + // use Nova as FoldingScheme type NOVA = Nova< Projective, GVar, Projective2, GVar2, CubicFCircuit, - Pedersen, + KZG<'static, Bn254>, Pedersen, >; type DECIDER = Decider< @@ -137,10 +274,10 @@ pub mod tests { Projective2, GVar2, CubicFCircuit, - Pedersen, + KZG<'static, Bn254>, Pedersen, - Groth16, // here we define the Snark to use in the decider - NOVA, // here we define the FoldingScheme to use + Groth16, // here we define the Snark to use in the decider + NOVA, // here we define the FoldingScheme to use >; let mut rng = ark_std::test_rng(); @@ -149,31 +286,32 @@ pub mod tests { let F_circuit = CubicFCircuit::::new(()); let z_0 = vec![Fr::from(3_u32)]; - let (cm_len, cf_cm_len) = - get_pedersen_params_len::>( + let (cs_len, cf_cs_len) = + get_cs_params_len::>( &poseidon_config, F_circuit, ) .unwrap(); - let pedersen_params = Pedersen::::new_params(&mut rng, cm_len); - let cf_pedersen_params = Pedersen::::new_params(&mut rng, cf_cm_len); - let start = Instant::now(); + let (kzg_pk, kzg_vk): (KZGProverKey, KZGVerifierKey) = + KZG::::setup(&mut rng, cs_len).unwrap(); + let (cf_pedersen_params, _) = Pedersen::::setup(&mut rng, cf_cs_len).unwrap(); + println!("generated KZG params, {:?}", start.elapsed()); + let prover_params = - ProverParams::, Pedersen> { + ProverParams::, Pedersen> { poseidon_config: poseidon_config.clone(), - cm_params: pedersen_params, - cf_cm_params: cf_pedersen_params, + cs_params: kzg_pk.clone(), + cf_cs_params: cf_pedersen_params, }; - println!("generating pedersen params, {:?}", start.elapsed()); - // use Nova as FoldingScheme let start = Instant::now(); let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap(); println!("Nova initialized, {:?}", start.elapsed()); let start = Instant::now(); nova.prove_step().unwrap(); println!("prove_step, {:?}", start.elapsed()); + nova.prove_step().unwrap(); // do a 2nd step // generate Groth16 setup let circuit = DeciderEthCircuit::< @@ -181,23 +319,31 @@ pub mod tests { GVar, Projective2, GVar2, - Pedersen, + KZG, Pedersen, - >::from_nova::>(nova.clone()); + >::from_nova::>(nova.clone()) + .unwrap(); let mut rng = rand::rngs::OsRng; let start = Instant::now(); - let (pk, vk) = - Groth16::::circuit_specific_setup(circuit.clone(), &mut rng).unwrap(); + let (g16_pk, g16_vk) = + Groth16::::circuit_specific_setup(circuit.clone(), &mut rng).unwrap(); println!("Groth16 setup, {:?}", start.elapsed()); // decider proof generation let start = Instant::now(); - let proof = DECIDER::prove(&pk, rng, nova.clone()).unwrap(); - println!("Decider Groth16 prove, {:?}", start.elapsed()); + let decider_pp = (poseidon_config.clone(), g16_pk, kzg_pk); + let proof = DECIDER::prove(decider_pp, rng, nova.clone()).unwrap(); + println!("Decider prove, {:?}", start.elapsed()); // decider proof verification - let verified = DECIDER::verify(&vk, nova.i, nova.z_0, nova.z_i, &nova.U_i, proof).unwrap(); + let start = Instant::now(); + let decider_vp = (g16_vk, kzg_vk); + let verified = DECIDER::verify( + decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, proof, + ) + .unwrap(); assert!(verified); + println!("Decider verify, {:?}", start.elapsed()); } } diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index 1c54447..d199d50 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -3,29 +3,40 @@ use ark_crypto_primitives::crh::poseidon::constraints::CRHParametersVar; use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; use ark_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; +use ark_ff::{BigInteger, PrimeField}; +use ark_poly::Polynomial; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, boolean::Boolean, eq::EqGadget, fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar}, groups::GroupOpsBounds, + poly::{domain::Radix2DomainVar, evaluations::univariate::EvaluationsVar}, prelude::CurveVar, + ToConstraintFieldGadget, }; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; -use ark_std::{One, Zero}; +use ark_std::{log2, One, Zero}; use core::{borrow::Borrow, marker::PhantomData}; +use super::{circuits::ChallengeGadget, nifs::NIFS}; use crate::ccs::r1cs::R1CS; -use crate::commitment::{pedersen::Params as PedersenParams, CommitmentProver}; +use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; +use crate::folding::circuits::nonnative::{point_to_nonnative_limbs, NonNativeAffineVar}; use crate::folding::nova::{ circuits::{CommittedInstanceVar, CF1, CF2}, CommittedInstance, Nova, Witness, }; use crate::frontend::FCircuit; -use crate::utils::gadgets::{ - hadamard, mat_vec_mul_sparse, vec_add, vec_scalar_mul, SparseMatrixVar, +use crate::transcript::{ + poseidon::{PoseidonTranscript, PoseidonTranscriptVar}, + Transcript, TranscriptVar, }; +use crate::utils::{ + gadgets::{hadamard, mat_vec_mul_sparse, vec_add, vec_scalar_mul, SparseMatrixVar}, + vec::poly_from_vec, +}; +use crate::Error; #[derive(Debug, Clone)] pub struct RelaxedR1CSGadget> { @@ -178,21 +189,21 @@ where /// Circuit that implements the in-circuit checks needed for the onchain (Ethereum's EVM) /// verification. #[derive(Clone, Debug)] -pub struct DeciderEthCircuit +pub struct DeciderEthCircuit where C1: CurveGroup, GC1: CurveVar>, C2: CurveGroup, GC2: CurveVar>, - CP1: CommitmentProver, - CP2: CommitmentProver, + CS1: CommitmentScheme, + CS2: CommitmentScheme, { _c1: PhantomData, _gc1: PhantomData, _c2: PhantomData, _gc2: PhantomData, - _cp1: PhantomData, - _cp2: PhantomData, + _cs1: PhantomData, + _cs2: PhantomData, /// E vector's length of the Nova instance witness pub E_len: usize, @@ -215,36 +226,89 @@ where pub w_i: Option>, pub U_i: Option>, pub W_i: Option>, + pub U_i1: Option>, + pub W_i1: Option>, + pub cmT: Option, + pub r: Option, /// CycleFold running instance pub cf_U_i: Option>, pub cf_W_i: Option>, + + /// KZG challenges + pub kzg_c_W: Option, + pub kzg_c_E: Option, + pub eval_W: Option, + pub eval_E: Option, } -impl DeciderEthCircuit +impl DeciderEthCircuit where C1: CurveGroup, C2: CurveGroup, GC1: CurveVar>, GC2: CurveVar>, - CP1: CommitmentProver, - // enforce that the CP2 is Pedersen commitment, since we're at Ethereum's EVM decider - CP2: CommitmentProver>, + CS1: CommitmentScheme, + // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider + CS2: CommitmentScheme>, + ::ScalarField: Absorb, + ::BaseField: PrimeField, { pub fn from_nova>( - nova: Nova, - ) -> Self { - Self { + nova: Nova, + ) -> Result { + // compute the U_{i+1}, W_{i+1} + let (T, cmT) = NIFS::::compute_cmT( + &nova.cs_params, + &nova.r1cs.clone(), + &nova.w_i.clone(), + &nova.u_i.clone(), + &nova.W_i.clone(), + &nova.U_i.clone(), + )?; + let r_bits = ChallengeGadget::::get_challenge_native( + &nova.poseidon_config, + nova.U_i.clone(), + nova.u_i.clone(), + cmT, + )?; + let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; + let (W_i1, U_i1) = NIFS::::fold_instances( + r_Fr, &nova.W_i, &nova.U_i, &nova.w_i, &nova.u_i, &T, cmT, + )?; + + // compute the KZG challenges used as inputs in the circuit + let (kzg_challenge_W, kzg_challenge_E) = + KZGChallengesGadget::::get_challenges_native(&nova.poseidon_config, U_i1.clone())?; + + // get KZG evals + let mut W = W_i1.W.clone(); + W.extend( + std::iter::repeat(C1::ScalarField::zero()) + .take(W_i1.W.len().next_power_of_two() - W_i1.W.len()), + ); + let mut E = W_i1.E.clone(); + E.extend( + std::iter::repeat(C1::ScalarField::zero()) + .take(W_i1.E.len().next_power_of_two() - W_i1.E.len()), + ); + let p_W = poly_from_vec(W.to_vec())?; + let eval_W = p_W.evaluate(&kzg_challenge_W); + let p_E = poly_from_vec(E.to_vec())?; + let eval_E = p_E.evaluate(&kzg_challenge_E); + + Ok(Self { _c1: PhantomData, _gc1: PhantomData, _c2: PhantomData, _gc2: PhantomData, - _cp1: PhantomData, - _cp2: PhantomData, + _cs1: PhantomData, + _cs2: PhantomData, E_len: nova.W_i.E.len(), cf_E_len: nova.cf_W_i.E.len(), r1cs: nova.r1cs, cf_r1cs: nova.cf_r1cs, - cf_pedersen_params: nova.cf_cm_params, + cf_pedersen_params: nova.cf_cs_params, poseidon_config: nova.poseidon_config, i: Some(nova.i), z_0: Some(nova.z_0), @@ -253,21 +317,29 @@ where w_i: Some(nova.w_i), U_i: Some(nova.U_i), W_i: Some(nova.W_i), + U_i1: Some(U_i1), + W_i1: Some(W_i1), + cmT: Some(cmT), + r: Some(r_Fr), cf_U_i: Some(nova.cf_U_i), cf_W_i: Some(nova.cf_W_i), - } + kzg_c_W: Some(kzg_challenge_W), + kzg_c_E: Some(kzg_challenge_E), + eval_W: Some(eval_W), + eval_E: Some(eval_E), + }) } } -impl ConstraintSynthesizer> - for DeciderEthCircuit +impl ConstraintSynthesizer> + for DeciderEthCircuit where C1: CurveGroup, C2: CurveGroup, GC1: CurveVar>, GC2: CurveVar>, - CP1: CommitmentProver, - CP2: CommitmentProver, + CS1: CommitmentScheme, + CS2: CommitmentScheme, ::BaseField: PrimeField, ::BaseField: PrimeField, ::ScalarField: Absorb, @@ -299,14 +371,29 @@ where let u_i = CommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.u_i.unwrap_or(u_dummy_native.clone())) })?; - let w_i = WitnessVar::::new_witness(cs.clone(), || { - Ok(self.w_i.unwrap_or(w_dummy_native.clone())) - })?; - let U_i = CommittedInstanceVar::::new_input(cs.clone(), || { + let U_i = CommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.U_i.unwrap_or(u_dummy_native.clone())) })?; - let W_i = WitnessVar::::new_witness(cs.clone(), || { - Ok(self.W_i.unwrap_or(w_dummy_native.clone())) + // here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i)) + let U_i1 = CommittedInstanceVar::::new_input(cs.clone(), || { + Ok(self.U_i1.unwrap_or(u_dummy_native.clone())) + })?; + let W_i1 = WitnessVar::::new_witness(cs.clone(), || { + Ok(self.W_i1.unwrap_or(w_dummy_native.clone())) + })?; + + // allocate the inputs for the check 6 + let kzg_c_W = FpVar::>::new_input(cs.clone(), || { + Ok(self.kzg_c_W.unwrap_or_else(CF1::::zero)) + })?; + let kzg_c_E = FpVar::>::new_input(cs.clone(), || { + Ok(self.kzg_c_E.unwrap_or_else(CF1::::zero)) + })?; + let _eval_W = FpVar::>::new_input(cs.clone(), || { + Ok(self.eval_W.unwrap_or_else(CF1::::zero)) + })?; + let _eval_E = FpVar::>::new_input(cs.clone(), || { + Ok(self.eval_E.unwrap_or_else(CF1::::zero)) })?; let crh_params = CRHParametersVar::::new_constant( @@ -314,31 +401,17 @@ where self.poseidon_config.clone(), )?; - // 1. check RelaxedR1CS of u_i - let z_u: Vec>> = [ - vec![FpVar::>::one()], - u_i.x.to_vec(), - w_i.W.to_vec(), - ] - .concat(); - RelaxedR1CSGadget::, FpVar>>::check( - r1cs.clone(), - w_i.E, - u_i.u.clone(), - z_u, - )?; - - // 2. check RelaxedR1CS of U_i - let z_U: Vec>> = - [vec![U_i.u.clone()], U_i.x.to_vec(), W_i.W.to_vec()].concat(); + // 1. check RelaxedR1CS of U_{i+1} + let z_U1: Vec>> = + [vec![U_i1.u.clone()], U_i1.x.to_vec(), W_i1.W.to_vec()].concat(); RelaxedR1CSGadget::, FpVar>>::check( r1cs, - W_i.E, - U_i.u.clone(), - z_U, + W_i1.E.clone(), + U_i1.u.clone(), + z_U1, )?; - // 3. u_i.cmE==cm(0), u_i.u==1 + // 2. u_i.cmE==cm(0), u_i.u==1 // Here zero_x & zero_y are the x & y coordinates of the zero point affine representation. let zero_x = NonNativeFieldVar::::new_constant( cs.clone(), @@ -348,19 +421,19 @@ where cs.clone(), C1::BaseField::one(), )?; - (u_i.cmE.x.is_eq(&zero_x)?).enforce_equal(&Boolean::TRUE)?; - (u_i.cmE.y.is_eq(&zero_y)?).enforce_equal(&Boolean::TRUE)?; + u_i.cmE.x.enforce_equal(&zero_x)?; + u_i.cmE.y.enforce_equal(&zero_y)?; (u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?; - // 4. u_i.x == H(i, z_0, z_i, U_i) - let (u_i_x, _) = U_i - .clone() - .hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?; + // 3. u_i.x == H(i, z_0, z_i, U_i) + let (u_i_x, U_i_vec) = + U_i.clone() + .hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?; (u_i.x[0]).enforce_equal(&u_i_x)?; // The following two checks (and their respective allocations) are disabled for normal - // tests since they take ~24.5M constraints and would take several minutes (and RAM) to run - // the test + // tests since they take several millions of constraints and would take several minutes + // (and RAM) to run the test. #[cfg(not(test))] { // imports here instead of at the top of the file, so we avoid having multiple @@ -381,7 +454,7 @@ where Ok(self.cf_W_i.unwrap_or(w_dummy_native.clone())) })?; - // 5. check Pedersen commitments of cf_U_i.{cmE, cmW} + // 4. check Pedersen commitments of cf_U_i.{cmE, cmW} let H = GC2::new_constant(cs.clone(), self.cf_pedersen_params.h)?; let G = Vec::::new_constant(cs.clone(), self.cf_pedersen_params.generators)?; let cf_W_i_E_bits: Result>>>, SynthesisError> = @@ -406,7 +479,7 @@ where NonNativeFieldVar>, >::new_witness(cs.clone(), || Ok(self.cf_r1cs.clone()))?; - // 6. check RelaxedR1CS of cf_U_i + // 5. check RelaxedR1CS of cf_U_i let cf_z_U: Vec>> = [vec![cf_U_i.u.clone()], cf_U_i.x.to_vec(), cf_W_i.W.to_vec()].concat(); RelaxedR1CSGadget::< @@ -416,10 +489,122 @@ where >::check(cf_r1cs, cf_W_i.E, cf_U_i.u.clone(), cf_z_U)?; } + // 6. check KZG challenges + let (incircuit_c_W, incircuit_c_E) = KZGChallengesGadget::::get_challenges_gadget( + cs.clone(), + &self.poseidon_config, + U_i1.clone(), + )?; + incircuit_c_W.enforce_equal(&kzg_c_W)?; + incircuit_c_E.enforce_equal(&kzg_c_E)?; + + // Check 7 is temporary disabled due + // https://github.com/privacy-scaling-explorations/folding-schemes/issues/80 + // + // 7. check eval_W==p_W(c_W) and eval_E==p_E(c_E) + // let incircuit_eval_W = evaluate_gadget::>(W_i1.W, incircuit_c_W)?; + // let incircuit_eval_E = evaluate_gadget::>(W_i1.E, incircuit_c_E)?; + // incircuit_eval_W.enforce_equal(&eval_W)?; + // incircuit_eval_E.enforce_equal(&eval_E)?; + + // 8. compute the NIFS.V challenge and check that matches the one from the public input (so we + // avoid the verifier computing it) + let cmT = + NonNativeAffineVar::new_input(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?; + let r_bits = ChallengeGadget::::get_challenge_gadget( + cs.clone(), + &self.poseidon_config, + U_i_vec, + u_i.clone(), + cmT.clone(), + )?; + let r_Fr = Boolean::le_bits_to_fp_var(&r_bits)?; + // check that the in-circuit computed r is equal to the inputted r + let r = + FpVar::>::new_input(cs.clone(), || Ok(self.r.unwrap_or_else(CF1::::zero)))?; + r_Fr.enforce_equal(&r)?; + Ok(()) } } +/// Interpolates the polynomial from the given vector, and then returns it's evaluation at the +/// given point. +#[allow(unused)] // unused while check 7 is disabled +fn evaluate_gadget( + v: Vec>, + point: FpVar, +) -> Result, SynthesisError> { + if !v.len().is_power_of_two() { + return Err(SynthesisError::Unsatisfiable); + } + let n = v.len() as u64; + let gen = F::get_root_of_unity(n).unwrap(); + let domain = Radix2DomainVar::new(gen, log2(v.len()) as u64, FpVar::one()).unwrap(); + + let evaluations_var = EvaluationsVar::from_vec_and_domain(v, domain, true); + evaluations_var.interpolate_and_evaluate(&point) +} + +/// Gadget that computes the KZG challenges, also offers the rust native implementation compatible +/// with the gadget. +pub struct KZGChallengesGadget { + _c: PhantomData, +} +impl KZGChallengesGadget +where + C: CurveGroup, + C::ScalarField: PrimeField, + ::BaseField: PrimeField, + C::ScalarField: Absorb, +{ + pub fn get_challenges_native( + poseidon_config: &PoseidonConfig, + U_i: CommittedInstance, + ) -> Result<(C::ScalarField, C::ScalarField), Error> { + let (cmE_x_limbs, cmE_y_limbs): (Vec, Vec) = + point_to_nonnative_limbs::(U_i.cmE)?; + let (cmW_x_limbs, cmW_y_limbs): (Vec, Vec) = + point_to_nonnative_limbs::(U_i.cmW)?; + + let transcript = &mut PoseidonTranscript::::new(poseidon_config); + // compute the KZG challenges, which are computed in-circuit and checked that it matches + // the inputted one + transcript.absorb_vec(&cmW_x_limbs); + transcript.absorb_vec(&cmW_y_limbs); + let challenge_W = transcript.get_challenge(); + transcript.absorb_vec(&cmE_x_limbs); + transcript.absorb_vec(&cmE_y_limbs); + let challenge_E = transcript.get_challenge(); + + Ok((challenge_W, challenge_E)) + } + // compatible with the native get_challenges_native + #[allow(clippy::type_complexity)] + pub fn get_challenges_gadget( + cs: ConstraintSystemRef, + poseidon_config: &PoseidonConfig, + U_i: CommittedInstanceVar, + ) -> Result<(FpVar, FpVar), SynthesisError> { + let mut transcript = + PoseidonTranscriptVar::>::new(cs.clone(), &poseidon_config.clone()); + + let cmW_x_limbs = U_i.cmW.x.to_constraint_field()?; + let cmW_y_limbs = U_i.cmW.y.to_constraint_field()?; + transcript.absorb_vec(&cmW_x_limbs)?; + transcript.absorb_vec(&cmW_y_limbs)?; + let challenge_W = transcript.get_challenge()?; + + let cmE_x_limbs = U_i.cmE.x.to_constraint_field()?; + let cmE_y_limbs = U_i.cmE.y.to_constraint_field()?; + transcript.absorb_vec(&cmE_x_limbs)?; + transcript.absorb_vec(&cmE_y_limbs)?; + let challenge_E = transcript.get_challenge()?; + + Ok((challenge_W, challenge_E)) + } +} + #[cfg(test)] pub mod tests { use super::*; @@ -439,10 +624,11 @@ pub mod tests { fields::{fp::FpVar, nonnative::NonNativeFieldVar}, }; use ark_relations::r1cs::ConstraintSystem; + use ark_std::UniformRand; use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::{get_pedersen_params_len, ProverParams, VerifierParams}; + use crate::folding::nova::{get_cs_params_len, ProverParams, VerifierParams}; use crate::frontend::tests::{CubicFCircuit, CustomFCircuit, WrapperCircuit}; use crate::transcript::poseidon::poseidon_test_config; use crate::FoldingScheme; @@ -610,21 +796,21 @@ pub mod tests { let F_circuit = CubicFCircuit::::new(()); let z_0 = vec![Fr::from(3_u32)]; - // get the CM & CF_CM len - let (cm_len, cf_cm_len) = - get_pedersen_params_len::>( + // get the CS & CF_CS len + let (cs_len, cf_cs_len) = + get_cs_params_len::>( &poseidon_config, F_circuit, ) .unwrap(); - let pedersen_params = Pedersen::::new_params(&mut rng, cm_len); - let cf_pedersen_params = Pedersen::::new_params(&mut rng, cf_cm_len); + let (pedersen_params, _) = Pedersen::::setup(&mut rng, cs_len).unwrap(); + let (cf_pedersen_params, _) = Pedersen::::setup(&mut rng, cf_cs_len).unwrap(); let prover_params = ProverParams::, Pedersen> { poseidon_config: poseidon_config.clone(), - cm_params: pedersen_params, - cf_cm_params: cf_pedersen_params, + cs_params: pedersen_params, + cf_cs_params: cf_pedersen_params, }; type NOVA = Nova< @@ -666,12 +852,82 @@ pub mod tests { GVar2, Pedersen, Pedersen, - >::from_nova(nova); + >::from_nova(nova) + .unwrap(); let cs = ConstraintSystem::::new_ref(); // generate the constraints and check that are satisfied by the inputs decider_circuit.generate_constraints(cs.clone()).unwrap(); assert!(cs.is_satisfied().unwrap()); + dbg!(cs.num_constraints()); + } + + // checks that the gadget and native implementations of the challenge computation match + #[test] + fn test_kzg_challenge_gadget() { + let mut rng = ark_std::test_rng(); + let poseidon_config = poseidon_test_config::(); + + let U_i = CommittedInstance:: { + cmE: Projective::rand(&mut rng), + u: Fr::rand(&mut rng), + cmW: Projective::rand(&mut rng), + x: vec![Fr::rand(&mut rng); 1], + }; + + // compute the challenge natively + let (challenge_W, challenge_E) = + KZGChallengesGadget::::get_challenges_native(&poseidon_config, U_i.clone()) + .unwrap(); + + let cs = ConstraintSystem::::new_ref(); + let U_iVar = + CommittedInstanceVar::::new_witness(cs.clone(), || Ok(U_i.clone())) + .unwrap(); + + let (challenge_W_Var, challenge_E_Var) = + KZGChallengesGadget::::get_challenges_gadget( + cs.clone(), + &poseidon_config, + U_iVar, + ) + .unwrap(); + assert!(cs.is_satisfied().unwrap()); + + // check that the natively computed and in-circuit computed hashes match + use ark_r1cs_std::R1CSVar; + assert_eq!(challenge_W_Var.value().unwrap(), challenge_W); + assert_eq!(challenge_E_Var.value().unwrap(), challenge_E); + } + + // The test test_polynomial_interpolation is temporary disabled due + // https://github.com/privacy-scaling-explorations/folding-schemes/issues/80 + // for n<=11 it will work, but for n>11 it will fail with stack overflow. + #[ignore] + #[test] + fn test_polynomial_interpolation() { + let mut rng = ark_std::test_rng(); + let n = 12; + let l = 1 << n; + + let v: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) + .take(l) + .collect(); + let challenge = Fr::rand(&mut rng); + + use ark_poly::Polynomial; + let polynomial = poly_from_vec(v.to_vec()).unwrap(); + let eval = polynomial.evaluate(&challenge); + + let cs = ConstraintSystem::::new_ref(); + let vVar = Vec::>::new_witness(cs.clone(), || Ok(v)).unwrap(); + let challengeVar = FpVar::::new_witness(cs.clone(), || Ok(challenge)).unwrap(); + + let evalVar = evaluate_gadget::(vVar, challengeVar).unwrap(); + + use ark_r1cs_std::R1CSVar; + assert_eq!(evalVar.value().unwrap(), eval); + assert!(cs.is_satisfied().unwrap()); } } diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index 4f2648e..499ef25 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -14,7 +14,7 @@ use core::marker::PhantomData; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS}; -use crate::commitment::CommitmentProver; +use crate::commitment::CommitmentScheme; use crate::folding::circuits::nonnative::point_to_nonnative_limbs; use crate::frontend::FCircuit; use crate::utils::vec::is_zero_vec; @@ -115,16 +115,16 @@ where rW: C::ScalarField::zero(), } } - pub fn commit>( + pub fn commit>( &self, - params: &CP::Params, + params: &CS::ProverParams, x: Vec, ) -> Result, Error> { let mut cmE = C::zero(); if !is_zero_vec::(&self.E) { - cmE = CP::commit(params, &self.E, &self.rE)?; + cmE = CS::commit(params, &self.E, &self.rE)?; } - let cmW = CP::commit(params, &self.W, &self.rW)?; + let cmW = CS::commit(params, &self.W, &self.rW)?; Ok(CommittedInstance { cmE, u: C::ScalarField::one(), @@ -135,16 +135,16 @@ where } #[derive(Debug, Clone)] -pub struct ProverParams +pub struct ProverParams where C1: CurveGroup, C2: CurveGroup, - CP1: CommitmentProver, - CP2: CommitmentProver, + CS1: CommitmentScheme, + CS2: CommitmentScheme, { pub poseidon_config: PoseidonConfig, - pub cm_params: CP1::Params, - pub cf_cm_params: CP2::Params, + pub cs_params: CS1::ProverParams, + pub cf_cs_params: CS2::ProverParams, } #[derive(Debug, Clone)] @@ -157,15 +157,15 @@ pub struct VerifierParams { /// Implements Nova+CycleFold's IVC, described in [Nova](https://eprint.iacr.org/2021/370.pdf) and /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait #[derive(Clone, Debug)] -pub struct Nova +pub struct Nova where C1: CurveGroup, GC1: CurveVar>, C2: CurveGroup, GC2: CurveVar>, FC: FCircuit, - CP1: CommitmentProver, - CP2: CommitmentProver, + CS1: CommitmentScheme, + CS2: CommitmentScheme, { _gc1: PhantomData, _c2: PhantomData, @@ -175,10 +175,10 @@ where /// R1CS of the CycleFold circuit pub cf_r1cs: R1CS, pub poseidon_config: PoseidonConfig, - /// CommitmentProver::Params over C1 - pub cm_params: CP1::Params, - /// CycleFold CommitmentProver::Params, over C2 - pub cf_cm_params: CP2::Params, + /// CommitmentScheme::ProverParams over C1 + pub cs_params: CS1::ProverParams, + /// CycleFold CommitmentScheme::ProverParams, over C2 + pub cf_cs_params: CS2::ProverParams, /// F circuit, the circuit that is being folded pub F: FC, pub i: C1::ScalarField, @@ -197,16 +197,16 @@ where pub cf_U_i: CommittedInstance, } -impl FoldingScheme - for Nova +impl FoldingScheme + for Nova where C1: CurveGroup, GC1: CurveVar>, C2: CurveGroup, GC2: CurveVar>, FC: FCircuit, - CP1: CommitmentProver, - CP2: CommitmentProver, + CS1: CommitmentScheme, + CS2: CommitmentScheme, ::BaseField: PrimeField, ::BaseField: PrimeField, ::ScalarField: Absorb, @@ -216,7 +216,7 @@ where for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, { type PreprocessorParam = (Self::ProverParam, FC); - type ProverParam = ProverParams; + type ProverParam = ProverParams; type VerifierParam = VerifierParams; type CommittedInstanceWithWitness = (CommittedInstance, Witness); type CFCommittedInstanceWithWitness = (CommittedInstance, Witness); @@ -270,8 +270,8 @@ where r1cs, cf_r1cs, poseidon_config: pp.poseidon_config.clone(), - cm_params: pp.cm_params.clone(), - cf_cm_params: pp.cf_cm_params.clone(), + cs_params: pp.cs_params.clone(), + cf_cs_params: pp.cf_cs_params.clone(), F, i: C1::ScalarField::zero(), z_0: z_0.clone(), @@ -315,7 +315,7 @@ where .ok_or(Error::OutOfBounds)?; // fold Nova instances - let (W_i1, U_i1): (Witness, CommittedInstance) = NIFS::::fold_instances( + let (W_i1, U_i1): (Witness, CommittedInstance) = NIFS::::fold_instances( r_Fr, &self.W_i, &self.U_i, &self.w_i, &self.u_i, &T, cmT, )?; @@ -355,7 +355,7 @@ where }; #[cfg(test)] - NIFS::::verify_folded_instance(r_Fr, &self.U_i, &self.u_i, &U_i1, &cmT)?; + NIFS::::verify_folded_instance(r_Fr, &self.U_i, &self.u_i, &U_i1, &cmT)?; } else { // CycleFold part: // get the vector used as public inputs 'x' in the CycleFold circuit @@ -444,6 +444,9 @@ where augmented_F_circuit.generate_constraints(cs.clone())?; + #[cfg(test)] + assert!(cs.is_satisfied().unwrap()); + let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let (w_i1, x_i1) = extract_w_x::(&cs); if x_i1[0] != u_i1_x { @@ -459,7 +462,7 @@ where self.i += C1::ScalarField::one(); self.z_i = z_i1.clone(); self.w_i = Witness::::new(w_i1, self.r1cs.A.n_rows); - self.u_i = self.w_i.commit::(&self.cm_params, vec![u_i1_x])?; + self.u_i = self.w_i.commit::(&self.cs_params, vec![u_i1_x])?; self.W_i = W_i1.clone(); self.U_i = U_i1.clone(); @@ -533,15 +536,15 @@ where } } -impl Nova +impl Nova where C1: CurveGroup, GC1: CurveVar>, C2: CurveGroup, GC2: CurveVar>, FC: FCircuit, - CP1: CommitmentProver, - CP2: CommitmentProver, + CS1: CommitmentScheme, + CS2: CommitmentScheme, ::BaseField: PrimeField, ::ScalarField: Absorb, ::ScalarField: Absorb, @@ -549,8 +552,8 @@ where { // computes T and cmT for the AugmentedFCircuit fn compute_cmT(&self) -> Result<(Vec, C1), Error> { - NIFS::::compute_cmT( - &self.cm_params, + NIFS::::compute_cmT( + &self.cs_params, &self.r1cs, &self.w_i, &self.u_i, @@ -566,8 +569,8 @@ where cf_W_i: &Witness, cf_U_i: &CommittedInstance, ) -> Result<(Vec, C2), Error> { - NIFS::::compute_cyclefold_cmT( - &self.cf_cm_params, + NIFS::::compute_cyclefold_cmT( + &self.cf_cs_params, &self.cf_r1cs, cf_w_i, cf_u_i, @@ -577,15 +580,15 @@ where } } -impl Nova +impl Nova where C1: CurveGroup, GC1: CurveVar>, C2: CurveGroup, GC2: CurveVar>, FC: FCircuit, - CP1: CommitmentProver, - CP2: CommitmentProver, + CS1: CommitmentScheme, + CS2: CommitmentScheme, ::BaseField: PrimeField, ::BaseField: PrimeField, ::ScalarField: Absorb, @@ -630,7 +633,7 @@ where // fold cyclefold instances let cf_w_i = Witness::::new(cf_w_i.clone(), self.cf_r1cs.A.n_rows); let cf_u_i: CommittedInstance = - cf_w_i.commit::(&self.cf_cm_params, cf_x_i.clone())?; + cf_w_i.commit::(&self.cf_cs_params, cf_x_i.clone())?; // compute T* and cmT* for CycleFoldCircuit let (cf_T, cf_cmT) = self.compute_cf_cmT(&cf_w_i, &cf_u_i, &cf_W_i, &cf_U_i)?; @@ -644,7 +647,7 @@ where let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) .ok_or(Error::OutOfBounds)?; - let (cf_W_i1, cf_U_i1) = NIFS::::fold_instances( + let (cf_W_i1, cf_U_i1) = NIFS::::fold_instances( cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT, )?; Ok((cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r_Fq)) @@ -693,7 +696,7 @@ where /// helper method to get the pedersen params length for both the AugmentedFCircuit and the /// CycleFold circuit -pub fn get_pedersen_params_len( +pub fn get_cs_params_len( poseidon_config: &PoseidonConfig, F_circuit: FC, ) -> Result<(usize, usize), Error> @@ -715,6 +718,7 @@ where Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows)) } +/// returns the coordinates of a commitment point pub(crate) fn get_cm_coordinates(cm: &C) -> Vec { let zero = (&C::BaseField::zero(), &C::BaseField::one()); let cm = cm.into_affine(); @@ -725,8 +729,10 @@ pub(crate) fn get_cm_coordinates(cm: &C) -> Vec { #[cfg(test)] pub mod tests { use super::*; - use ark_pallas::{constraints::GVar, Fr, Projective}; - use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; + use crate::commitment::kzg::{ProverKey as KZGProverKey, KZG}; + use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; + use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; + use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey; use crate::commitment::pedersen::Pedersen; use crate::frontend::tests::CubicFCircuit; @@ -736,38 +742,56 @@ pub mod tests { /// AugmentedFCircuit #[test] fn test_ivc() { - type NOVA = Nova< - Projective, - GVar, - Projective2, - GVar2, - CubicFCircuit, - Pedersen, - Pedersen, - >; - let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::(); let F_circuit = CubicFCircuit::::new(()); - let z_0 = vec![Fr::from(3_u32)]; - let (cm_len, cf_cm_len) = - get_pedersen_params_len::>( + let (cs_len, cf_cs_len) = + get_cs_params_len::>( &poseidon_config, F_circuit, ) .unwrap(); - let pedersen_params = Pedersen::::new_params(&mut rng, cm_len); - let cf_pedersen_params = Pedersen::::new_params(&mut rng, cf_cm_len); - - let prover_params = - ProverParams::, Pedersen> { - poseidon_config: poseidon_config.clone(), - cm_params: pedersen_params, - cf_cm_params: cf_pedersen_params, - }; + let (kzg_pk, _): (KZGProverKey, KZGVerifierKey) = + KZG::::setup(&mut rng, cs_len).unwrap(); + let (pedersen_params, _) = Pedersen::::setup(&mut rng, cs_len).unwrap(); + let (cf_pedersen_params, _) = Pedersen::::setup(&mut rng, cf_cs_len).unwrap(); + + // run the test using Pedersen commitments on both sides of the curve cycle + test_ivc_opt::, Pedersen>( + poseidon_config.clone(), + pedersen_params, + cf_pedersen_params.clone(), + F_circuit, + ); + // run the test using KZG for the commitments on the main curve, and Pedersen for the + // commitments on the secondary curve + test_ivc_opt::, Pedersen>( + poseidon_config, + kzg_pk, + cf_pedersen_params, + F_circuit, + ); + } + + // test_ivc allowing to choose the CommitmentSchemes + fn test_ivc_opt, CS2: CommitmentScheme>( + poseidon_config: PoseidonConfig, + cs_params: CS1::ProverParams, + cf_cs_params: CS2::ProverParams, + F_circuit: CubicFCircuit, + ) { + type NOVA = + Nova, CS1, CS2>; + let prover_params = ProverParams:: { + poseidon_config: poseidon_config.clone(), + cs_params, + cf_cs_params, + }; + + let z_0 = vec![Fr::from(3_u32)]; let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap(); let num_steps: usize = 3; @@ -782,7 +806,7 @@ pub mod tests { cf_r1cs: nova.clone().cf_r1cs, }; let (running_instance, incoming_instance, cyclefold_instance) = nova.instances(); - NOVA::verify( + NOVA::::verify( verifier_params, z_0, nova.z_i, diff --git a/folding-schemes/src/folding/nova/nifs.rs b/folding-schemes/src/folding/nova/nifs.rs index aae042b..d2bcc65 100644 --- a/folding-schemes/src/folding/nova/nifs.rs +++ b/folding-schemes/src/folding/nova/nifs.rs @@ -1,23 +1,23 @@ use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; -use ark_std::One; +use ark_std::Zero; use std::marker::PhantomData; use super::{CommittedInstance, Witness}; use crate::ccs::r1cs::R1CS; -use crate::commitment::CommitmentProver; +use crate::commitment::CommitmentScheme; use crate::transcript::Transcript; use crate::utils::vec::*; use crate::Error; /// Implements the Non-Interactive Folding Scheme described in section 4 of /// [Nova](https://eprint.iacr.org/2021/370.pdf) -pub struct NIFS> { +pub struct NIFS> { _c: PhantomData, - _cp: PhantomData, + _cp: PhantomData, } -impl> NIFS +impl> NIFS where ::ScalarField: Absorb, { @@ -90,7 +90,7 @@ where /// compute_cmT is part of the NIFS.P logic pub fn compute_cmT( - cm_prover_params: &CP::Params, + cs_prover_params: &CS::ProverParams, r1cs: &R1CS, w1: &Witness, ci1: &CommittedInstance, @@ -102,12 +102,12 @@ where // compute cross terms let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?; - // use r_T=1 since we don't need hiding property for cm(T) - let cmT = CP::commit(cm_prover_params, &T, &C::ScalarField::one())?; + // use r_T=0 since we don't need hiding property for cm(T) + let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?; Ok((T, cmT)) } pub fn compute_cyclefold_cmT( - cm_prover_params: &CP::Params, + cs_prover_params: &CS::ProverParams, r1cs: &R1CS, // R1CS over C2.Fr=C1.Fq (here C=C2) w1: &Witness, ci1: &CommittedInstance, @@ -122,8 +122,8 @@ where // compute cross terms let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?; - // use r_T=1 since we don't need hiding property for cm(T) - let cmT = CP::commit(cm_prover_params, &T, &C::ScalarField::one())?; + // use r_T=0 since we don't need hiding property for cm(T) + let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?; Ok((T, cmT)) } @@ -140,11 +140,11 @@ where cmT: C, ) -> Result<(Witness, CommittedInstance), Error> { // fold witness - // use r_T=1 since we don't need hiding property for cm(T) - let w3 = NIFS::::fold_witness(r, w1, w2, T, C::ScalarField::one())?; + // use r_T=0 since we don't need hiding property for cm(T) + let w3 = NIFS::::fold_witness(r, w1, w2, T, C::ScalarField::zero())?; // fold committed instances - let ci3 = NIFS::::fold_committed_instance(r, ci1, ci2, &cmT); + let ci3 = NIFS::::fold_committed_instance(r, ci1, ci2, &cmT); Ok((w3, ci3)) } @@ -158,7 +158,7 @@ where ci2: &CommittedInstance, cmT: &C, ) -> CommittedInstance { - NIFS::::fold_committed_instance(r, ci1, ci2, cmT) + NIFS::::fold_committed_instance(r, ci1, ci2, cmT) } /// Verify committed folded instance (ci) relations. Notice that this method does not open the @@ -184,15 +184,15 @@ where pub fn prove_commitments( tr: &mut impl Transcript, - cm_prover_params: &CP::Params, + cs_prover_params: &CS::ProverParams, w: &Witness, ci: &CommittedInstance, T: Vec, cmT: &C, - ) -> Result<[CP::Proof; 3], Error> { - let cmE_proof = CP::prove(cm_prover_params, tr, &ci.cmE, &w.E, &w.rE, None)?; - let cmW_proof = CP::prove(cm_prover_params, tr, &ci.cmW, &w.W, &w.rW, None)?; - let cmT_proof = CP::prove(cm_prover_params, tr, cmT, &T, &C::ScalarField::one(), None)?; // cm(T) is committed with rT=1 + ) -> Result<[CS::Proof; 3], Error> { + let cmE_proof = CS::prove(cs_prover_params, tr, &ci.cmE, &w.E, &w.rE, None)?; + let cmW_proof = CS::prove(cs_prover_params, tr, &ci.cmW, &w.W, &w.rW, None)?; + let cmT_proof = CS::prove(cs_prover_params, tr, cmT, &T, &C::ScalarField::zero(), None)?; // cm(T) is committed with rT=0 Ok([cmE_proof, cmW_proof, cmT_proof]) } } @@ -213,67 +213,64 @@ pub mod tests { use crate::utils::vec::vec_scalar_mul; #[allow(clippy::type_complexity)] - pub(crate) fn prepare_simple_fold_inputs() -> ( - PedersenParams, - PoseidonConfig, - R1CS, - Witness, // w1 - CommittedInstance, // ci1 - Witness, // w2 - CommittedInstance, // ci2 - Witness, // w3 - CommittedInstance, // ci3 - Vec, // T - Projective, // cmT - Vec, // r_bits - Fr, // r_Fr - ) { + pub(crate) fn prepare_simple_fold_inputs() -> ( + PedersenParams, + PoseidonConfig, + R1CS, + Witness, // w1 + CommittedInstance, // ci1 + Witness, // w2 + CommittedInstance, // ci2 + Witness, // w3 + CommittedInstance, // ci3 + Vec, // T + C, // cmT + Vec, // r_bits + C::ScalarField, // r_Fr + ) + where + C: CurveGroup, + ::BaseField: PrimeField, + C::ScalarField: Absorb, + { let r1cs = get_test_r1cs(); let z1 = get_test_z(3); let z2 = get_test_z(4); let (w1, x1) = r1cs.split_z(&z1); let (w2, x2) = r1cs.split_z(&z2); - let w1 = Witness::::new(w1.clone(), r1cs.A.n_rows); - let w2 = Witness::::new(w2.clone(), r1cs.A.n_rows); + let w1 = Witness::::new(w1.clone(), r1cs.A.n_rows); + let w2 = Witness::::new(w2.clone(), r1cs.A.n_rows); let mut rng = ark_std::test_rng(); - let pedersen_params = Pedersen::::new_params(&mut rng, r1cs.A.n_cols); + let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); // compute committed instances let ci1 = w1 - .commit::>(&pedersen_params, x1.clone()) + .commit::>(&pedersen_params, x1.clone()) .unwrap(); let ci2 = w2 - .commit::>(&pedersen_params, x2.clone()) + .commit::>(&pedersen_params, x2.clone()) .unwrap(); // NIFS.P - let (T, cmT) = NIFS::>::compute_cmT( - &pedersen_params, - &r1cs, - &w1, - &ci1, - &w2, - &ci2, - ) - .unwrap(); + let (T, cmT) = + NIFS::>::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2) + .unwrap(); - let poseidon_config = poseidon_test_config::(); + let poseidon_config = poseidon_test_config::(); - let r_bits = ChallengeGadget::::get_challenge_native( + let r_bits = ChallengeGadget::::get_challenge_native( &poseidon_config, ci1.clone(), ci2.clone(), cmT, ) .unwrap(); - let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); + let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); - let (w3, ci3) = NIFS::>::fold_instances( - r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT, - ) - .unwrap(); + let (w3, ci3) = + NIFS::>::fold_instances(r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap(); ( pedersen_params, @@ -300,7 +297,7 @@ pub mod tests { let (w1, x1) = r1cs.split_z(&z1); let mut rng = ark_std::test_rng(); - let pedersen_params = Pedersen::::new_params(&mut rng, r1cs.A.n_cols); + let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); // dummy instance, witness and public inputs zeroes let w_dummy = Witness::::new(vec![Fr::zero(); w1.len()], r1cs.A.n_rows); @@ -386,22 +383,22 @@ pub mod tests { Pedersen::::verify( &pedersen_params, &mut transcript_v, - ci3.cmE, - cm_proofs[0].clone(), + &ci3.cmE, + &cm_proofs[0].clone(), ) .unwrap(); Pedersen::::verify( &pedersen_params, &mut transcript_v, - ci3.cmW, - cm_proofs[1].clone(), + &ci3.cmW, + &cm_proofs[1].clone(), ) .unwrap(); Pedersen::::verify( &pedersen_params, &mut transcript_v, - cmT, - cm_proofs[2].clone(), + &cmT, + &cm_proofs[2].clone(), ) .unwrap(); } @@ -413,7 +410,7 @@ pub mod tests { let (w, x) = r1cs.split_z(&z); let mut rng = ark_std::test_rng(); - let pedersen_params = Pedersen::::new_params(&mut rng, r1cs.A.n_cols); + let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); // prepare the running instance let mut running_instance_w = Witness::::new(w.clone(), r1cs.A.n_rows); diff --git a/folding-schemes/src/folding/protogalaxy/folding.rs b/folding-schemes/src/folding/protogalaxy/folding.rs index 946d15d..77a0577 100644 --- a/folding-schemes/src/folding/protogalaxy/folding.rs +++ b/folding-schemes/src/folding/protogalaxy/folding.rs @@ -384,7 +384,7 @@ mod tests { use ark_std::UniformRand; use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z}; - use crate::commitment::{pedersen::Pedersen, CommitmentProver}; + use crate::commitment::{pedersen::Pedersen, CommitmentScheme}; use crate::transcript::poseidon::{poseidon_test_config, PoseidonTranscript}; pub(crate) fn check_instance( @@ -452,7 +452,7 @@ mod tests { Vec>, ) { let mut rng = ark_std::test_rng(); - let pedersen_params = Pedersen::::new_params(&mut rng, 100); // 100 is wip, will get it from actual vec + let (pedersen_params, _) = Pedersen::::setup(&mut rng, 100).unwrap(); // 100 is wip, will get it from actual vec let z = get_test_z::(3); let mut zs: Vec> = Vec::new(); @@ -471,8 +471,8 @@ mod tests { w: z.clone(), r_w: Fr::rand(&mut rng), }; - let phi = - Pedersen::::commit(&pedersen_params, &witness.w, &witness.r_w).unwrap(); + let phi = Pedersen::::commit(&pedersen_params, &witness.w, &witness.r_w) + .unwrap(); let instance = CommittedInstance:: { phi, betas: betas.clone(), @@ -487,9 +487,12 @@ mod tests { w: zs[i].clone(), r_w: Fr::rand(&mut rng), }; - let phi_i = - Pedersen::::commit(&pedersen_params, &witness_i.w, &witness_i.r_w) - .unwrap(); + let phi_i = Pedersen::::commit( + &pedersen_params, + &witness_i.w, + &witness_i.r_w, + ) + .unwrap(); let instance_i = CommittedInstance:: { phi: phi_i, betas: betas.clone(), diff --git a/folding-schemes/src/lib.rs b/folding-schemes/src/lib.rs index 3ff2e1e..7a06a38 100644 --- a/folding-schemes/src/lib.rs +++ b/folding-schemes/src/lib.rs @@ -21,6 +21,7 @@ pub mod utils; #[derive(Debug, Error)] pub enum Error { + // Wrappers on top of other errors #[error("ark_relations::r1cs::SynthesisError")] SynthesisError(#[from] ark_relations::r1cs::SynthesisError), #[error("ark_serialize::SerializationError")] @@ -29,27 +30,16 @@ pub enum Error { PolyCommitError(#[from] ark_poly_commit::Error), #[error("crate::utils::espresso::virtual_polynomial::ArithErrors")] ArithError(#[from] utils::espresso::virtual_polynomial::ArithErrors), + #[error(transparent)] + ProtoGalaxy(folding::protogalaxy::ProtoGalaxyError), #[error("{0}")] Other(String), + // Relation errors #[error("Relation not satisfied")] NotSatisfied, - #[error("Not equal")] - NotEqual, - #[error("Vectors should have the same length ({0}: {1}, {2}: {3})")] - NotSameLength(String, usize, String, usize), - #[error("Vector's length ({0}) is not the expected ({1})")] - NotExpectedLength(usize, usize), - #[error("Vector ({0}) length ({1}) is not a power of two")] - NotPowerOfTwo(String, usize), - #[error("Can not be empty")] - Empty, - #[error("Pedersen parameters length is not sufficient (generators.len={0} < vector.len={1} unsatisfied)")] - PedersenParamsLen(usize, usize), - #[error("Randomness for blinding not found")] - MissingRandomness, - #[error("Commitment verification failed")] - CommitmentVerificationFail, + #[error("SNARK verification failed")] + SNARKVerificationFail, #[error("IVC verification failed")] IVCVerificationFail, #[error("R1CS instance is expected to not be relaxed")] @@ -60,17 +50,42 @@ pub enum Error { SumCheckProveError(String), #[error("Sum-check verify failed: {0}")] SumCheckVerifyError(String), + + // Comparators errors + #[error("Not equal")] + NotEqual, + #[error("Vectors should have the same length ({0}: {1}, {2}: {3})")] + NotSameLength(String, usize, String, usize), + #[error("Vector's length ({0}) is not the expected ({1})")] + NotExpectedLength(usize, usize), + #[error("Vector ({0}) length ({1}) is not a power of two")] + NotPowerOfTwo(String, usize), + #[error("Can not be empty")] + Empty, #[error("Value out of bounds")] OutOfBounds, #[error("Could not construct the Evaluation Domain")] NewDomainFail, + + // Commitment errors + #[error("Pedersen parameters length is not sufficient (generators.len={0} < vector.len={1} unsatisfied)")] + PedersenParamsLen(usize, usize), + #[error("Blinding factor not 0 for Commitment without hiding")] + BlindingNotZero, + #[error("Commitment verification failed")] + CommitmentVerificationFail, + + // Other + #[error("Randomness for blinding not found")] + MissingRandomness, + #[error("Missing value: {0}")] + MissingValue(String), #[error("Feature '{0}' not supported yet")] NotSupportedYet(String), + #[error("Feature '{0}' is not supported and it will not be")] + NotSupported(String), #[error("max i-th step reached (usize limit reached)")] MaxStep, - - #[error(transparent)] - ProtoGalaxy(folding::protogalaxy::ProtoGalaxyError), } /// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined @@ -138,24 +153,25 @@ pub trait Decider< C2::BaseField: PrimeField, { type ProverParam: Clone; - type Proof: Clone; + type Proof; type VerifierParam; type PublicInput: Debug; type CommittedInstanceWithWitness: Debug; type CommittedInstance: Clone + Debug; fn prove( - pp: &Self::ProverParam, + pp: Self::ProverParam, rng: impl RngCore + CryptoRng, folding_scheme: FS, ) -> Result; fn verify( - vp: &Self::VerifierParam, + vp: Self::VerifierParam, i: C1::ScalarField, z_0: Vec, z_i: Vec, running_instance: &Self::CommittedInstance, + incoming_instance: &Self::CommittedInstance, proof: Self::Proof, // returns `Result` to differentiate between an error occurred while performing // the verification steps, and the verification logic of the scheme not passing. diff --git a/folding-schemes/src/utils/vec.rs b/folding-schemes/src/utils/vec.rs index e96b9c8..47c58c3 100644 --- a/folding-schemes/src/utils/vec.rs +++ b/folding-schemes/src/utils/vec.rs @@ -1,4 +1,7 @@ use ark_ff::PrimeField; +use ark_poly::{ + univariate::DensePolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, +}; pub use ark_relations::r1cs::Matrix as R1CSMatrix; use ark_std::cfg_iter; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; @@ -128,6 +131,13 @@ pub fn hadamard(a: &[F], b: &[F]) -> Result, Error> { Ok(cfg_iter!(a).zip(b).map(|(a, b)| *a * b).collect()) } +/// returns the interpolated polynomial of degree=v.len().next_power_of_two(), which passes through all +/// the given elements of v. +pub fn poly_from_vec(v: Vec) -> Result, Error> { + let D = GeneralEvaluationDomain::::new(v.len()).ok_or(Error::NewDomainFail)?; + Ok(Evaluations::from_vec_and_domain(v, D).interpolate()) +} + #[cfg(test)] pub mod tests { use super::*; diff --git a/solidity-verifiers/src/verifiers/mod.rs b/solidity-verifiers/src/verifiers/mod.rs index dddd3e1..2de6d96 100644 --- a/solidity-verifiers/src/verifiers/mod.rs +++ b/solidity-verifiers/src/verifiers/mod.rs @@ -68,8 +68,8 @@ mod tests { use askama::Template; use folding_schemes::{ commitment::{ - kzg::{KZGProver, KZGSetup, ProverKey}, - CommitmentProver, + kzg::{ProverKey, KZG}, + CommitmentScheme, }, transcript::{ poseidon::{poseidon_test_config, PoseidonTranscript}, @@ -131,7 +131,7 @@ mod tests { let (g16_pk, g16_vk) = Groth16::::setup(circuit, &mut rng).unwrap(); let (kzg_pk, kzg_vk): (ProverKey, VerifierKey) = - KZGSetup::::setup(&mut rng, n); + KZG::::setup(&mut rng, n).unwrap(); (kzg_pk, kzg_vk, g16_pk, g16_vk, circuit) } @@ -290,9 +290,8 @@ mod tests { let v: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) .take(DEFAULT_SETUP_LEN) .collect(); - let cm = KZGProver::::commit(&kzg_pk, &v, &Fr::zero()).unwrap(); - let (eval, proof) = - KZGProver::::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap(); + let cm = KZG::::commit(&kzg_pk, &v, &Fr::zero()).unwrap(); + let proof = KZG::::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap(); let template = HeaderInclusion::::builder() .template(kzg_data) .build() @@ -303,10 +302,10 @@ mod tests { let mut evm = Evm::default(); let verifier_address = evm.create(kzg_verifier_bytecode); - let (cm_affine, proof_affine) = (cm.into_affine(), proof.into_affine()); + let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine()); let (x_comm, y_comm) = cm_affine.xy().unwrap(); let (x_proof, y_proof) = proof_affine.xy().unwrap(); - let y = eval.into_bigint().to_bytes_be(); + let y = proof.eval.into_bigint().to_bytes_be(); transcript_v.absorb_point(&cm).unwrap(); let x = transcript_v.get_challenge(); @@ -372,9 +371,8 @@ mod tests { let v: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) .take(DEFAULT_SETUP_LEN) .collect(); - let cm = KZGProver::::commit(&kzg_pk, &v, &Fr::zero()).unwrap(); - let (eval, proof) = - KZGProver::::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap(); + let cm = KZG::::commit(&kzg_pk, &v, &Fr::zero()).unwrap(); + let proof = KZG::::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap(); let decider_template = HeaderInclusion::::builder() .template(nova_cyclefold_data) @@ -387,10 +385,10 @@ mod tests { let mut evm = Evm::default(); let verifier_address = evm.create(nova_cyclefold_verifier_bytecode); - let (cm_affine, proof_affine) = (cm.into_affine(), proof.into_affine()); + let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine()); let (x_comm, y_comm) = cm_affine.xy().unwrap(); let (x_proof, y_proof) = proof_affine.xy().unwrap(); - let y = eval.into_bigint().to_bytes_be(); + let y = proof.eval.into_bigint().to_bytes_be(); transcript_v.absorb_point(&cm).unwrap(); let x = transcript_v.get_challenge();