Browse Source

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
main
arnaucube 1 month ago
committed by GitHub
parent
commit
edcef6c352
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
6 changed files with 541 additions and 39 deletions
  1. +4
    -0
      folding-schemes/Cargo.toml
  2. +4
    -2
      folding-schemes/src/folding/circuits/cyclefold.rs
  3. +492
    -0
      folding-schemes/src/folding/nova/decider.rs
  4. +38
    -36
      folding-schemes/src/folding/nova/decider_circuits.rs
  5. +2
    -1
      folding-schemes/src/folding/nova/decider_eth.rs
  6. +1
    -0
      folding-schemes/src/folding/nova/mod.rs

+ 4
- 0
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" }

+ 4
- 2
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::<CF2<C>>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
let x: Vec<NonNativeUintVar<CF2<C>>> =
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)?;

+ 492
- 0
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<C1, C2, CS1, CS2, S1, S2>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
S1: SNARK<C1::ScalarField>,
S2: SNARK<C2::ScalarField>,
{
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<C2>,
// 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<CS1_ProvingKey, S1_ProvingKey, CS2_ProvingKey, S2_ProvingKey>
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<C1, CS1_VerifyingKey, S1_VerifyingKey, CS2_VerifyingKey, S2_VerifyingKey>
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, GC1, C2, GC2, FC, CS1, CS2, S1, S2, FS> {
_c1: PhantomData<C1>,
_gc1: PhantomData<GC1>,
_c2: PhantomData<C2>,
_gc2: PhantomData<GC2>,
_fc: PhantomData<FC>,
_cs1: PhantomData<CS1>,
_cs2: PhantomData<CS2>,
_s1: PhantomData<S1>,
_s2: PhantomData<S2>,
_fs: PhantomData<FS>,
}
impl<C1, GC1, C2, GC2, FC, CS1, CS2, S1, S2, FS> DeciderTrait<C1, C2, FC, FS>
for Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S1, S2, FS>
where
C1: CurveGroup,
C2: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<
C1,
ProverChallenge = C1::ScalarField,
Challenge = C1::ScalarField,
Proof = crate::commitment::kzg::Proof<C1>,
>,
CS2: CommitmentScheme<
C2,
ProverChallenge = C2::ScalarField,
Challenge = C2::ScalarField,
Proof = crate::commitment::kzg::Proof<C2>,
>,
S1: SNARK<C1::ScalarField>,
S2: SNARK<C2::ScalarField>,
FS: FoldingScheme<C1, C2, FC>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
<C1 as Group>::ScalarField: Absorb,
<C2 as Group>::ScalarField: Absorb,
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
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<C1, GC1, C2, GC2, FC, CS1, CS2, false>: From<FS>,
crate::folding::nova::ProverParams<C1, C2, CS1, CS2, false>:
From<<FS as FoldingScheme<C1, C2, FC>>::ProverParam>,
crate::folding::nova::VerifierParams<C1, C2, CS1, CS2, false>:
From<<FS as FoldingScheme<C1, C2, FC>>::VerifierParam>,
{
type PreprocessorParam = (FS::ProverParam, FS::VerifierParam);
type ProverParam =
ProverParam<CS1::ProverParams, S1::ProvingKey, CS2::ProverParams, S2::ProvingKey>;
type Proof = Proof<C1, C2, CS1, CS2, S1, S2>;
type VerifierParam = VerifierParam<
C1,
CS1::VerifierParams,
S1::VerifyingKey,
CS2::VerifierParams,
S2::VerifyingKey,
>;
type PublicInput = Vec<C1::ScalarField>;
type CommittedInstance = CommittedInstance<C1>;
fn preprocess(
mut rng: impl RngCore + CryptoRng,
prep_param: Self::PreprocessorParam,
fs: FS,
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
let circuit1 = DeciderCircuit1::<C1, C2, GC2>::from_nova::<GC1, CS1, CS2, false, FC>(
fs.clone().into(),
)?;
let circuit2 =
DeciderCircuit2::<C1, GC1, C2>::from_nova::<GC2, CS1, CS2, false, FC>(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: <Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false> as FoldingScheme<
C1,
C2,
FC,
>>::ProverParam = prep_param.0.clone().into();
#[allow(clippy::type_complexity)]
let nova_vp: <Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false> 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<Self::Proof, Error> {
let circuit1 = DeciderCircuit1::<C1, C2, GC2>::from_nova::<GC1, CS1, CS2, false, FC>(
fs.clone().into(),
)?;
let circuit2 =
DeciderCircuit2::<C1, GC1, C2>::from_nova::<GC2, CS1, CS2, false, FC>(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<C1::ScalarField>,
z_i: Vec<C1::ScalarField>,
running_instance: &Self::CommittedInstance,
incoming_instance: &Self::CommittedInstance,
proof: &Self::Proof,
) -> Result<bool, Error> {
if i <= C1::ScalarField::one() {
return Err(Error::NotEnoughSteps);
}
// compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
let U = NIFS::<C1, CS1>::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<C1::ScalarField> = [
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::<CF2<C2>>::inputize(proof.cf_U_i.u),
proof
.cf_U_i
.x
.iter()
.flat_map(|&x_i| NonNativeUintVar::<CF2<C2>>::inputize(x_i))
.collect::<Vec<C1::ScalarField>>(),
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<C2::ScalarField> = [
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<Fr>,
KZG<'static, MNT4>,
KZG<'static, MNT6>,
false,
>;
type D = Decider<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<'static, MNT4>,
KZG<'static, MNT6>,
Groth16<MNT4>,
Groth16<MNT6>,
N, // here we define the FoldingScheme to use
>;
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_canonical_config::<Fr>();
let F_circuit = CubicFCircuit::<Fr>::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());
}
}

+ 38
- 36
folding-schemes/src/folding/nova/decider_circuits.rs

@ -84,9 +84,10 @@ where
/// CycleFold running instance
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>,
/// KZG challenges
pub kzg_c_W: Option<C1::ScalarField>,
pub kzg_c_E: Option<C1::ScalarField>,
/// Commitment Scheme challenges
pub cs_c_W: Option<C1::ScalarField>,
pub cs_c_E: Option<C1::ScalarField>,
/// Evaluations of the committed polynomials at the challenge
pub eval_W: Option<C1::ScalarField>,
pub eval_E: Option<C1::ScalarField>,
}
@ -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::<C1>::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::<CF1<C1>>::new_input(cs.clone(), || {
Ok(self.kzg_c_W.unwrap_or_else(CF1::<C1>::zero))
let cs_c_W = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
Ok(self.cs_c_W.unwrap_or_else(CF1::<C1>::zero))
})?;
let kzg_c_E = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
Ok(self.kzg_c_E.unwrap_or_else(CF1::<C1>::zero))
let cs_c_E = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
Ok(self.cs_c_E.unwrap_or_else(CF1::<C1>::zero))
})?;
let _eval_W = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
Ok(self.eval_W.unwrap_or_else(CF1::<C1>::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::<C1>::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<CommittedInstance<C2>>,
pub cf_W_i: Option<CycleFoldWitness<C2>>,
/// KZG challenges
pub kzg_c_W: Option<C2::ScalarField>,
pub kzg_c_E: Option<C2::ScalarField>,
/// Commitment Scheme challenges
pub cs_c_W: Option<C2::ScalarField>,
pub cs_c_E: Option<C2::ScalarField>,
/// Evaluations of the committed polynomials at the challenge
pub eval_W: Option<C2::ScalarField>,
pub eval_E: Option<C2::ScalarField>,
}
@ -366,8 +368,8 @@ where
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
// 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::<C2::ScalarField>();
let mut transcript = PoseidonSponge::<C2::ScalarField>::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::<C2>::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::<CF1<C2>>::new_input(cs.clone(), || {
Ok(self.kzg_c_W.unwrap_or_else(CF1::<C2>::zero))
let cs_c_W = FpVar::<CF1<C2>>::new_input(cs.clone(), || {
Ok(self.cs_c_W.unwrap_or_else(CF1::<C2>::zero))
})?;
let kzg_c_E = FpVar::<CF1<C2>>::new_input(cs.clone(), || {
Ok(self.kzg_c_E.unwrap_or_else(CF1::<C2>::zero))
let cs_c_E = FpVar::<CF1<C2>>::new_input(cs.clone(), || {
Ok(self.cs_c_E.unwrap_or_else(CF1::<C2>::zero))
})?;
// allocate the inputs for the check 7.2
let _eval_W = FpVar::<CF1<C2>>::new_input(cs.clone(), || {
@ -471,11 +473,11 @@ where
Ok(self.eval_E.unwrap_or_else(CF1::<C2>::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::<C2>::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

+ 2
- 1
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;

+ 1
- 0
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;

Loading…
Cancel
Save