diff --git a/folding-schemes/src/commitment/kzg.rs b/folding-schemes/src/commitment/kzg.rs index 0538102b..3fef0a7b 100644 --- a/folding-schemes/src/commitment/kzg.rs +++ b/folding-schemes/src/commitment/kzg.rs @@ -9,7 +9,7 @@ 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::{ Commitment as KZG10Commitment, Proof as KZG10Proof, VerifierKey, KZG10, @@ -22,6 +22,7 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; 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 @@ -32,6 +33,12 @@ pub struct ProverKey<'a, C: CurveGroup> { pub powers_of_g: Cow<'a, [C::Affine]>, } +#[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> { @@ -44,8 +51,7 @@ where { type ProverParams = ProverKey<'a, E::G1>; type VerifierParams = VerifierKey; - /// Proof is a tuple containing (evaluation, proof) - type Proof = (E::ScalarField, E::G1); + type Proof = Proof; type ProverChallenge = E::ScalarField; type Challenge = E::ScalarField; @@ -144,7 +150,7 @@ where // should not give an error. .unwrap(); - let evaluation = if remainder_poly.is_zero() { + let eval = if remainder_poly.is_zero() { E::ScalarField::zero() } else { remainder_poly[0] @@ -158,7 +164,7 @@ where &witness_coeffs, ); - Ok((evaluation, proof)) + Ok(Proof { eval, proof }) } fn verify( @@ -187,9 +193,9 @@ where params, // vk &KZG10Commitment(cm.into_affine()), challenge, - proof.0, // eval + proof.eval, &KZG10Proof:: { - w: proof.1.into_affine(), + w: proof.proof.into_affine(), random_v: None, }, )?; @@ -200,13 +206,6 @@ where } } -/// 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 check_degree_is_too_large( degree: usize, num_powers: usize, diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index fab0eb09..1b8261a4 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -15,7 +15,9 @@ use super::{ nifs::NIFS, CommittedInstance, Nova, Witness, }; -use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; +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::frontend::FCircuit; use crate::Error; @@ -61,7 +63,12 @@ where GC1: CurveVar>, GC2: CurveVar>, FC: FCircuit, - CS1: CommitmentScheme, // KZG commitment, where challenge is C1::Fr elem + 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, @@ -200,6 +207,10 @@ where 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], diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index 49397cb1..d199d504 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -4,17 +4,19 @@ use ark_crypto_primitives::crh::poseidon::constraints::CRHParametersVar; use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; use ark_ec::{CurveGroup, Group}; 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}; @@ -30,8 +32,9 @@ use crate::transcript::{ poseidon::{PoseidonTranscript, PoseidonTranscriptVar}, Transcript, TranscriptVar, }; -use crate::utils::gadgets::{ - hadamard, mat_vec_mul_sparse, vec_add, vec_scalar_mul, SparseMatrixVar, +use crate::utils::{ + gadgets::{hadamard, mat_vec_mul_sparse, vec_add, vec_scalar_mul, SparseMatrixVar}, + vec::poly_from_vec, }; use crate::Error; @@ -234,6 +237,8 @@ where /// KZG challenges pub kzg_c_W: Option, pub kzg_c_E: Option, + pub eval_W: Option, + pub eval_E: Option, } impl DeciderEthCircuit where @@ -275,6 +280,22 @@ where 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, @@ -304,6 +325,8 @@ where 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), }) } } @@ -366,6 +389,12 @@ where 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( cs.clone(), @@ -377,7 +406,7 @@ where [vec![U_i1.u.clone()], U_i1.x.to_vec(), W_i1.W.to_vec()].concat(); RelaxedR1CSGadget::, FpVar>>::check( r1cs, - W_i1.E, + W_i1.E.clone(), U_i1.u.clone(), z_U1, )?; @@ -469,7 +498,16 @@ where incircuit_c_W.enforce_equal(&kzg_c_W)?; incircuit_c_E.enforce_equal(&kzg_c_E)?; - // 7. compute the NIFS.V challenge and check that matches the one from the public input (so we + // 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)))?; @@ -481,7 +519,6 @@ where 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)))?; @@ -491,6 +528,24 @@ where } } +/// 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 { @@ -845,4 +900,34 @@ pub mod tests { 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/utils/vec.rs b/folding-schemes/src/utils/vec.rs index e96b9c86..47c58c32 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 920c4b42..2de6d969 100644 --- a/solidity-verifiers/src/verifiers/mod.rs +++ b/solidity-verifiers/src/verifiers/mod.rs @@ -291,8 +291,7 @@ mod tests { .take(DEFAULT_SETUP_LEN) .collect(); let cm = KZG::::commit(&kzg_pk, &v, &Fr::zero()).unwrap(); - let (eval, proof) = - KZG::::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).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(); @@ -373,8 +372,7 @@ mod tests { .take(DEFAULT_SETUP_LEN) .collect(); let cm = KZG::::commit(&kzg_pk, &v, &Fr::zero()).unwrap(); - let (eval, proof) = - KZG::::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).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();