From 5dc7e5f5a7f3180d7b3b26f5d3f4db918be43d20 Mon Sep 17 00:00:00 2001 From: winderica Date: Sun, 6 Oct 2024 16:23:45 +0800 Subject: [PATCH] Verifier should check the commitments in committed instances --- .../src/folding/circuits/decider/mod.rs | 3 + .../src/folding/circuits/decider/off_chain.rs | 24 +++++- .../src/folding/circuits/decider/on_chain.rs | 15 +++- .../src/folding/circuits/nonnative/affine.rs | 49 +++++++++++ .../src/folding/hypernova/decider_eth.rs | 57 ++++++------ .../folding/hypernova/decider_eth_circuit.rs | 10 ++- folding-schemes/src/folding/nova/decider.rs | 45 +++++++--- .../src/folding/nova/decider_circuits.rs | 1 + .../src/folding/nova/decider_eth.rs | 86 ++++++++++++------- .../src/folding/nova/decider_eth_circuit.rs | 10 ++- .../protogalaxy/decider_eth_circuit.rs | 14 ++- solidity-verifiers/src/utils/mod.rs | 2 +- .../src/verifiers/nova_cyclefold.rs | 13 +-- .../nova_cyclefold_decider.askama.sol | 63 ++++++++------ 14 files changed, 274 insertions(+), 118 deletions(-) diff --git a/folding-schemes/src/folding/circuits/decider/mod.rs b/folding-schemes/src/folding/circuits/decider/mod.rs index 5f14d3a3..1142f809 100644 --- a/folding-schemes/src/folding/circuits/decider/mod.rs +++ b/folding-schemes/src/folding/circuits/decider/mod.rs @@ -104,6 +104,8 @@ pub trait DeciderEnabledNIFS< { type ProofDummyCfg; type Proof: Dummy; + type RandomnessDummyCfg; + type Randomness: Dummy; #[allow(clippy::too_many_arguments)] fn fold_gadget( @@ -114,6 +116,7 @@ pub trait DeciderEnabledNIFS< U_vec: Vec>>, u: IU::Var, proof: Self::Proof, + randomness: Self::Randomness, ) -> Result; } diff --git a/folding-schemes/src/folding/circuits/decider/off_chain.rs b/folding-schemes/src/folding/circuits/decider/off_chain.rs index 9c5f8ed7..9a01e9cc 100644 --- a/folding-schemes/src/folding/circuits/decider/off_chain.rs +++ b/folding-schemes/src/folding/circuits/decider/off_chain.rs @@ -25,6 +25,7 @@ use crate::{ CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, CycleFoldWitness, }, decider::{EvalGadget, KZGChallengesGadget}, + nonnative::affine::NonNativeAffineVar, CF1, CF2, }, nova::{circuits::CommittedInstanceVar, decider_eth_circuit::WitnessVar}, @@ -69,6 +70,7 @@ pub struct GenericOffchainDeciderCircuit1< /// Helper for folding verification pub proof: D::Proof, + pub randomness: D::Randomness, /// CycleFold running instance pub cf_U_i: CycleFoldCommittedInstance, @@ -94,16 +96,26 @@ impl< &R1CS>, PoseidonConfig>, D::ProofDummyCfg, + D::RandomnessDummyCfg, usize, usize, )> for GenericOffchainDeciderCircuit1 { fn dummy( - (arith, cf_arith, poseidon_config, proof_config, state_len, num_commitments): ( + ( + arith, + cf_arith, + poseidon_config, + proof_config, + randomness_config, + state_len, + num_commitments, + ): ( A, &R1CS>, PoseidonConfig>, D::ProofDummyCfg, + D::RandomnessDummyCfg, usize, usize, ), @@ -123,6 +135,7 @@ impl< U_i1: RU::dummy(&arith), W_i1: W::dummy(&arith), proof: D::Proof::dummy(proof_config), + randomness: D::Randomness::dummy(randomness_config), cf_U_i: CycleFoldCommittedInstance::dummy(cf_arith), kzg_challenges: vec![Zero::zero(); num_commitments], kzg_evaluations: vec![Zero::zero(); num_commitments], @@ -144,7 +157,7 @@ impl< > ConstraintSynthesizer> for GenericOffchainDeciderCircuit1 where - RU::Var: AbsorbGadget>, + RU::Var: AbsorbGadget> + CommittedInstanceVarOps>, CF1: Absorb, { fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { @@ -158,8 +171,12 @@ where let u_i = IU::Var::new_witness(cs.clone(), || Ok(self.u_i))?; let U_i = RU::Var::new_witness(cs.clone(), || Ok(self.U_i))?; // here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i)) - let U_i1 = RU::Var::new_input(cs.clone(), || Ok(self.U_i1))?; + let U_i1_commitments = Vec::>::new_input(cs.clone(), || { + Ok(self.U_i1.get_commitments()) + })?; + let U_i1 = RU::Var::new_witness(cs.clone(), || Ok(self.U_i1))?; let W_i1 = W::Var::new_witness(cs.clone(), || Ok(self.W_i1))?; + U_i1.get_commitments().enforce_equal(&U_i1_commitments)?; let cf_U_i = CycleFoldCommittedInstanceVar::::new_input(cs.clone(), || Ok(self.cf_U_i))?; @@ -194,6 +211,7 @@ where U_i_vec, u_i, self.proof, + self.randomness, )? .enforce_partial_equal(&U_i1)?; diff --git a/folding-schemes/src/folding/circuits/decider/on_chain.rs b/folding-schemes/src/folding/circuits/decider/on_chain.rs index b5f4f4bf..48ee5806 100644 --- a/folding-schemes/src/folding/circuits/decider/on_chain.rs +++ b/folding-schemes/src/folding/circuits/decider/on_chain.rs @@ -21,6 +21,7 @@ use crate::{ CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, CycleFoldWitness, }, decider::{EvalGadget, KZGChallengesGadget}, + nonnative::affine::NonNativeAffineVar, CF1, CF2, }, traits::{CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps, WitnessVarOps}, @@ -97,6 +98,7 @@ pub struct GenericOnchainDeciderCircuit< /// Helper for folding verification pub proof: D::Proof, + pub randomness: D::Randomness, /// CycleFold running instance pub cf_U_i: CycleFoldCommittedInstance, @@ -124,6 +126,7 @@ impl< PedersenParams, PoseidonConfig>, D::ProofDummyCfg, + D::RandomnessDummyCfg, usize, usize, )> for GenericOnchainDeciderCircuit @@ -135,6 +138,7 @@ impl< cf_pedersen_params, poseidon_config, proof_config, + randomness_config, state_len, num_commitments, ): ( @@ -143,6 +147,7 @@ impl< PedersenParams, PoseidonConfig>, D::ProofDummyCfg, + D::RandomnessDummyCfg, usize, usize, ), @@ -163,6 +168,7 @@ impl< U_i1: RU::dummy(&arith), W_i1: W::dummy(&arith), proof: D::Proof::dummy(proof_config), + randomness: D::Randomness::dummy(randomness_config), cf_U_i: CycleFoldCommittedInstance::dummy(&cf_arith), cf_W_i: CycleFoldWitness::dummy(&cf_arith), kzg_challenges: vec![Zero::zero(); num_commitments], @@ -186,7 +192,7 @@ impl< > ConstraintSynthesizer> for GenericOnchainDeciderCircuit where - RU::Var: AbsorbGadget>, + RU::Var: AbsorbGadget> + CommittedInstanceVarOps>, CF1: Absorb, { fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { @@ -200,8 +206,12 @@ where let u_i = IU::Var::new_witness(cs.clone(), || Ok(self.u_i))?; let U_i = RU::Var::new_witness(cs.clone(), || Ok(self.U_i))?; // here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i)) - let U_i1 = RU::Var::new_input(cs.clone(), || Ok(self.U_i1))?; + let U_i1_commitments = Vec::>::new_input(cs.clone(), || { + Ok(self.U_i1.get_commitments()) + })?; + let U_i1 = RU::Var::new_witness(cs.clone(), || Ok(self.U_i1))?; let W_i1 = W::Var::new_witness(cs.clone(), || Ok(self.W_i1))?; + U_i1.get_commitments().enforce_equal(&U_i1_commitments)?; let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || Ok(self.cf_U_i))?; @@ -291,6 +301,7 @@ where U_i_vec, u_i, self.proof, + self.randomness, )? .enforce_partial_equal(&U_i1)?; diff --git a/folding-schemes/src/folding/circuits/nonnative/affine.rs b/folding-schemes/src/folding/circuits/nonnative/affine.rs index 58f6b2ae..9502a1b0 100644 --- a/folding-schemes/src/folding/circuits/nonnative/affine.rs +++ b/folding-schemes/src/folding/circuits/nonnative/affine.rs @@ -2,7 +2,9 @@ use ark_ec::{short_weierstrass::SWFlags, AffineRepr, CurveGroup}; use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, + eq::EqGadget, fields::fp::FpVar, + prelude::Boolean, R1CSVar, ToConstraintFieldGadget, }; use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; @@ -93,6 +95,53 @@ impl ToConstraintFieldGadget for NonNativeAffineV } } +impl EqGadget for NonNativeAffineVar { + fn is_eq(&self, other: &Self) -> Result, SynthesisError> { + let mut result = Boolean::TRUE; + if self.x.0.len() != other.x.0.len() { + return Err(SynthesisError::Unsatisfiable); + } + if self.y.0.len() != other.y.0.len() { + return Err(SynthesisError::Unsatisfiable); + } + for (l, r) in self + .x + .0 + .iter() + .chain(&self.y.0) + .zip(other.x.0.iter().chain(&other.y.0)) + { + if l.ub != r.ub { + return Err(SynthesisError::Unsatisfiable); + } + result = result.and(&l.v.is_eq(&r.v)?)?; + } + Ok(result) + } + + fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> { + if self.x.0.len() != other.x.0.len() { + return Err(SynthesisError::Unsatisfiable); + } + if self.y.0.len() != other.y.0.len() { + return Err(SynthesisError::Unsatisfiable); + } + for (l, r) in self + .x + .0 + .iter() + .chain(&self.y.0) + .zip(other.x.0.iter().chain(&other.y.0)) + { + if l.ub != r.ub { + return Err(SynthesisError::Unsatisfiable); + } + l.v.enforce_equal(&r.v)?; + } + Ok(()) + } +} + /// The out-circuit counterpart of `NonNativeAffineVar::to_constraint_field` #[allow(clippy::type_complexity)] pub(crate) fn nonnative_affine_to_field_elements( diff --git a/folding-schemes/src/folding/hypernova/decider_eth.rs b/folding-schemes/src/folding/hypernova/decider_eth.rs index 07b232be..c5b9c078 100644 --- a/folding-schemes/src/folding/hypernova/decider_eth.rs +++ b/folding-schemes/src/folding/hypernova/decider_eth.rs @@ -10,13 +10,13 @@ use ark_std::{One, Zero}; use core::marker::PhantomData; pub use super::decider_eth_circuit::DeciderEthCircuit; -use super::{lcccs::LCCCS, HyperNova}; +use super::HyperNova; use crate::commitment::{ kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme, }; use crate::folding::circuits::CF2; use crate::folding::nova::decider_eth::VerifierParam; -use crate::folding::traits::{CommittedInstanceOps, Inputize, WitnessOps}; +use crate::folding::traits::{Inputize, WitnessOps}; use crate::frontend::FCircuit; use crate::Error; use crate::{Decider as DeciderTrait, FoldingScheme}; @@ -30,7 +30,8 @@ where { snark_proof: S::Proof, kzg_proof: CS1::Proof, - U_final: LCCCS, + // rho used at the last fold, U_{i+1}=NIMFS.V(rho, U_i, u_i), it is checked in-circuit + rho: C1::ScalarField, // the KZG challenge is provided by the prover, but in-circuit it is checked to match // the in-circuit computed computed one. kzg_challenge: C1::ScalarField, @@ -86,7 +87,7 @@ where type Proof = Proof; type VerifierParam = VerifierParam; type PublicInput = Vec; - type CommittedInstance = (); + type CommittedInstance = Vec; fn preprocess( mut rng: impl RngCore + CryptoRng, @@ -133,7 +134,7 @@ where let circuit = DeciderEthCircuit::::try_from(HyperNova::from(folding_scheme)).unwrap(); - let U_final = circuit.U_i1.clone(); + let rho = circuit.randomness; // get the challenges that have been already computed when preparing the circuit inputs in // the above `try_from` call @@ -155,7 +156,7 @@ where Ok(Self::Proof { snark_proof, - U_final, + rho, kzg_proof: (kzg_proofs.len() == 1) .then(|| kzg_proofs[0].clone()) .ok_or(Error::NotExpectedLength(kzg_proofs.len(), 1))?, @@ -171,8 +172,8 @@ where z_0: Vec, z_i: Vec, // we don't use the instances at the verifier level, since we check them in-circuit - _running_instance: &Self::CommittedInstance, - _incoming_instance: &Self::CommittedInstance, + running_commitments: &Self::CommittedInstance, + incoming_commitments: &Self::CommittedInstance, proof: &Self::Proof, ) -> Result { if i <= C1::ScalarField::one() { @@ -185,16 +186,18 @@ where cs_vp, } = vp; - let U = proof.U_final.clone(); + let U_C = running_commitments[0]; + let u_C = incoming_commitments[0]; + let C = U_C + u_C.mul(proof.rho); // Note: the NIMFS proof is checked inside the DeciderEthCircuit, which ensures that the // 'proof.U_i1' is correctly computed let public_input: Vec = [ - vec![pp_hash, i], - z_0, - z_i, - U.inputize(), - vec![proof.kzg_challenge, proof.kzg_proof.eval], + &[pp_hash, i][..], + &z_0, + &z_i, + &C.inputize(), + &[proof.kzg_challenge, proof.kzg_proof.eval, proof.rho], ] .concat(); @@ -204,18 +207,8 @@ where return Err(Error::SNARKVerificationFail); } - let commitments = U.get_commitments(); - if commitments.len() != 1 { - return Err(Error::NotExpectedLength(commitments.len(), 1)); - } - // we're at the Ethereum EVM case, so the CS1 is KZG commitments - CS1::verify_with_challenge( - &cs_vp, - proof.kzg_challenge, - &commitments[0], - &proof.kzg_proof, - )?; + CS1::verify_with_challenge(&cs_vp, proof.kzg_challenge, &C, &proof.kzg_proof)?; Ok(true) } @@ -231,9 +224,11 @@ pub mod tests { use super::*; use crate::commitment::{kzg::KZG, pedersen::Pedersen}; use crate::folding::hypernova::cccs::CCCS; + use crate::folding::hypernova::lcccs::LCCCS; use crate::folding::hypernova::{ PreprocessorParam, ProverParams, VerifierParams as HyperNovaVerifierParams, }; + use crate::folding::traits::CommittedInstanceOps; use crate::frontend::utils::CubicFCircuit; use crate::transcript::poseidon::poseidon_canonical_config; @@ -298,8 +293,8 @@ pub mod tests { hypernova.i, hypernova.z_0, hypernova.z_i, - &(), - &(), + &hypernova.U_i.get_commitments(), + &hypernova.u_i.get_commitments(), &proof, ) .unwrap(); @@ -415,8 +410,8 @@ pub mod tests { hypernova.i, hypernova.z_0.clone(), hypernova.z_i.clone(), - &(), - &(), + &hypernova.U_i.get_commitments(), + &hypernova.u_i.get_commitments(), &proof, ) .unwrap(); @@ -482,8 +477,8 @@ pub mod tests { i_deserialized, z_0_deserialized.clone(), z_i_deserialized.clone(), - &(), - &(), + &hypernova.U_i.get_commitments(), + &hypernova.u_i.get_commitments(), &proof_deserialized, ) .unwrap(); diff --git a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs index 5ecff42c..35ea0dc9 100644 --- a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs @@ -135,7 +135,7 @@ where // compute the U_{i+1}, W_{i+1}, by folding the last running & incoming instances let mut transcript = PoseidonSponge::::new(&hn.poseidon_config); transcript.absorb(&hn.pp_hash); - let (nimfs_proof, U_i1, W_i1, _) = NIMFS::>::prove( + let (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( &mut transcript, &hn.ccs, &[hn.U_i.clone()], @@ -173,6 +173,7 @@ where U_i1, W_i1, proof: nimfs_proof, + randomness: rho, cf_U_i: hn.cf_U_i, cf_W_i: hn.cf_W_i, kzg_challenges, @@ -190,6 +191,8 @@ where { type ProofDummyCfg = (usize, usize, usize, usize); type Proof = NIMFSProof; + type Randomness = CF1; + type RandomnessDummyCfg = (); fn fold_gadget( arith: &CCS>, @@ -199,11 +202,13 @@ where _U_vec: Vec>>, u: CCCSVar, proof: Self::Proof, + randomness: Self::Randomness, ) -> Result, SynthesisError> { let cs = transcript.cs(); transcript.absorb(&pp_hash)?; let nimfs_proof = NIMFSProofVar::::new_witness(cs.clone(), || Ok(proof))?; - let (computed_U_i1, _) = NIMFSGadget::::verify( + let rho = FpVar::>::new_input(cs.clone(), || Ok(randomness))?; + let (computed_U_i1, rho_bits) = NIMFSGadget::::verify( cs.clone(), arith, transcript, @@ -212,6 +217,7 @@ where nimfs_proof, Boolean::TRUE, // enabled )?; + Boolean::le_bits_to_fp_var(&rho_bits)?.enforce_equal(&rho)?; Ok(computed_U_i1) } } diff --git a/folding-schemes/src/folding/nova/decider.rs b/folding-schemes/src/folding/nova/decider.rs index faa654b9..a0f134f1 100644 --- a/folding-schemes/src/folding/nova/decider.rs +++ b/folding-schemes/src/folding/nova/decider.rs @@ -13,7 +13,7 @@ use ark_std::{One, Zero}; use core::marker::PhantomData; use super::decider_circuits::{DeciderCircuit1, DeciderCircuit2}; -use super::{CommittedInstance, Nova}; +use super::Nova; use crate::commitment::CommitmentScheme; use crate::folding::circuits::{ cyclefold::{CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar}, @@ -38,7 +38,10 @@ where c2_snark_proof: S2::Proof, cs1_proofs: [CS1::Proof; 2], cs2_proofs: [CS2::Proof; 2], - U_final: CommittedInstance, + // 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, // cyclefold committed instance cf_U_final: CycleFoldCommittedInstance, // the CS challenges are provided by the prover, but in-circuit they are checked to match the @@ -141,7 +144,7 @@ where S2::VerifyingKey, >; type PublicInput = Vec; - type CommittedInstance = (); + type CommittedInstance = Vec; fn preprocess( mut rng: impl RngCore + CryptoRng, @@ -194,7 +197,8 @@ where let circuit1 = DeciderCircuit1::::try_from(Nova::from(fs.clone()))?; let circuit2 = DeciderCircuit2::::try_from(Nova::from(fs))?; - let U_final = circuit1.U_i1.clone(); + let cmT = circuit1.proof; + let r = circuit1.randomness; let cf_U_final = circuit1.cf_U_i.clone(); let c1_kzg_challenges = circuit1.kzg_challenges.clone(); @@ -228,7 +232,8 @@ where c2_snark_proof, cs1_proofs: c1_kzg_proofs.try_into().unwrap(), cs2_proofs: c2_kzg_proofs.try_into().unwrap(), - U_final, + cmT, + r, cf_U_final, cs1_challenges: c1_kzg_challenges.try_into().unwrap(), cs2_challenges: c2_kzg_challenges.try_into().unwrap(), @@ -241,15 +246,20 @@ where z_0: Vec, z_i: Vec, // we don't use the instances at the verifier level, since we check them in-circuit - _running_instance: &Self::CommittedInstance, - _incoming_instance: &Self::CommittedInstance, + running_commitments: &Self::CommittedInstance, + incoming_commitments: &Self::CommittedInstance, proof: &Self::Proof, ) -> Result { if i <= C1::ScalarField::one() { return Err(Error::NotEnoughSteps); } - let U = proof.U_final.clone(); + let U_cmW = running_commitments[0]; + let U_cmE = running_commitments[1]; + let u_cmW = incoming_commitments[0]; + let u_cmE = incoming_commitments[1]; + let cmW = U_cmW + u_cmW.mul(proof.r); + let cmE = U_cmE + proof.cmT.mul(proof.r) + u_cmE.mul(proof.r * proof.r); let cf_U = proof.cf_U_final.clone(); // snark proof 1 @@ -257,10 +267,13 @@ where &[vp.pp_hash, i][..], &z_0, &z_i, - &U.inputize(), + &cmW.inputize(), + &cmE.inputize(), &Inputize::, CycleFoldCommittedInstanceVar>::inputize(&cf_U), &proof.cs1_challenges, &proof.cs1_proofs.iter().map(|p| p.eval).collect::>(), + &proof.cmT.inputize(), + &[proof.r], ] .concat(); @@ -289,8 +302,7 @@ where } // check C1 commitments (main instance commitments) - for ((cm, &c), pi) in U - .get_commitments() + for ((cm, &c), pi) in [cmW, cmE] .iter() .zip(&proof.cs1_challenges) .zip(&proof.cs1_proofs) @@ -391,7 +403,16 @@ pub mod tests { // decider proof verification let start = Instant::now(); - let verified = D::verify(decider_vp, nova.i, nova.z_0, nova.z_i, &(), &(), &proof).unwrap(); + let verified = D::verify( + decider_vp, + nova.i, + nova.z_0, + nova.z_i, + &nova.U_i.get_commitments(), + &nova.u_i.get_commitments(), + &proof, + ) + .unwrap(); assert!(verified); println!("Decider verify, {:?}", start.elapsed()); } diff --git a/folding-schemes/src/folding/nova/decider_circuits.rs b/folding-schemes/src/folding/nova/decider_circuits.rs index 160f3909..820f2b95 100644 --- a/folding-schemes/src/folding/nova/decider_circuits.rs +++ b/folding-schemes/src/folding/nova/decider_circuits.rs @@ -109,6 +109,7 @@ where U_i1, W_i1, proof: cmT, + randomness: r_Fr, cf_U_i: nova.cf_U_i, kzg_challenges, kzg_evaluations, diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index 92a2e295..2edf9008 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -22,7 +22,7 @@ use crate::commitment::{ CommitmentScheme, }; use crate::folding::circuits::CF2; -use crate::folding::traits::{CommittedInstanceOps, Inputize, WitnessOps}; +use crate::folding::traits::{Inputize, WitnessOps}; use crate::frontend::FCircuit; use crate::Error; use crate::{Decider as DeciderTrait, FoldingScheme}; @@ -36,7 +36,10 @@ where { snark_proof: S::Proof, kzg_proofs: [CS::Proof; 2], - U_final: CommittedInstance, + // 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: C, + r: C::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: [C::ScalarField; 2], @@ -104,7 +107,7 @@ where type Proof = Proof; type VerifierParam = VerifierParam; type PublicInput = Vec; - type CommittedInstance = (); + type CommittedInstance = Vec; fn preprocess( mut rng: impl RngCore + CryptoRng, @@ -149,7 +152,8 @@ where let circuit = DeciderEthCircuit::::try_from(Nova::from(folding_scheme))?; - let U_final = circuit.U_i1.clone(); + let cmT = circuit.proof; + let r = circuit.randomness; // get the challenges that have been already computed when preparing the circuit inputs in // the above `try_from` call @@ -171,7 +175,8 @@ where Ok(Self::Proof { snark_proof, - U_final, + cmT, + r, kzg_proofs: kzg_proofs.try_into().unwrap(), kzg_challenges: kzg_challenges.try_into().unwrap(), }) @@ -183,8 +188,8 @@ where z_0: Vec, z_i: Vec, // we don't use the instances at the verifier level, since we check them in-circuit - _running_instance: &Self::CommittedInstance, - _incoming_instance: &Self::CommittedInstance, + running_commitments: &Self::CommittedInstance, + incoming_commitments: &Self::CommittedInstance, proof: &Self::Proof, ) -> Result { if i <= C1::ScalarField::one() { @@ -196,16 +201,26 @@ where snark_vp, cs_vp, } = vp; - - let U = proof.U_final.clone(); + let U_cmW = running_commitments[0]; + let U_cmE = running_commitments[1]; + let u_cmW = incoming_commitments[0]; + let u_cmE = incoming_commitments[1]; + if !u_cmE.is_zero() { + return Err(Error::NotIncomingCommittedInstance); + } + let cmW = U_cmW + u_cmW.mul(proof.r); + let cmE = U_cmE + proof.cmT.mul(proof.r); let public_input = [ &[pp_hash, i][..], &z_0, &z_i, - &U.inputize(), + &cmW.inputize(), + &cmE.inputize(), &proof.kzg_challenges, &proof.kzg_proofs.iter().map(|p| p.eval).collect::>(), + &proof.cmT.inputize(), + &[proof.r], ] .concat(); @@ -215,8 +230,7 @@ where return Err(Error::SNARKVerificationFail); } - for ((cm, &c), pi) in U - .get_commitments() + for ((cm, &c), pi) in [cmW, cmE] .iter() .zip(&proof.kzg_challenges) .zip(&proof.kzg_proofs) @@ -235,8 +249,8 @@ pub fn prepare_calldata( i: ark_bn254::Fr, z_0: Vec, z_i: Vec, - _running_instance: &CommittedInstance, - _incoming_instance: &CommittedInstance, + running_instance: &CommittedInstance, + incoming_instance: &CommittedInstance, proof: Proof, Groth16>, ) -> Result, Error> { Ok(vec![ @@ -248,18 +262,14 @@ pub fn prepare_calldata( z_i.iter() .flat_map(|v| v.into_bigint().to_bytes_be()) .collect::>(), // z_i - point_to_eth_format(proof.U_final.cmW.into_affine())?, // U_final_cmW - point_to_eth_format(proof.U_final.cmE.into_affine())?, // U_final_cmE - proof.U_final.u.into_bigint().to_bytes_be(), // U_final_u - proof - .U_final - .x - .iter() - .flat_map(|v| v.into_bigint().to_bytes_be()) - .collect::>(), // U_final_x - point_to_eth_format(proof.snark_proof.a)?, // pA - point2_to_eth_format(proof.snark_proof.b)?, // pB - point_to_eth_format(proof.snark_proof.c)?, // pC + point_to_eth_format(running_instance.cmW.into_affine())?, + point_to_eth_format(running_instance.cmE.into_affine())?, + point_to_eth_format(incoming_instance.cmW.into_affine())?, + point_to_eth_format(proof.cmT.into_affine())?, // cmT + proof.r.into_bigint().to_bytes_be(), // r + point_to_eth_format(proof.snark_proof.a)?, // pA + point2_to_eth_format(proof.snark_proof.b)?, // pB + point_to_eth_format(proof.snark_proof.c)?, // pC proof.kzg_challenges[0].into_bigint().to_bytes_be(), // challenge_W proof.kzg_challenges[1].into_bigint().to_bytes_be(), // challenge_E proof.kzg_proofs[0].eval.into_bigint().to_bytes_be(), // eval W @@ -304,6 +314,7 @@ pub mod tests { use crate::folding::nova::{ PreprocessorParam, ProverParams as NovaProverParams, VerifierParams as NovaVerifierParams, }; + use crate::folding::traits::CommittedInstanceOps; use crate::frontend::utils::CubicFCircuit; use crate::transcript::poseidon::poseidon_canonical_config; @@ -365,8 +376,8 @@ pub mod tests { nova.i, nova.z_0.clone(), nova.z_i.clone(), - &(), - &(), + &nova.U_i.get_commitments(), + &nova.u_i.get_commitments(), &proof, ) .unwrap(); @@ -374,7 +385,16 @@ pub mod tests { println!("Decider verify, {:?}", start.elapsed()); // decider proof verification using the deserialized data - let verified = D::verify(decider_vp, nova.i, nova.z_0, nova.z_i, &(), &(), &proof).unwrap(); + let verified = D::verify( + decider_vp, + nova.i, + nova.z_0, + nova.z_i, + &nova.U_i.get_commitments(), + &nova.u_i.get_commitments(), + &proof, + ) + .unwrap(); assert!(verified); } @@ -476,8 +496,8 @@ pub mod tests { nova.i, nova.z_0.clone(), nova.z_i.clone(), - &(), - &(), + &nova.U_i.get_commitments(), + &nova.u_i.get_commitments(), &proof, ) .unwrap(); @@ -532,8 +552,8 @@ pub mod tests { i_deserialized, z_0_deserialized, z_i_deserialized, - &(), - &(), + &nova.U_i.get_commitments(), + &nova.u_i.get_commitments(), &proof_deserialized, ) .unwrap(); diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index 18167060..83d4e19e 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -12,6 +12,7 @@ use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, boolean::Boolean, + eq::EqGadget, fields::fp::FpVar, prelude::CurveVar, ToConstraintFieldGadget, @@ -166,6 +167,7 @@ where U_i1, W_i1, proof: cmT, + randomness: r_Fr, cf_U_i: nova.cf_U_i, cf_W_i: nova.cf_W_i, kzg_challenges, @@ -184,6 +186,8 @@ where { type ProofDummyCfg = (); type Proof = C; + type RandomnessDummyCfg = (); + type Randomness = CF1; fn fold_gadget( _arith: &R1CS>, @@ -193,9 +197,11 @@ where U_vec: Vec>>, u: CommittedInstanceVar, proof: C, + randomness: CF1, ) -> Result, SynthesisError> { let cs = transcript.cs(); - let cmT = NonNativeAffineVar::new_witness(cs.clone(), || Ok(proof))?; + let cmT = NonNativeAffineVar::new_input(cs.clone(), || Ok(proof))?; + let r = FpVar::new_input(cs.clone(), || Ok(randomness))?; let r_bits = ChallengeGadget::>::get_challenge_gadget( transcript, pp_hash, @@ -203,7 +209,7 @@ where u.clone(), Some(cmT), )?; - let r = Boolean::le_bits_to_fp_var(&r_bits)?; + Boolean::le_bits_to_fp_var(&r_bits)?.enforce_equal(&r)?; NIFSGadget::::fold_committed_instance(r, U, u) } diff --git a/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs b/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs index 7dd0f671..04756beb 100644 --- a/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs @@ -9,6 +9,7 @@ use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, + eq::EqGadget, fields::fp::FpVar, groups::CurveVar, ToConstraintFieldGadget, @@ -101,7 +102,7 @@ where fn try_from(protogalaxy: ProtoGalaxy) -> Result { let mut transcript = PoseidonSponge::::new(&protogalaxy.poseidon_config); - let (U_i1, W_i1, proof, _) = Folding::prove( + let (U_i1, W_i1, proof, aux) = Folding::prove( &mut transcript, &protogalaxy.r1cs, &protogalaxy.U_i, @@ -139,6 +140,7 @@ where U_i1, W_i1, proof, + randomness: aux.L_X_evals, cf_U_i: protogalaxy.cf_U_i, cf_W_i: protogalaxy.cf_W_i, kzg_challenges, @@ -160,6 +162,8 @@ impl { type Proof = ProtoGalaxyProof>; type ProofDummyCfg = (usize, usize, usize); + type Randomness = Vec>; + type RandomnessDummyCfg = usize; fn fold_gadget( _arith: &R1CS>, @@ -169,12 +173,18 @@ impl _U_vec: Vec>>, u: CommittedInstanceVar, proof: Self::Proof, + randomness: Self::Randomness, ) -> Result, SynthesisError> { let cs = transcript.cs(); let F_coeffs = Vec::new_witness(cs.clone(), || Ok(&proof.F_coeffs[..]))?; let K_coeffs = Vec::new_witness(cs.clone(), || Ok(&proof.K_coeffs[..]))?; + let challenge = Vec::new_input(cs.clone(), || Ok(randomness))?; - Ok(FoldingGadget::fold_committed_instance(transcript, &U, &[u], F_coeffs, K_coeffs)?.0) + let (U_next, L_X_evals) = + FoldingGadget::fold_committed_instance(transcript, &U, &[u], F_coeffs, K_coeffs)?; + L_X_evals.enforce_equal(&challenge)?; + + Ok(U_next) } } diff --git a/solidity-verifiers/src/utils/mod.rs b/solidity-verifiers/src/utils/mod.rs index d44b0924..deb4c7e4 100644 --- a/solidity-verifiers/src/utils/mod.rs +++ b/solidity-verifiers/src/utils/mod.rs @@ -22,7 +22,7 @@ pub fn get_function_selector_for_nova_cyclefold_verifier( first_param_array_length: usize, ) -> [u8; 4] { let mut hasher = Sha3::keccak256(); - let fn_sig = format!("verifyNovaProof(uint256[{}],uint256[4],uint256[3],uint256[2],uint256[2][2],uint256[2],uint256[4],uint256[2][2])", first_param_array_length); + let fn_sig = format!("verifyNovaProof(uint256[{}],uint256[4],uint256[2],uint256[3],uint256[2],uint256[2][2],uint256[2],uint256[4],uint256[2][2])", first_param_array_length); hasher.input_str(&fn_sig); let hash = &mut [0u8; 32]; hasher.result(hash); diff --git a/solidity-verifiers/src/verifiers/nova_cyclefold.rs b/solidity-verifiers/src/verifiers/nova_cyclefold.rs index 13117406..6df52654 100644 --- a/solidity-verifiers/src/verifiers/nova_cyclefold.rs +++ b/solidity-verifiers/src/verifiers/nova_cyclefold.rs @@ -153,9 +153,12 @@ mod tests { use folding_schemes::{ commitment::{kzg::KZG, pedersen::Pedersen}, - folding::nova::{ - decider_eth::{prepare_calldata, Decider as DeciderEth}, - Nova, PreprocessorParam, + folding::{ + nova::{ + decider_eth::{prepare_calldata, Decider as DeciderEth}, + Nova, PreprocessorParam, + }, + traits::CommittedInstanceOps, }, frontend::FCircuit, transcript::poseidon::poseidon_canonical_config, @@ -388,8 +391,8 @@ mod tests { nova.i, nova.z_0.clone(), nova.z_i.clone(), - &(), - &(), + &nova.U_i.get_commitments(), + &nova.u_i.get_commitments(), &proof, ) .unwrap(); diff --git a/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol b/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol index b27f9473..c9d1d79c 100644 --- a/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol +++ b/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol @@ -62,8 +62,9 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier { function verifyNovaProof( // inputs are grouped to prevent errors due stack too deep uint256[{{ 1 + z_len * 2 }}] calldata i_z0_zi, // [i, z0, zi] where |z0| == |zi| - uint256[4] calldata U_final_cmW_U_final_cmE, // [U_final_cmW[2], U_final_cmE[2]] - uint256[3] calldata U_final_u_U_final_x, // [U_final_u, U_final_x[2]] + uint256[4] calldata U_i_cmW_U_i_cmE, // [U_i_cmW[2], U_i_cmE[2]] + uint256[2] calldata u_i_cmW, // [u_i_cmW[2]] + uint256[3] calldata cmT_r, // [cmT[2], r] uint256[2] calldata pA, // groth16 uint256[2][2] calldata pB, // groth16 uint256[2] calldata pC, // groth16 @@ -84,21 +85,35 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier { } { - public_inputs[{{ z_len * 2 + 2 }}] = U_final_u_U_final_x[0]; - public_inputs[{{ z_len * 2 + 3 }}] = U_final_u_U_final_x[1]; - public_inputs[{{ z_len * 2 + 4 }}] = U_final_u_U_final_x[2]; + // U_i.cmW + r * u_i.cmW + uint256[2] memory mulScalarPoint = super.mulScalar([u_i_cmW[0], u_i_cmW[1]], cmT_r[2]); + uint256[2] memory cmW = super.add([U_i_cmW_U_i_cmE[0], U_i_cmW_U_i_cmE[1]], mulScalarPoint); + + { + uint256[{{num_limbs}}] memory cmW_x_limbs = LimbsDecomposition.decompose(cmW[0]); + uint256[{{num_limbs}}] memory cmW_y_limbs = LimbsDecomposition.decompose(cmW[1]); + + for (uint8 k = 0; k < {{num_limbs}}; k++) { + public_inputs[{{ z_len * 2 + 2 }} + k] = cmW_x_limbs[k]; + public_inputs[{{ z_len * 2 + 2 + num_limbs }} + k] = cmW_y_limbs[k]; + } + } + + require(this.check(cmW, kzg_proof[0], challenge_W_challenge_E_kzg_evals[0], challenge_W_challenge_E_kzg_evals[2]), "KZG: verifying proof for challenge W failed"); } { - uint256[2] memory cmE = [U_final_cmW_U_final_cmE[2], U_final_cmW_U_final_cmE[3]]; + // U_i.cmE + r * cmT + uint256[2] memory mulScalarPoint = super.mulScalar([cmT_r[0], cmT_r[1]], cmT_r[2]); + uint256[2] memory cmE = super.add([U_i_cmW_U_i_cmE[2], U_i_cmW_U_i_cmE[3]], mulScalarPoint); { uint256[{{num_limbs}}] memory cmE_x_limbs = LimbsDecomposition.decompose(cmE[0]); uint256[{{num_limbs}}] memory cmE_y_limbs = LimbsDecomposition.decompose(cmE[1]); for (uint8 k = 0; k < {{num_limbs}}; k++) { - public_inputs[{{ z_len * 2 + 5 }} + k] = cmE_x_limbs[k]; - public_inputs[{{ z_len * 2 + 5 + num_limbs }} + k] = cmE_y_limbs[k]; + public_inputs[{{ z_len * 2 + 2 + num_limbs * 2 }} + k] = cmE_x_limbs[k]; + public_inputs[{{ z_len * 2 + 2 + num_limbs * 3 }} + k] = cmE_y_limbs[k]; } } @@ -106,28 +121,26 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier { } { - uint256[2] memory cmW = [U_final_cmW_U_final_cmE[0], U_final_cmW_U_final_cmE[1]]; + // add challenges + public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 }}] = challenge_W_challenge_E_kzg_evals[0]; + public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 1 }}] = challenge_W_challenge_E_kzg_evals[1]; + public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 2 }}] = challenge_W_challenge_E_kzg_evals[2]; + public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 3 }}] = challenge_W_challenge_E_kzg_evals[3]; + + uint256[{{num_limbs}}] memory cmT_x_limbs; + uint256[{{num_limbs}}] memory cmT_y_limbs; - { - uint256[{{num_limbs}}] memory cmW_x_limbs = LimbsDecomposition.decompose(cmW[0]); - uint256[{{num_limbs}}] memory cmW_y_limbs = LimbsDecomposition.decompose(cmW[1]); + cmT_x_limbs = LimbsDecomposition.decompose(cmT_r[0]); + cmT_y_limbs = LimbsDecomposition.decompose(cmT_r[1]); - for (uint8 k = 0; k < {{num_limbs}}; k++) { - public_inputs[{{ z_len * 2 + 5 + num_limbs * 2 }} + k] = cmW_x_limbs[k]; - public_inputs[{{ z_len * 2 + 5 + num_limbs * 3 }} + k] = cmW_y_limbs[k]; - } + for (uint8 k = 0; k < {{num_limbs}}; k++) { + public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 }} + 4 + k] = cmT_x_limbs[k]; + public_inputs[{{ z_len * 2 + 2 + num_limbs * 5 }} + 4 + k] = cmT_y_limbs[k]; } - require(this.check(cmW, kzg_proof[0], challenge_W_challenge_E_kzg_evals[0], challenge_W_challenge_E_kzg_evals[2]), "KZG: verifying proof for challenge W failed"); - } + // last element of the groth16 proof's public inputs is `r` + public_inputs[{{ public_inputs_len - 2 }}] = cmT_r[2]; - { - // add challenges - public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 }}] = challenge_W_challenge_E_kzg_evals[0]; - public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 + 1 }}] = challenge_W_challenge_E_kzg_evals[1]; - public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 + 2 }}] = challenge_W_challenge_E_kzg_evals[2]; - public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 + 3 }}] = challenge_W_challenge_E_kzg_evals[3]; - bool success_g16 = this.verifyProof(pA, pB, pC, public_inputs); require(success_g16 == true, "Groth16: verifying proof failed"); }