Browse Source

implement HyperNova's DeciderEth (#156)

* implement HyperNova's DeciderEth

* add remark about Nova's zk layer implementation and the 3 identified use cases
main
arnaucube 2 months ago
committed by GitHub
parent
commit
0ad54576ec
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
12 changed files with 372 additions and 27 deletions
  1. +1
    -1
      examples/circom_full_flow.rs
  2. +1
    -1
      examples/full_flow.rs
  3. +1
    -1
      examples/noir_full_flow.rs
  4. +1
    -1
      examples/noname_full_flow.rs
  5. +312
    -0
      folding-schemes/src/folding/hypernova/decider_eth.rs
  6. +1
    -1
      folding-schemes/src/folding/hypernova/decider_eth_circuit.rs
  7. +13
    -12
      folding-schemes/src/folding/hypernova/mod.rs
  8. +2
    -2
      folding-schemes/src/folding/hypernova/nimfs.rs
  9. +6
    -5
      folding-schemes/src/folding/nova/decider_eth.rs
  10. +32
    -1
      folding-schemes/src/folding/nova/zk.rs
  11. +1
    -1
      folding-schemes/src/lib.rs
  12. +1
    -1
      solidity-verifiers/src/verifiers/nova_cyclefold.rs

+ 1
- 1
examples/circom_full_flow.rs

@ -89,7 +89,7 @@ fn main() {
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
// prepare the Decider prover & verifier params // prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
// run n steps of the folding iteration // run n steps of the folding iteration
for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {

+ 1
- 1
examples/full_flow.rs

@ -106,7 +106,7 @@ fn main() {
let mut nova = N::init(&nova_params, f_circuit, z_0).unwrap(); let mut nova = N::init(&nova_params, f_circuit, z_0).unwrap();
// prepare the Decider prover & verifier params // prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
// run n steps of the folding iteration // run n steps of the folding iteration
for i in 0..n_steps { for i in 0..n_steps {

+ 1
- 1
examples/noir_full_flow.rs

@ -79,7 +79,7 @@ fn main() {
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
// prepare the Decider prover & verifier params // prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
// run n steps of the folding iteration // run n steps of the folding iteration
for i in 0..5 { for i in 0..5 {

+ 1
- 1
examples/noname_full_flow.rs

@ -89,7 +89,7 @@ fn main() {
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
// prepare the Decider prover & verifier params // prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
// run n steps of the folding iteration // run n steps of the folding iteration
for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {

+ 312
- 0
folding-schemes/src/folding/hypernova/decider_eth.rs

@ -0,0 +1,312 @@
/// This file implements the HyperNova's onchain (Ethereum's EVM) decider.
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_ff::PrimeField;
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_snark::SNARK;
use ark_std::rand::{CryptoRng, RngCore};
use ark_std::{One, Zero};
use core::marker::PhantomData;
pub use super::decider_eth_circuit::DeciderEthCircuit;
use super::{lcccs::LCCCS, HyperNova};
use crate::commitment::{
kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme,
};
use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2};
use crate::frontend::FCircuit;
use crate::Error;
use crate::{Decider as DeciderTrait, FoldingScheme};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Proof<C1, CS1, S>
where
C1: CurveGroup,
CS1: CommitmentScheme<C1, ProverChallenge = C1::ScalarField, Challenge = C1::ScalarField>,
S: SNARK<C1::ScalarField>,
{
snark_proof: S::Proof,
kzg_proof: CS1::Proof,
// rho used at the last fold, U_{i+1}=NIMFS.V(rho, U_i, u_i), it is checked in-circuit
rho: C1::ScalarField,
U_i1: LCCCS<C1>, // U_{i+1}, which is checked in-circuit
// 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,
}
/// Onchain Decider, for ethereum use cases
#[derive(Clone, Debug)]
pub struct Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS, const MU: usize, const NU: usize> {
_c1: PhantomData<C1>,
_gc1: PhantomData<GC1>,
_c2: PhantomData<C2>,
_gc2: PhantomData<GC2>,
_fc: PhantomData<FC>,
_cs1: PhantomData<CS1>,
_cs2: PhantomData<CS2>,
_s: PhantomData<S>,
_fs: PhantomData<FS>,
}
impl<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS, const MU: usize, const NU: usize>
DeciderTrait<C1, C2, FC, FS> for Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS, MU, NU>
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 is a KZG commitment, where challenge is C1::Fr elem
CS1: CommitmentScheme<
C1,
ProverChallenge = C1::ScalarField,
Challenge = C1::ScalarField,
Proof = KZGProof<C1>,
>,
// enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
CS2: CommitmentScheme<C2, ProverParams = PedersenParams<C2>>,
S: SNARK<C1::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 HyperNova, since this is a Decider specifically for HyperNova
HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, false>: From<FS>,
crate::folding::hypernova::ProverParams<C1, C2, CS1, CS2, false>:
From<<FS as FoldingScheme<C1, C2, FC>>::ProverParam>,
crate::folding::hypernova::VerifierParams<C1, C2, CS1, CS2, false>:
From<<FS as FoldingScheme<C1, C2, FC>>::VerifierParam>,
{
type PreprocessorParam = (FS::ProverParam, FS::VerifierParam);
type ProverParam = (S::ProvingKey, CS1::ProverParams);
type Proof = Proof<C1, CS1, S>;
/// VerifierParam = (pp_hash, snark::vk, commitment_scheme::vk)
type VerifierParam = (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams);
type PublicInput = Vec<C1::ScalarField>;
type CommittedInstance = ();
fn preprocess(
mut rng: impl RngCore + CryptoRng,
prep_param: Self::PreprocessorParam,
fs: FS,
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
let circuit =
DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_hypernova::<FC, MU, NU>(
fs.into(),
)
.unwrap();
// get the Groth16 specific setup for the circuit
let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng).unwrap();
// get the FoldingScheme prover & verifier params from HyperNova
#[allow(clippy::type_complexity)]
let hypernova_pp: <HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, false> as FoldingScheme<
C1,
C2,
FC,
>>::ProverParam = prep_param.0.into();
#[allow(clippy::type_complexity)]
let hypernova_vp: <HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, false> as FoldingScheme<
C1,
C2,
FC,
>>::VerifierParam = prep_param.1.into();
let pp_hash = hypernova_vp.pp_hash()?;
let pp = (g16_pk, hypernova_pp.cs_pp);
let vp = (pp_hash, g16_vk, hypernova_vp.cs_vp);
Ok((pp, vp))
}
fn prove(
mut rng: impl RngCore + CryptoRng,
pp: Self::ProverParam,
folding_scheme: FS,
) -> Result<Self::Proof, Error> {
let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp;
let circuit = DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_hypernova::<FC, MU, NU>(
folding_scheme.into(),
)?;
let snark_proof = S::prove(&snark_pk, circuit.clone(), &mut rng)
.map_err(|e| Error::Other(e.to_string()))?;
// Notice that since the `circuit` has been constructed at the `from_hypernova` call, which
// in case of failure it would have returned an error there, the next two unwraps should
// never reach an error.
let rho_Fr = circuit.rho.ok_or(Error::Empty)?;
let W_i1 = circuit.W_i1.ok_or(Error::Empty)?;
// get the challenges that have been already computed when preparing the circuit inputs in
// the above `from_hypernova` call
let challenge_W = circuit
.kzg_challenge
.ok_or(Error::MissingValue("kzg_challenge".to_string()))?;
// generate KZG proofs
let U_cmW_proof = CS1::prove_with_challenge(
&cs_pk,
challenge_W,
&W_i1.w,
&C1::ScalarField::zero(),
None,
)?;
Ok(Self::Proof {
snark_proof,
kzg_proof: U_cmW_proof,
rho: rho_Fr,
U_i1: circuit.U_i1.ok_or(Error::Empty)?,
kzg_challenge: challenge_W,
})
}
fn verify(
vp: Self::VerifierParam,
i: C1::ScalarField,
z_0: Vec<C1::ScalarField>,
z_i: Vec<C1::ScalarField>,
// we don't use the instances at the verifier level, since we check them in-circuit
_running_instance: &Self::CommittedInstance,
_incoming_instance: &Self::CommittedInstance,
proof: &Self::Proof,
) -> Result<bool, Error> {
if i <= C1::ScalarField::one() {
return Err(Error::NotEnoughSteps);
}
let (pp_hash, snark_vk, cs_vk): (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams) =
vp;
// Note: the NIMFS proof is checked inside the DeciderEthCircuit, which ensures that the
// 'proof.U_i1' is correctly computed
let (cmC_x, cmC_y) = NonNativeAffineVar::inputize(proof.U_i1.C)?;
let public_input: Vec<C1::ScalarField> = [
vec![pp_hash, i],
z_0,
z_i,
// U_i+1:
cmC_x,
cmC_y,
vec![proof.U_i1.u],
proof.U_i1.x.clone(),
proof.U_i1.r_x.clone(),
proof.U_i1.v.clone(),
vec![proof.kzg_challenge, proof.kzg_proof.eval, proof.rho],
]
.concat();
let snark_v = S::verify(&snark_vk, &public_input, &proof.snark_proof)
.map_err(|e| Error::Other(e.to_string()))?;
if !snark_v {
return Err(Error::SNARKVerificationFail);
}
// we're at the Ethereum EVM case, so the CS1 is KZG commitments
CS1::verify_with_challenge(&cs_vk, proof.kzg_challenge, &proof.U_i1.C, &proof.kzg_proof)?;
Ok(true)
}
}
#[cfg(test)]
pub mod tests {
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
use ark_groth16::Groth16;
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
use std::time::Instant;
use super::*;
use crate::commitment::{kzg::KZG, pedersen::Pedersen};
use crate::folding::hypernova::PreprocessorParam;
use crate::frontend::tests::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
#[test]
fn test_decider() {
const MU: usize = 1;
const NU: usize = 1;
// use HyperNova as FoldingScheme
type HN = HyperNova<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
MU,
NU,
false,
>;
type D = Decider<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
Groth16<Bn254>, // here we define the Snark to use in the decider
HN, // here we define the FoldingScheme to use
MU,
NU,
>;
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 prep_param = PreprocessorParam::new(poseidon_config, F_circuit);
let hypernova_params = HN::preprocess(&mut rng, &prep_param).unwrap();
let start = Instant::now();
let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap();
println!("Nova initialized, {:?}", start.elapsed());
let start = Instant::now();
hypernova
.prove_step(&mut rng, vec![], Some((vec![], vec![])))
.unwrap();
println!("prove_step, {:?}", start.elapsed());
hypernova
.prove_step(&mut rng, vec![], Some((vec![], vec![])))
.unwrap(); // do a 2nd step
let mut rng = rand::rngs::OsRng;
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) =
D::preprocess(&mut rng, hypernova_params, hypernova.clone()).unwrap();
// decider proof generation
let start = Instant::now();
let proof = D::prove(rng, decider_pp, hypernova.clone()).unwrap();
println!("Decider prove, {:?}", start.elapsed());
// decider proof verification
let start = Instant::now();
let verified = D::verify(
decider_vp,
hypernova.i,
hypernova.z_0,
hypernova.z_i,
&(),
&(),
&proof,
)
.unwrap();
assert!(verified);
println!("Decider verify, {:?}", start.elapsed());
}
}

+ 1
- 1
folding-schemes/src/folding/hypernova/decider_eth_circuit.rs

@ -231,7 +231,7 @@ where
cf_E_len: hn.cf_W_i.E.len(), cf_E_len: hn.cf_W_i.E.len(),
ccs: hn.ccs, ccs: hn.ccs,
cf_r1cs: hn.cf_r1cs, cf_r1cs: hn.cf_r1cs,
cf_pedersen_params: hn.cf_cs_params,
cf_pedersen_params: hn.cf_cs_pp,
poseidon_config: hn.poseidon_config, poseidon_config: hn.poseidon_config,
pp_hash: Some(hn.pp_hash), pp_hash: Some(hn.pp_hash),
i: Some(hn.i), i: Some(hn.i),

+ 13
- 12
folding-schemes/src/folding/hypernova/mod.rs

@ -10,6 +10,7 @@ use ark_std::{fmt::Debug, marker::PhantomData, rand::RngCore, One, Zero};
pub mod cccs; pub mod cccs;
pub mod circuits; pub mod circuits;
pub mod decider_eth;
pub mod decider_eth_circuit; pub mod decider_eth_circuit;
pub mod lcccs; pub mod lcccs;
pub mod nimfs; pub mod nimfs;
@ -84,8 +85,8 @@ where
CS2: CommitmentScheme<C2, H>, CS2: CommitmentScheme<C2, H>,
{ {
pub poseidon_config: PoseidonConfig<C1::ScalarField>, pub poseidon_config: PoseidonConfig<C1::ScalarField>,
pub cs_params: CS1::ProverParams,
pub cf_cs_params: CS2::ProverParams,
pub cs_pp: CS1::ProverParams,
pub cf_cs_pp: CS2::ProverParams,
// if ccs is set, it will be used, if not, it will be computed at runtime // if ccs is set, it will be used, if not, it will be computed at runtime
pub ccs: Option<CCS<C1::ScalarField>>, pub ccs: Option<CCS<C1::ScalarField>>,
} }
@ -162,9 +163,9 @@ pub struct HyperNova<
pub cf_r1cs: R1CS<C2::ScalarField>, pub cf_r1cs: R1CS<C2::ScalarField>,
pub poseidon_config: PoseidonConfig<C1::ScalarField>, pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// CommitmentScheme::ProverParams over C1 /// CommitmentScheme::ProverParams over C1
pub cs_params: CS1::ProverParams,
pub cs_pp: CS1::ProverParams,
/// CycleFold CommitmentScheme::ProverParams, over C2 /// CycleFold CommitmentScheme::ProverParams, over C2
pub cf_cs_params: CS2::ProverParams,
pub cf_cs_pp: CS2::ProverParams,
/// F circuit, the circuit that is being folded /// F circuit, the circuit that is being folded
pub F: FC, pub F: FC,
/// public params hash /// public params hash
@ -221,7 +222,7 @@ where
// assign them directly to w_i, u_i. // assign them directly to w_i, u_i.
let (U_i, W_i) = self let (U_i, W_i) = self
.ccs .ccs
.to_lcccs::<_, _, CS1, H>(&mut rng, &self.cs_params, &r1cs_z)?;
.to_lcccs::<_, _, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
#[cfg(test)] #[cfg(test)]
U_i.check_relation(&self.ccs, &W_i)?; U_i.check_relation(&self.ccs, &W_i)?;
@ -243,7 +244,7 @@ where
// assign them directly to w_i, u_i. // assign them directly to w_i, u_i.
let (u_i, w_i) = self let (u_i, w_i) = self
.ccs .ccs
.to_cccs::<_, _, CS1, H>(&mut rng, &self.cs_params, &r1cs_z)?;
.to_cccs::<_, _, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
#[cfg(test)] #[cfg(test)]
u_i.check_relation(&self.ccs, &w_i)?; u_i.check_relation(&self.ccs, &w_i)?;
@ -426,8 +427,8 @@ where
let pp = ProverParams::<C1, C2, CS1, CS2, H> { let pp = ProverParams::<C1, C2, CS1, CS2, H> {
poseidon_config: prep_param.poseidon_config.clone(), poseidon_config: prep_param.poseidon_config.clone(),
cs_params: cs_pp.clone(),
cf_cs_params: cf_cs_pp.clone(),
cs_pp,
cf_cs_pp,
ccs: Some(ccs.clone()), ccs: Some(ccs.clone()),
}; };
let vp = VerifierParams::<C1, C2, CS1, CS2, H> { let vp = VerifierParams::<C1, C2, CS1, CS2, H> {
@ -496,8 +497,8 @@ where
ccs, ccs,
cf_r1cs, cf_r1cs,
poseidon_config: pp.poseidon_config.clone(), poseidon_config: pp.poseidon_config.clone(),
cs_params: pp.cs_params.clone(),
cf_cs_params: pp.cf_cs_params.clone(),
cs_pp: pp.cs_pp.clone(),
cf_cs_pp: pp.cf_cs_pp.clone(),
F, F,
pp_hash, pp_hash,
i: C1::ScalarField::zero(), i: C1::ScalarField::zero(),
@ -736,7 +737,7 @@ where
>( >(
&mut transcript_p, &mut transcript_p,
self.cf_r1cs.clone(), self.cf_r1cs.clone(),
self.cf_cs_params.clone(),
self.cf_cs_pp.clone(),
self.pp_hash, self.pp_hash,
self.cf_W_i.clone(), // CycleFold running instance witness self.cf_W_i.clone(), // CycleFold running instance witness
self.cf_U_i.clone(), // CycleFold running instance self.cf_U_i.clone(), // CycleFold running instance
@ -796,7 +797,7 @@ where
// assign them directly to w_i, u_i. // assign them directly to w_i, u_i.
let (u_i, w_i) = self let (u_i, w_i) = self
.ccs .ccs
.to_cccs::<_, C1, CS1, H>(&mut rng, &self.cs_params, &r1cs_z)?;
.to_cccs::<_, C1, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
self.u_i = u_i.clone(); self.u_i = u_i.clone();
self.w_i = w_i.clone(); self.w_i = w_i.clone();

+ 2
- 2
folding-schemes/src/folding/hypernova/nimfs.rs

@ -23,7 +23,7 @@ use std::fmt::Debug;
use std::marker::PhantomData; use std::marker::PhantomData;
/// NIMFSProof defines a multifolding proof /// NIMFSProof defines a multifolding proof
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NIMFSProof<C: CurveGroup> { pub struct NIMFSProof<C: CurveGroup> {
pub sc_proof: SumCheckProof<C::ScalarField>, pub sc_proof: SumCheckProof<C::ScalarField>,
pub sigmas_thetas: SigmasThetas<C::ScalarField>, pub sigmas_thetas: SigmasThetas<C::ScalarField>,
@ -51,7 +51,7 @@ impl NIMFSProof {
} }
} }
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SigmasThetas<F: PrimeField>(pub Vec<Vec<F>>, pub Vec<Vec<F>>); pub struct SigmasThetas<F: PrimeField>(pub Vec<Vec<F>>, pub Vec<Vec<F>>);
#[derive(Debug)] #[derive(Debug)]

+ 6
- 5
folding-schemes/src/folding/nova/decider_eth.rs

@ -1,4 +1,4 @@
/// This file implements the onchain (Ethereum's EVM) decider.
/// This file implements the Nova's onchain (Ethereum's EVM) decider.
use ark_bn254::Bn254; use ark_bn254::Bn254;
use ark_crypto_primitives::sponge::Absorb; use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{AffineRepr, CurveGroup, Group}; use ark_ec::{AffineRepr, CurveGroup, Group};
@ -11,7 +11,7 @@ use ark_std::rand::{CryptoRng, RngCore};
use ark_std::{One, Zero}; use ark_std::{One, Zero};
use core::marker::PhantomData; use core::marker::PhantomData;
pub use super::decider_eth_circuit::{DeciderEthCircuit, KZGChallengesGadget};
pub use super::decider_eth_circuit::DeciderEthCircuit;
use super::{nifs::NIFS, CommittedInstance, Nova}; use super::{nifs::NIFS, CommittedInstance, Nova};
use crate::commitment::{ use crate::commitment::{
kzg::{Proof as KZGProof, KZG}, kzg::{Proof as KZGProof, KZG},
@ -109,7 +109,7 @@ where
fn preprocess( fn preprocess(
mut rng: impl RngCore + CryptoRng, mut rng: impl RngCore + CryptoRng,
prep_param: &Self::PreprocessorParam,
prep_param: Self::PreprocessorParam,
fs: FS, fs: FS,
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
let circuit = let circuit =
@ -384,7 +384,7 @@ pub mod tests {
println!("Nova initialized, {:?}", start.elapsed()); println!("Nova initialized, {:?}", start.elapsed());
// prepare the Decider prover & verifier params // prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
let start = Instant::now(); let start = Instant::now();
nova.prove_step(&mut rng, vec![], None).unwrap(); nova.prove_step(&mut rng, vec![], None).unwrap();
@ -461,7 +461,8 @@ pub mod tests {
println!("Nova initialized, {:?}", start.elapsed()); println!("Nova initialized, {:?}", start.elapsed());
// prepare the Decider prover & verifier params // prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) =
D::preprocess(&mut rng, nova_params.clone(), nova.clone()).unwrap();
// serialize the Nova params. These params are the trusted setup of the commitment schemes used // serialize the Nova params. These params are the trusted setup of the commitment schemes used
// (ie. KZG & Pedersen in this case) // (ie. KZG & Pedersen in this case)

+ 32
- 1
folding-schemes/src/folding/nova/zk.rs

@ -1,4 +1,35 @@
// Implements nova's zero-knowledge layer, as described in https://eprint.iacr.org/2023/573.pdf
/// Implements Nova's zero-knowledge layer, as described in https://eprint.iacr.org/2023/573.pdf.
///
/// Remark: this zk layer implementation only covers a subset of the use cases:
///
/// We identify 3 interesting places to use the nova zk-layer: one before all the folding pipeline
/// (Use-case-1), one at the end of the folding pipeline right before the final Decider SNARK
/// proof (Use-case-2), and a third one for cases where compressed SNARK proofs are not needed, and
/// just IVC proofs (bigger than SNARK proofs) suffice (Use-case-3):
///
/// * Use-case-1: at the beginning of the folding pipeline, right when the user has their original
/// instance prior to be folded into the running instance, the user can fold it with the
/// random-satisfying-instance to then have a blinded instance that can be sent to a server that
/// will fold it with the running instance.
/// --> In this one, the user could externalize all the IVC folding and also the Decider
/// final proof generation to a server.
/// * Use-case-2: at the end of all the IVC folding steps (after n iterations of nova.prove_step),
/// to 'blind' the IVC proof so then it can be sent to a server that will generate the final
/// decider snark proof.
/// --> In this one, the user could externalize the Decider final proof generation to a
/// server.
/// * Use-case-3: the user does not care about the Decider (final compressed SNARK proof), and
/// wants to generate a zk-proof of the IVC state to an IVC verifier (without any SNARK proof
/// involved). Note that this proof will be much bigger and expensive to verify than a Decider
/// SNARK proof.
///
/// The current implementation covers the Use-case-3.
/// Use-case-1 can be achieved directly by a simpler version of the zk IVC scheme skipping steps
/// and implemented directly at the app level by folding the original instance with a randomized
/// instance (steps 2,3,4 from section D.4 of the [HyperNova](https://eprint.iacr.org/2023/573.pdf)
/// paper).
/// And the Use-case-2 would require a modified version of the Decider circuits.
///
use crate::folding::nova::traits::NovaR1CS; use crate::folding::nova::traits::NovaR1CS;
use ark_crypto_primitives::sponge::CryptographicSponge; use ark_crypto_primitives::sponge::CryptographicSponge;
use ark_ff::{BigInteger, PrimeField}; use ark_ff::{BigInteger, PrimeField};

+ 1
- 1
folding-schemes/src/lib.rs

@ -219,7 +219,7 @@ pub trait Decider<
fn preprocess( fn preprocess(
rng: impl RngCore + CryptoRng, rng: impl RngCore + CryptoRng,
prep_param: &Self::PreprocessorParam,
prep_param: Self::PreprocessorParam,
fs: FS, fs: FS,
) -> Result<(Self::ProverParam, Self::VerifierParam), Error>; ) -> Result<(Self::ProverParam, Self::VerifierParam), Error>;

+ 1
- 1
solidity-verifiers/src/verifiers/nova_cyclefold.rs

@ -346,7 +346,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let decider_params = let decider_params =
DECIDER::preprocess(&mut rng, &nova_params.clone(), nova.clone()).unwrap();
DECIDER::preprocess(&mut rng, nova_params.clone(), nova.clone()).unwrap();
(nova_params, decider_params) (nova_params, decider_params)
} }

Loading…
Cancel
Save