From edcef6c3528decc6994029bc2e284d101c5433cf Mon Sep 17 00:00:00 2001 From: arnaucube Date: Thu, 3 Oct 2024 11:38:57 +0200 Subject: [PATCH] implement Nova's Offchain Decider (prover & verifier) for non-ethereum cases (#164) The idea & motivation is that the [onchain decider](https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html) could still be used for non-onchain verification but the proving time is big (eg. a little bit less than 3 minutes on my laptop) since the circuit is big due the EVM constraints. Whereas with this new [offchain decider](https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html) we can generate the proofs much faster for the cases where it is not required to verify the proofs in the EVM. The code is mostly abstracted from any specifics of the current usage of Groth16 & KZG10, with the idea that eventually in the future we can have Spartan plugged in and use non-pairing-curves such as pallas&vesta. For the current version it relies on KZG10 commitments. The logic implemented in the code of this commit can be found at the updated docs section 'offchain decider': https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html --- folding-schemes/Cargo.toml | 4 + .../src/folding/circuits/cyclefold.rs | 6 +- folding-schemes/src/folding/nova/decider.rs | 492 ++++++++++++++++++ .../src/folding/nova/decider_circuits.rs | 74 +-- .../src/folding/nova/decider_eth.rs | 3 +- folding-schemes/src/folding/nova/mod.rs | 1 + 6 files changed, 541 insertions(+), 39 deletions(-) create mode 100644 folding-schemes/src/folding/nova/decider.rs diff --git a/folding-schemes/Cargo.toml b/folding-schemes/Cargo.toml index 2fbbc3b..d7c4548 100644 --- a/folding-schemes/Cargo.toml +++ b/folding-schemes/Cargo.toml @@ -41,6 +41,10 @@ ark-pallas = {version="0.4.0", features=["r1cs"]} ark-vesta = {version="0.4.0", features=["r1cs"]} ark-bn254 = {version="0.4.0", features=["r1cs"]} ark-grumpkin = {version="0.4.0", features=["r1cs"]} +# Note: do not use the MNTx_298 curves in practice due security reasons, here +# we only use them in the tests. +ark-mnt4-298 = {version="0.4.0", features=["r1cs"]} +ark-mnt6-298 = {version="0.4.0", features=["r1cs"]} rand = "0.8.5" tracing = { version = "0.1", default-features = false, features = [ "attributes" ] } tracing-subscriber = { version = "0.2" } diff --git a/folding-schemes/src/folding/circuits/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs index dc22061..3cb3402 100644 --- a/folding-schemes/src/folding/circuits/cyclefold.rs +++ b/folding-schemes/src/folding/circuits/cyclefold.rs @@ -61,8 +61,10 @@ where f().and_then(|val| { let cs = cs.into(); - let u = NonNativeUintVar::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; - let x = Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; + let u = + NonNativeUintVar::>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; + let x: Vec>> = + Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?; diff --git a/folding-schemes/src/folding/nova/decider.rs b/folding-schemes/src/folding/nova/decider.rs new file mode 100644 index 0000000..11e864b --- /dev/null +++ b/folding-schemes/src/folding/nova/decider.rs @@ -0,0 +1,492 @@ +/// This file implements the offchain decider. For ethereum use cases, use the +/// DeciderEth from decider_eth.rs file. +/// More details can be found at the documentation page: +/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{AffineRepr, CurveGroup, Group}; +use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_snark::SNARK; +use ark_std::rand::{CryptoRng, RngCore}; +use ark_std::{One, Zero}; +use core::marker::PhantomData; + +use super::decider_circuits::{DeciderCircuit1, DeciderCircuit2}; +use super::{nifs::NIFS, CommittedInstance, Nova}; +use crate::commitment::CommitmentScheme; +use crate::folding::circuits::{ + cyclefold::CycleFoldCommittedInstance, + nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, + CF2, +}; +use crate::frontend::FCircuit; +use crate::Error; +use crate::{Decider as DeciderTrait, FoldingScheme}; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Proof +where + C1: CurveGroup, + C2: CurveGroup, + CS1: CommitmentScheme, + CS2: CommitmentScheme, + S1: SNARK, + S2: SNARK, +{ + c1_snark_proof: S1::Proof, + c2_snark_proof: S2::Proof, + cs1_proofs: [CS1::Proof; 2], + cs2_proofs: [CS2::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, + // cyclefold committed instance + cf_U_i: CycleFoldCommittedInstance, + // the CS challenges are provided by the prover, but in-circuit they are checked to match the + // in-circuit computed computed ones. + cs1_challenges: [C1::ScalarField; 2], + cs2_challenges: [C2::ScalarField; 2], +} + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct ProverParam +where + CS1_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize, + S1_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize, + CS2_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize, + S2_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize, +{ + pub c1_snark_pp: S1_ProvingKey, + pub c1_cs_pp: CS1_ProvingKey, + pub c2_snark_pp: S2_ProvingKey, + pub c2_cs_pp: CS2_ProvingKey, +} + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct VerifierParam +where + C1: CurveGroup, + CS1_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, + S1_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, + CS2_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, + S2_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize, +{ + pub pp_hash: C1::ScalarField, + pub c1_snark_vp: S1_VerifyingKey, + pub c1_cs_vp: CS1_VerifyingKey, + pub c2_snark_vp: S2_VerifyingKey, + pub c2_cs_vp: CS2_VerifyingKey, +} + +/// Onchain Decider, for ethereum use cases +#[derive(Clone, Debug)] +pub struct Decider { + _c1: PhantomData, + _gc1: PhantomData, + _c2: PhantomData, + _gc2: PhantomData, + _fc: PhantomData, + _cs1: PhantomData, + _cs2: PhantomData, + _s1: PhantomData, + _s2: PhantomData, + _fs: PhantomData, +} + +impl DeciderTrait + for Decider +where + C1: CurveGroup, + C2: CurveGroup, + GC1: CurveVar> + ToConstraintFieldGadget>, + GC2: CurveVar> + ToConstraintFieldGadget>, + FC: FCircuit, + CS1: CommitmentScheme< + C1, + ProverChallenge = C1::ScalarField, + Challenge = C1::ScalarField, + Proof = crate::commitment::kzg::Proof, + >, + CS2: CommitmentScheme< + C2, + ProverChallenge = C2::ScalarField, + Challenge = C2::ScalarField, + Proof = crate::commitment::kzg::Proof, + >, + S1: SNARK, + S2: SNARK, + FS: FoldingScheme, + ::BaseField: PrimeField, + ::BaseField: PrimeField, + ::ScalarField: Absorb, + ::ScalarField: Absorb, + C1: CurveGroup, + for<'b> &'b GC1: GroupOpsBounds<'b, C1, GC1>, + for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>, + // constrain FS into Nova, since this is a Decider specifically for Nova + Nova: From, + crate::folding::nova::ProverParams: + From<>::ProverParam>, + crate::folding::nova::VerifierParams: + From<>::VerifierParam>, +{ + type PreprocessorParam = (FS::ProverParam, FS::VerifierParam); + type ProverParam = + ProverParam; + type Proof = Proof; + type VerifierParam = VerifierParam< + C1, + CS1::VerifierParams, + S1::VerifyingKey, + CS2::VerifierParams, + S2::VerifyingKey, + >; + type PublicInput = Vec; + type CommittedInstance = CommittedInstance; + + fn preprocess( + mut rng: impl RngCore + CryptoRng, + prep_param: Self::PreprocessorParam, + fs: FS, + ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { + let circuit1 = DeciderCircuit1::::from_nova::( + fs.clone().into(), + )?; + let circuit2 = + DeciderCircuit2::::from_nova::(fs.into())?; + + // get the Groth16 specific setup for the circuits + let (c1_g16_pk, c1_g16_vk) = S1::circuit_specific_setup(circuit1, &mut rng).unwrap(); + let (c2_g16_pk, c2_g16_vk) = S2::circuit_specific_setup(circuit2, &mut rng).unwrap(); + + // get the FoldingScheme prover & verifier params from Nova + #[allow(clippy::type_complexity)] + let nova_pp: as FoldingScheme< + C1, + C2, + FC, + >>::ProverParam = prep_param.0.clone().into(); + #[allow(clippy::type_complexity)] + let nova_vp: as FoldingScheme< + C1, + C2, + FC, + >>::VerifierParam = prep_param.1.clone().into(); + + let pp_hash = nova_vp.pp_hash()?; + let pp = Self::ProverParam { + c1_snark_pp: c1_g16_pk, + c1_cs_pp: nova_pp.cs_pp, + c2_snark_pp: c2_g16_pk, + c2_cs_pp: nova_pp.cf_cs_pp, + }; + let vp = Self::VerifierParam { + pp_hash, + c1_snark_vp: c1_g16_vk, + c1_cs_vp: nova_vp.cs_vp, + c2_snark_vp: c2_g16_vk, + c2_cs_vp: nova_vp.cf_cs_vp, + }; + Ok((pp, vp)) + } + + fn prove( + mut rng: impl RngCore + CryptoRng, + pp: Self::ProverParam, + fs: FS, + ) -> Result { + let circuit1 = DeciderCircuit1::::from_nova::( + fs.clone().into(), + )?; + let circuit2 = + DeciderCircuit2::::from_nova::(fs.into())?; + + let c1_snark_proof = S1::prove(&pp.c1_snark_pp, circuit1.clone(), &mut rng) + .map_err(|e| Error::Other(e.to_string()))?; + let c2_snark_proof = S2::prove(&pp.c2_snark_pp, circuit2.clone(), &mut rng) + .map_err(|e| Error::Other(e.to_string()))?; + + let cmT = circuit1.cmT.unwrap(); + let r_Fr = circuit1.r.unwrap(); + let W_i1 = circuit1.W_i1.unwrap(); + let cf_W_i = circuit2.cf_W_i.unwrap(); + + // get the challenges that have been already computed when preparing the circuits inputs in + // the above `from_nova` calls + let challenge_W = circuit1 + .cs_c_W + .ok_or(Error::MissingValue("cs_c_W".to_string()))?; + let challenge_E = circuit1 + .cs_c_E + .ok_or(Error::MissingValue("cs_c_E".to_string()))?; + let c2_challenge_W = circuit2 + .cs_c_W + .ok_or(Error::MissingValue("c2's cs_c_W".to_string()))?; + let c2_challenge_E = circuit2 + .cs_c_E + .ok_or(Error::MissingValue("c2's cs_c_E".to_string()))?; + + // generate CommitmentScheme proofs for the main instance + let U_cmW_proof = CS1::prove_with_challenge( + &pp.c1_cs_pp, + challenge_W, + &W_i1.W, + &C1::ScalarField::zero(), + None, + )?; + let U_cmE_proof = CS1::prove_with_challenge( + &pp.c1_cs_pp, + challenge_E, + &W_i1.E, + &C1::ScalarField::zero(), + None, + )?; + // CS proofs for the CycleFold instance + let cf_cmW_proof = CS2::prove_with_challenge( + &pp.c2_cs_pp, + c2_challenge_W, + &cf_W_i.W, + &C2::ScalarField::zero(), + None, + )?; + let cf_cmE_proof = CS2::prove_with_challenge( + &pp.c2_cs_pp, + c2_challenge_E, + &cf_W_i.E, + &C2::ScalarField::zero(), + None, + )?; + + Ok(Self::Proof { + c1_snark_proof, + c2_snark_proof, + cs1_proofs: [U_cmW_proof, U_cmE_proof], + cs2_proofs: [cf_cmW_proof, cf_cmE_proof], + cmT, + r: r_Fr, + cf_U_i: circuit1.cf_U_i.unwrap(), + cs1_challenges: [challenge_W, challenge_E], + cs2_challenges: [c2_challenge_W, c2_challenge_E], + }) + } + + fn verify( + vp: Self::VerifierParam, + i: C1::ScalarField, + z_0: Vec, + z_i: Vec, + running_instance: &Self::CommittedInstance, + incoming_instance: &Self::CommittedInstance, + proof: &Self::Proof, + ) -> Result { + if i <= C1::ScalarField::one() { + return Err(Error::NotEnoughSteps); + } + + // 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) = NonNativeAffineVar::inputize(U.cmE)?; + let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?; + let (cmT_x, cmT_y) = NonNativeAffineVar::inputize(proof.cmT)?; + + let zero = (&C2::BaseField::zero(), &C2::BaseField::zero()); + let cmE_affine = proof.cf_U_i.cmE.into_affine(); + let cmW_affine = proof.cf_U_i.cmW.into_affine(); + let (cf_cmE_x, cf_cmE_y) = cmE_affine.xy().unwrap_or(zero); + let cf_cmE_z = C1::ScalarField::one(); + let (cf_cmW_x, cf_cmW_y) = cmW_affine.xy().unwrap_or(zero); + let cf_cmW_z = C1::ScalarField::one(); + + // snark proof 1 + let c1_public_input: Vec = [ + vec![vp.pp_hash, i], + z_0, + z_i, + // U_{i+1} values: + vec![U.u], + U.x.clone(), + cmE_x, + cmE_y, + cmW_x, + cmW_y, + // CS1 values: + proof.cs1_challenges.to_vec(), // c_W, c_E + vec![ + proof.cs1_proofs[0].eval, // eval_W + proof.cs1_proofs[1].eval, // eval_E + ], + // cf_U_i values + NonNativeUintVar::>::inputize(proof.cf_U_i.u), + proof + .cf_U_i + .x + .iter() + .flat_map(|&x_i| NonNativeUintVar::>::inputize(x_i)) + .collect::>(), + vec![ + *cf_cmE_x, *cf_cmE_y, cf_cmE_z, *cf_cmW_x, *cf_cmW_y, cf_cmW_z, + ], + // NIFS values: + cmT_x, + cmT_y, + vec![proof.r], + ] + .concat(); + + let c1_snark_v = S1::verify(&vp.c1_snark_vp, &c1_public_input, &proof.c1_snark_proof) + .map_err(|e| Error::Other(e.to_string()))?; + if !c1_snark_v { + return Err(Error::SNARKVerificationFail); + } + + let (cf2_cmE_x, cf2_cmE_y) = NonNativeAffineVar::inputize(proof.cf_U_i.cmE)?; + let (cf2_cmW_x, cf2_cmW_y) = NonNativeAffineVar::inputize(proof.cf_U_i.cmW)?; + + // snark proof 2 + // migrate pp_hash from C1::Fr to C1::Fq + let pp_hash_Fq = + C2::ScalarField::from_le_bytes_mod_order(&vp.pp_hash.into_bigint().to_bytes_le()); + let c2_public_input: Vec = [ + vec![pp_hash_Fq], + vec![proof.cf_U_i.u], + proof.cf_U_i.x.clone(), + cf2_cmE_x, + cf2_cmE_y, + cf2_cmW_x, + cf2_cmW_y, + proof.cs2_challenges.to_vec(), + vec![ + proof.cs2_proofs[0].eval, // eval_W + proof.cs2_proofs[1].eval, // eval_E + ], + ] + .concat(); + + let c2_snark_v = S2::verify(&vp.c2_snark_vp, &c2_public_input, &proof.c2_snark_proof) + .map_err(|e| Error::Other(e.to_string()))?; + if !c2_snark_v { + return Err(Error::SNARKVerificationFail); + } + + // check C1 commitments (main instance commitments) + CS1::verify_with_challenge( + &vp.c1_cs_vp, + proof.cs1_challenges[0], + &U.cmW, + &proof.cs1_proofs[0], + )?; + CS1::verify_with_challenge( + &vp.c1_cs_vp, + proof.cs1_challenges[1], + &U.cmE, + &proof.cs1_proofs[1], + )?; + + // check C2 commitments (CycleFold instance commitments) + CS2::verify_with_challenge( + &vp.c2_cs_vp, + proof.cs2_challenges[0], + &proof.cf_U_i.cmW, + &proof.cs2_proofs[0], + )?; + CS2::verify_with_challenge( + &vp.c2_cs_vp, + proof.cs2_challenges[1], + &proof.cf_U_i.cmE, + &proof.cs2_proofs[1], + )?; + + Ok(true) + } +} + +#[cfg(test)] +pub mod tests { + use ark_groth16::Groth16; + + // Note: do not use the MNTx_298 curves in practice, these are just for tests. Use the MNTx_753 + // curves instead. + use ark_mnt4_298::{ + constraints::G1Var as GVar, Fr, G1Projective as Projective, MNT4_298 as MNT4, + }; + use ark_mnt6_298::{ + constraints::G1Var as GVar2, G1Projective as Projective2, MNT6_298 as MNT6, + }; + use std::time::Instant; + + use super::*; + use crate::commitment::kzg::KZG; + use crate::folding::nova::PreprocessorParam; + use crate::frontend::utils::CubicFCircuit; + use crate::transcript::poseidon::poseidon_canonical_config; + + #[test] + fn test_decider() { + // use Nova as FoldingScheme + type N = Nova< + Projective, + GVar, + Projective2, + GVar2, + CubicFCircuit, + KZG<'static, MNT4>, + KZG<'static, MNT6>, + false, + >; + type D = Decider< + Projective, + GVar, + Projective2, + GVar2, + CubicFCircuit, + KZG<'static, MNT4>, + KZG<'static, MNT6>, + Groth16, + Groth16, + N, // here we define the FoldingScheme to use + >; + + let mut rng = ark_std::test_rng(); + let poseidon_config = poseidon_canonical_config::(); + + let F_circuit = CubicFCircuit::::new(()).unwrap(); + let z_0 = vec![Fr::from(3_u32)]; + + let start = Instant::now(); + let prep_param = PreprocessorParam::new(poseidon_config, F_circuit); + let nova_params = N::preprocess(&mut rng, &prep_param).unwrap(); + println!("Nova preprocess, {:?}", start.elapsed()); + + let start = Instant::now(); + let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap(); + println!("Nova initialized, {:?}", start.elapsed()); + let start = Instant::now(); + nova.prove_step(&mut rng, vec![], None).unwrap(); + println!("prove_step, {:?}", start.elapsed()); + nova.prove_step(&mut rng, vec![], None).unwrap(); // do a 2nd step + + let mut rng = rand::rngs::OsRng; + + // prepare the Decider prover & verifier params + let start = Instant::now(); + let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap(); + println!("Decider preprocess, {:?}", start.elapsed()); + + // decider proof generation + let start = Instant::now(); + let proof = D::prove(rng, decider_pp, nova.clone()).unwrap(); + println!("Decider prove, {:?}", start.elapsed()); + + // decider proof verification + let start = Instant::now(); + let verified = D::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_circuits.rs b/folding-schemes/src/folding/nova/decider_circuits.rs index 959a1e3..457d057 100644 --- a/folding-schemes/src/folding/nova/decider_circuits.rs +++ b/folding-schemes/src/folding/nova/decider_circuits.rs @@ -84,9 +84,10 @@ where /// CycleFold running instance pub cf_U_i: Option>, - /// KZG challenges - pub kzg_c_W: Option, - pub kzg_c_E: Option, + /// Commitment Scheme challenges + pub cs_c_W: Option, + pub cs_c_E: Option, + /// Evaluations of the committed polynomials at the challenge pub eval_W: Option, pub eval_E: Option, } @@ -134,11 +135,11 @@ where 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) = + // compute the commitment scheme challenges used as inputs in the circuit + let (cs_challenge_W, cs_challenge_E) = KZGChallengesGadget::::get_challenges_native(&mut transcript, U_i1.clone()); - // get KZG evals + // get evals of the committed polys at the challenges let mut W = W_i1.W.clone(); W.extend( std::iter::repeat(C1::ScalarField::zero()) @@ -150,9 +151,9 @@ where .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 eval_W = p_W.evaluate(&cs_challenge_W); let p_E = poly_from_vec(E.to_vec())?; - let eval_E = p_E.evaluate(&kzg_challenge_E); + let eval_E = p_E.evaluate(&cs_challenge_E); Ok(Self { _c1: PhantomData, @@ -176,8 +177,8 @@ where cmT: Some(cmT), r: Some(r_Fr), cf_U_i: Some(nova.cf_U_i), - kzg_c_W: Some(kzg_challenge_W), - kzg_c_E: Some(kzg_challenge_E), + cs_c_W: Some(cs_challenge_W), + cs_c_E: Some(cs_challenge_E), eval_W: Some(eval_W), eval_E: Some(eval_E), }) @@ -232,11 +233,11 @@ where })?; // 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 cs_c_W = FpVar::>::new_input(cs.clone(), || { + Ok(self.cs_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 cs_c_E = FpVar::>::new_input(cs.clone(), || { + Ok(self.cs_c_E.unwrap_or_else(CF1::::zero)) })?; let _eval_W = FpVar::>::new_input(cs.clone(), || { Ok(self.eval_W.unwrap_or_else(CF1::::zero)) @@ -276,7 +277,7 @@ where [vec![U_i1.u.clone()], U_i1.x.to_vec(), W_i1.W.to_vec()].concat(); RelaxedR1CSGadget::check_native(r1cs, W_i1.E.clone(), U_i1.u.clone(), z_U1)?; - // 1.1.a, 5.1 compute NIFS.V and KZG challenges. + // 1.1.a, 5.1 compute NIFS.V and Commitment Scheme challenges. // We need to ensure the order of challenge generation is the same as // the native counterpart, so we first compute the challenges here and // do the actual checks later. @@ -292,8 +293,8 @@ where // 5.1. let (incircuit_c_W, incircuit_c_E) = KZGChallengesGadget::::get_challenges_gadget(&mut transcript, U_i1.clone())?; - incircuit_c_W.enforce_equal(&kzg_c_W)?; - incircuit_c_E.enforce_equal(&kzg_c_E)?; + incircuit_c_W.enforce_equal(&cs_c_W)?; + incircuit_c_E.enforce_equal(&cs_c_E)?; // Check 5.2 is temporary disabled due // https://github.com/privacy-scaling-explorations/sonobe/issues/80 @@ -342,9 +343,10 @@ where /// be computed natively pub cf_U_i: Option>, pub cf_W_i: Option>, - /// KZG challenges - pub kzg_c_W: Option, - pub kzg_c_E: Option, + /// Commitment Scheme challenges + pub cs_c_W: Option, + pub cs_c_E: Option, + /// Evaluations of the committed polynomials at the challenge pub eval_W: Option, pub eval_E: Option, } @@ -366,8 +368,8 @@ where CS1: CommitmentScheme, CS2: CommitmentScheme, { - // compute the KZG challenges of the CycleFold instance commitments, used as inputs in the - // circuit + // compute the Commitment Scheme challenges of the CycleFold instance commitments, used as + // inputs in the circuit let poseidon_config = crate::transcript::poseidon::poseidon_canonical_config::(); let mut transcript = PoseidonSponge::::new(&poseidon_config); @@ -375,10 +377,10 @@ where C2::ScalarField::from_le_bytes_mod_order(&nova.pp_hash.into_bigint().to_bytes_le()); transcript.absorb(&pp_hash_Fq); - let (kzg_challenge_W, kzg_challenge_E) = + let (cs_challenge_W, cs_challenge_E) = KZGChallengesGadget::::get_challenges_native(&mut transcript, nova.cf_U_i.clone()); - // get KZG evals + // get evals of the committed polynomials at the challenge let mut W = nova.cf_W_i.W.clone(); W.extend( std::iter::repeat(C2::ScalarField::zero()) @@ -390,9 +392,9 @@ where .take(nova.cf_W_i.E.len().next_power_of_two() - nova.cf_W_i.E.len()), ); let p_W = poly_from_vec(W.to_vec())?; - let eval_W = p_W.evaluate(&kzg_challenge_W); + let eval_W = p_W.evaluate(&cs_challenge_W); let p_E = poly_from_vec(E.to_vec())?; - let eval_E = p_E.evaluate(&kzg_challenge_E); + let eval_E = p_E.evaluate(&cs_challenge_E); Ok(Self { _c1: PhantomData, @@ -407,9 +409,9 @@ where cf_U_i: Some(nova.cf_U_i), cf_W_i: Some(nova.cf_W_i), - // CycleFold instance commitments kzg challenges - kzg_c_W: Some(kzg_challenge_W), - kzg_c_E: Some(kzg_challenge_E), + // CycleFold instance commitments challenges + cs_c_W: Some(cs_challenge_W), + cs_c_E: Some(cs_challenge_E), eval_W: Some(eval_W), eval_E: Some(eval_E), }) @@ -457,11 +459,11 @@ where transcript.absorb(&pp_hash)?; // allocate the inputs for the check 7.1 - let kzg_c_W = FpVar::>::new_input(cs.clone(), || { - Ok(self.kzg_c_W.unwrap_or_else(CF1::::zero)) + let cs_c_W = FpVar::>::new_input(cs.clone(), || { + Ok(self.cs_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 cs_c_E = FpVar::>::new_input(cs.clone(), || { + Ok(self.cs_c_E.unwrap_or_else(CF1::::zero)) })?; // allocate the inputs for the check 7.2 let _eval_W = FpVar::>::new_input(cs.clone(), || { @@ -471,11 +473,11 @@ where Ok(self.eval_E.unwrap_or_else(CF1::::zero)) })?; - // 7.1. check the KZG challenges correct computation + // 7.1. check the commitment scheme challenges correct computation let (incircuit_c_W, incircuit_c_E) = KZGChallengesGadget::::get_challenges_gadget(&mut transcript, cf_U_i.clone())?; - incircuit_c_W.enforce_equal(&kzg_c_W)?; - incircuit_c_E.enforce_equal(&kzg_c_E)?; + incircuit_c_W.enforce_equal(&cs_c_W)?; + incircuit_c_E.enforce_equal(&cs_c_E)?; // Check 7.2 is temporary disabled due // https://github.com/privacy-scaling-explorations/sonobe/issues/80 diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index abf718e..9ab880b 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -1,4 +1,5 @@ -/// This file implements the Nova's onchain (Ethereum's EVM) decider. +/// This file implements the Nova's onchain (Ethereum's EVM) decider. For non-ethereum use cases, +/// the Decider from decider.rs file will be more efficient. /// More details can be found at the documentation page: /// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html use ark_bn254::Bn254; diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index d6436c5..7a6691d 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -45,6 +45,7 @@ pub mod traits; pub mod zk; // offchain decider +pub mod decider; pub mod decider_circuits; // onchain decider pub mod decider_eth;