From 186766c3483869a28b5419785e0e2acc1482a538 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Fri, 2 Feb 2024 17:24:18 +0100 Subject: [PATCH] Fit Nova+CycleFold into `FoldingScheme` trait & Add `examples/` for folding SHA256 circuit (#64) * Update FoldingSchemes trait, fit Nova+CycleFold - update lib.rs's `FoldingScheme` trait interface - fit Nova+CycleFold into the `FoldingScheme` trait - refactor `src/nova/*` * Add `examples` dir, with Nova's `FoldingScheme` example * polishing * expose poseidon_test_config outside tests --- examples/fold_sha256.rs | 172 +++++ src/commitment/kzg.rs | 3 +- src/commitment/mod.rs | 8 +- src/commitment/pedersen.rs | 2 +- src/constants.rs | 6 +- src/folding/circuits/sum_check.rs | 2 +- src/folding/hypernova/nimfs.rs | 2 +- src/folding/nova/circuits.rs | 4 +- src/folding/nova/cyclefold.rs | 2 +- .../{decider.rs => decider_eth_circuit.rs} | 105 +-- src/folding/nova/ivc.rs | 470 -------------- src/folding/nova/mod.rs | 607 +++++++++++++++++- src/folding/nova/nifs.rs | 2 +- src/folding/protogalaxy/folding.rs | 2 +- src/frontend/mod.rs | 2 + src/lib.rs | 45 +- src/transcript/poseidon.rs | 54 +- src/utils/espresso/sum_check/mod.rs | 2 +- 18 files changed, 916 insertions(+), 574 deletions(-) create mode 100644 examples/fold_sha256.rs rename src/folding/nova/{decider.rs => decider_eth_circuit.rs} (90%) delete mode 100644 src/folding/nova/ivc.rs diff --git a/examples/fold_sha256.rs b/examples/fold_sha256.rs new file mode 100644 index 0000000..74ad626 --- /dev/null +++ b/examples/fold_sha256.rs @@ -0,0 +1,172 @@ +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(clippy::upper_case_acronyms)] + +use ark_crypto_primitives::crh::{ + sha256::{ + constraints::{Sha256Gadget, UnitVar}, + Sha256, + }, + CRHScheme, CRHSchemeGadget, +}; +use ark_ff::{BigInteger, PrimeField, ToConstraintField}; +use ark_r1cs_std::{fields::fp::FpVar, ToBytesGadget, ToConstraintFieldGadget}; +use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; +use core::marker::PhantomData; +use std::time::Instant; + +use ark_pallas::{constraints::GVar, Fr, Projective}; +use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; + +use folding_schemes::commitment::pedersen::Pedersen; +use folding_schemes::folding::nova::{get_r1cs, Nova, ProverParams, VerifierParams}; +use folding_schemes::frontend::FCircuit; +use folding_schemes::transcript::poseidon::poseidon_test_config; +use folding_schemes::{Error, FoldingScheme}; + +/// This is the circuit that we want to fold, it implements the FCircuit trait. +/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state which we get by +/// applying the step. +/// In this example we set z_i and z_{i+1} to be a single value, but the trait is made to support +/// arrays, so our state could be an array with different values. +#[derive(Clone, Copy, Debug)] +pub struct Sha256FCircuit { + _f: PhantomData, +} +impl FCircuit for Sha256FCircuit { + type Params = (); + + fn new(_params: Self::Params) -> Self { + Self { _f: PhantomData } + } + + /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new + /// z_{i+1} + fn step_native(self, z_i: Vec) -> Result, Error> { + let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap(); + let out: Vec = out_bytes.to_field_elements().unwrap(); + + Ok(vec![out[0]]) + } + + /// generates the constraints for the step of F for the given z_i + fn generate_step_constraints( + self, + _cs: ConstraintSystemRef, + z_i: Vec>, + ) -> Result>, SynthesisError> { + let unit_var = UnitVar::default(); + let out_bytes = Sha256Gadget::evaluate(&unit_var, &z_i[0].to_bytes()?)?; + let out = out_bytes.0.to_constraint_field()?; + Ok(vec![out[0].clone()]) + } +} + +/// cargo test --example simple +#[cfg(test)] +pub mod tests { + use super::*; + use ark_r1cs_std::alloc::AllocVar; + use ark_relations::r1cs::ConstraintSystem; + + // test to check that the Sha256FCircuit computes the same values inside and outside the circuit + #[test] + fn test_sha256_f_circuit() { + let cs = ConstraintSystem::::new_ref(); + + let circuit = Sha256FCircuit::::new(()); + let z_i = vec![Fr::from(1_u32)]; + + let z_i1 = circuit.step_native(z_i.clone()).unwrap(); + + let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i)).unwrap(); + let computed_z_i1Var = circuit + .generate_step_constraints(cs.clone(), z_iVar.clone()) + .unwrap(); + assert_eq!(computed_z_i1Var.value().unwrap(), z_i1); + } +} + +// This method computes the Prover & Verifier parameters for the example. For a real world use case +// those parameters should be generated carefuly (both the PoseidonConfig and the PedersenParams) +#[allow(clippy::type_complexity)] +fn nova_setup>( + F_circuit: FC, +) -> ( + ProverParams, Pedersen>, + VerifierParams, +) { + let mut rng = ark_std::test_rng(); + let poseidon_config = poseidon_test_config::(); + + // get the CM & CF_CM len + let (r1cs, cf_r1cs) = + get_r1cs::(&poseidon_config, F_circuit).unwrap(); + let cm_len = r1cs.A.n_rows; + let cf_cm_len = cf_r1cs.A.n_rows; + + let pedersen_params = Pedersen::::new_params(&mut rng, cm_len); + let cf_pedersen_params = Pedersen::::new_params(&mut rng, cf_cm_len); + + let prover_params = + ProverParams::, Pedersen> { + poseidon_config: poseidon_config.clone(), + cm_params: pedersen_params, + cf_cm_params: cf_pedersen_params, + }; + let verifier_params = VerifierParams:: { + poseidon_config: poseidon_config.clone(), + r1cs, + cf_r1cs, + }; + (prover_params, verifier_params) +} + +/// cargo run --release --example fold_sha256 +fn main() { + let num_steps = 10; + let initial_state = vec![Fr::from(1_u32)]; + + let F_circuit = Sha256FCircuit::::new(()); + + println!("Prepare Nova ProverParams & VerifierParams"); + let (prover_params, verifier_params) = nova_setup::>(F_circuit); + + /// The idea here is that eventually we could replace the next line chunk that defines the + /// `type NOVA = Nova<...>` by using another folding scheme that fulfills the `FoldingScheme` + /// trait, and the rest of our code would be working without needing to be updated. + type NOVA = Nova< + Projective, + GVar, + Projective2, + GVar2, + Sha256FCircuit, + Pedersen, + Pedersen, + >; + + println!("Initialize FoldingScheme"); + let mut folding_scheme = NOVA::init(&prover_params, F_circuit, initial_state.clone()).unwrap(); + + // compute a step of the IVC + for i in 0..num_steps { + let start = Instant::now(); + folding_scheme.prove_step().unwrap(); + println!("Nova::prove_step {}: {:?}", i, start.elapsed()); + } + + let (running_instance, incomming_instance, cyclefold_instance) = folding_scheme.instances(); + + println!("Run the Nova's IVC verifier"); + NOVA::verify( + verifier_params, + initial_state, + folding_scheme.state(), // latest state + Fr::from(num_steps as u32), + running_instance, + incomming_instance, + cyclefold_instance, + ) + .unwrap(); +} diff --git a/src/commitment/kzg.rs b/src/commitment/kzg.rs index beaf3da..debab15 100644 --- a/src/commitment/kzg.rs +++ b/src/commitment/kzg.rs @@ -65,6 +65,7 @@ where } /// KZGProver implements the CommitmentProver trait for the KZG commitment scheme. +#[derive(Debug, Clone, Default, Eq, PartialEq)] pub struct KZGProver<'a, C: CurveGroup> { _a: PhantomData<&'a ()>, _c: PhantomData, @@ -196,7 +197,7 @@ mod tests { use ark_std::{test_rng, UniformRand}; use super::*; - use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript}; + use crate::transcript::poseidon::{poseidon_test_config, PoseidonTranscript}; #[test] fn test_kzg_commitment_scheme() { diff --git a/src/commitment/mod.rs b/src/commitment/mod.rs index a8ef24b..fdba674 100644 --- a/src/commitment/mod.rs +++ b/src/commitment/mod.rs @@ -8,9 +8,9 @@ pub mod kzg; pub mod pedersen; /// CommitmentProver defines the vector commitment scheme prover trait. -pub trait CommitmentProver { - type Params: Debug; - type Proof: Debug; +pub trait CommitmentProver: Clone + Debug { + type Params: Clone + Debug; + type Proof: Clone + Debug; fn commit( params: &Self::Params, @@ -41,7 +41,7 @@ mod tests { use super::kzg::{KZGProver, KZGSetup, ProverKey}; use super::pedersen::Pedersen; use crate::transcript::{ - poseidon::{tests::poseidon_test_config, PoseidonTranscript}, + poseidon::{poseidon_test_config, PoseidonTranscript}, Transcript, }; diff --git a/src/commitment/pedersen.rs b/src/commitment/pedersen.rs index b711cc8..1feee43 100644 --- a/src/commitment/pedersen.rs +++ b/src/commitment/pedersen.rs @@ -165,7 +165,7 @@ mod tests { use ark_std::UniformRand; use super::*; - use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript}; + use crate::transcript::poseidon::{poseidon_test_config, PoseidonTranscript}; #[test] fn test_pedersen_vector() { diff --git a/src/constants.rs b/src/constants.rs index 742c6c7..e2d7f28 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,5 +1,5 @@ // used for the RO challenges. -// From [Srinath Setty](research.microsoft.com/en-us/people/srinath/): In Nova, soundness error ≤ -// 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this case, -// we keep the size of S close to 2^128. +// From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness +// error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this +// case, we keep the size of S close to 2^128. pub const N_BITS_RO: usize = 128; diff --git a/src/folding/circuits/sum_check.rs b/src/folding/circuits/sum_check.rs index b570198..e0da76b 100644 --- a/src/folding/circuits/sum_check.rs +++ b/src/folding/circuits/sum_check.rs @@ -176,7 +176,7 @@ mod tests { use crate::{ folding::circuits::sum_check::{IOPProofVar, VPAuxInfoVar}, transcript::{ - poseidon::{tests::poseidon_test_config, PoseidonTranscript, PoseidonTranscriptVar}, + poseidon::{poseidon_test_config, PoseidonTranscript, PoseidonTranscriptVar}, Transcript, TranscriptVar, }, utils::{ diff --git a/src/folding/hypernova/nimfs.rs b/src/folding/hypernova/nimfs.rs index da408e1..6872db1 100644 --- a/src/folding/hypernova/nimfs.rs +++ b/src/folding/hypernova/nimfs.rs @@ -373,7 +373,7 @@ where pub mod tests { use super::*; use crate::ccs::tests::{get_test_ccs, get_test_z}; - use crate::transcript::poseidon::tests::poseidon_test_config; + use crate::transcript::poseidon::poseidon_test_config; use crate::transcript::poseidon::PoseidonTranscript; use ark_std::test_rng; use ark_std::UniformRand; diff --git a/src/folding/nova/circuits.rs b/src/folding/nova/circuits.rs index faf56a6..26885a5 100644 --- a/src/folding/nova/circuits.rs +++ b/src/folding/nova/circuits.rs @@ -473,10 +473,10 @@ pub mod tests { use crate::commitment::pedersen::Pedersen; use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; use crate::folding::nova::{ - ivc::get_committed_instance_coordinates, nifs::NIFS, traits::NovaR1CS, Witness, + get_committed_instance_coordinates, nifs::NIFS, traits::NovaR1CS, Witness, }; use crate::frontend::tests::CubicFCircuit; - use crate::transcript::poseidon::tests::poseidon_test_config; + use crate::transcript::poseidon::poseidon_test_config; #[test] fn test_committed_instance_var() { diff --git a/src/folding/nova/cyclefold.rs b/src/folding/nova/cyclefold.rs index cb2d9c3..54dbb74 100644 --- a/src/folding/nova/cyclefold.rs +++ b/src/folding/nova/cyclefold.rs @@ -402,7 +402,7 @@ pub mod tests { use ark_std::UniformRand; use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; - use crate::transcript::poseidon::tests::poseidon_test_config; + use crate::transcript::poseidon::poseidon_test_config; #[test] fn test_committed_instance_cyclefold_var() { diff --git a/src/folding/nova/decider.rs b/src/folding/nova/decider_eth_circuit.rs similarity index 90% rename from src/folding/nova/decider.rs rename to src/folding/nova/decider_eth_circuit.rs index 8f4cf40..b79bc26 100644 --- a/src/folding/nova/decider.rs +++ b/src/folding/nova/decider_eth_circuit.rs @@ -21,8 +21,7 @@ use crate::ccs::r1cs::R1CS; use crate::commitment::{pedersen::Params as PedersenParams, CommitmentProver}; use crate::folding::nova::{ circuits::{CommittedInstanceVar, CF1, CF2}, - ivc::IVC, - CommittedInstance, Witness, + CommittedInstance, Nova, Witness, }; use crate::frontend::FCircuit; use crate::utils::gadgets::{ @@ -179,7 +178,7 @@ where /// Circuit that implements the in-circuit checks needed for the onchain (Ethereum's EVM) /// verification. -pub struct DeciderCircuit +pub struct DeciderEthCircuit where C1: CurveGroup, GC1: CurveVar>, @@ -220,7 +219,7 @@ where pub cf_U_i: Option>, pub cf_W_i: Option>, } -impl DeciderCircuit +impl DeciderEthCircuit where C1: CurveGroup, C2: CurveGroup, @@ -230,8 +229,8 @@ where // enforce that the CP2 is Pedersen commitment, since we're at Ethereum's EVM decider CP2: CommitmentProver>, { - pub fn from_ivc>( - ivc: IVC, + pub fn from_nova>( + nova: Nova, ) -> Self { Self { _c1: PhantomData, @@ -241,27 +240,27 @@ where _cp1: PhantomData, _cp2: PhantomData, - E_len: ivc.W_i.E.len(), - cf_E_len: ivc.cf_W_i.E.len(), - r1cs: ivc.r1cs, - cf_r1cs: ivc.cf_r1cs, - cf_pedersen_params: ivc.cf_cm_params, - poseidon_config: ivc.poseidon_config, - i: Some(ivc.i), - z_0: Some(ivc.z_0), - z_i: Some(ivc.z_i), - u_i: Some(ivc.u_i), - w_i: Some(ivc.w_i), - U_i: Some(ivc.U_i), - W_i: Some(ivc.W_i), - cf_U_i: Some(ivc.cf_U_i), - cf_W_i: Some(ivc.cf_W_i), + E_len: nova.W_i.E.len(), + cf_E_len: nova.cf_W_i.E.len(), + r1cs: nova.r1cs, + cf_r1cs: nova.cf_r1cs, + cf_pedersen_params: nova.cf_cm_params, + poseidon_config: nova.poseidon_config, + i: Some(nova.i), + z_0: Some(nova.z_0), + z_i: Some(nova.z_i), + u_i: Some(nova.u_i), + w_i: Some(nova.w_i), + U_i: Some(nova.U_i), + W_i: Some(nova.W_i), + cf_U_i: Some(nova.cf_U_i), + cf_W_i: Some(nova.cf_W_i), } } } impl ConstraintSynthesizer> - for DeciderCircuit + for DeciderEthCircuit where C1: CurveGroup, C2: CurveGroup, @@ -452,9 +451,10 @@ pub mod tests { use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::ivc::tests::get_pedersen_params_len; + use crate::folding::nova::{get_pedersen_params_len, ProverParams, VerifierParams}; use crate::frontend::tests::{CubicFCircuit, CustomFCircuit, WrapperCircuit}; - use crate::transcript::poseidon::tests::poseidon_test_config; + use crate::transcript::poseidon::poseidon_test_config; + use crate::FoldingScheme; use crate::ccs::r1cs::{extract_r1cs, extract_w_x}; use crate::ccs::r1cs::{ @@ -619,14 +619,24 @@ pub mod tests { let F_circuit = CubicFCircuit::::new(()); let z_0 = vec![Fr::from(3_u32)]; - let (pedersen_len, cf_pedersen_len) = - get_pedersen_params_len::>(&poseidon_config, F_circuit).unwrap(); - // generate the Pedersen params - let pedersen_params = Pedersen::::new_params(&mut rng, pedersen_len); - let cf_pedersen_params = Pedersen::::new_params(&mut rng, cf_pedersen_len); + // get the CM & CF_CM len + let (cm_len, cf_cm_len) = + get_pedersen_params_len::>( + &poseidon_config, + F_circuit, + ) + .unwrap(); + let pedersen_params = Pedersen::::new_params(&mut rng, cm_len); + let cf_pedersen_params = Pedersen::::new_params(&mut rng, cf_cm_len); + + let prover_params = + ProverParams::, Pedersen> { + poseidon_config: poseidon_config.clone(), + cm_params: pedersen_params, + cf_cm_params: cf_pedersen_params, + }; - // generate an IVC and do a step of it - let mut ivc = IVC::< + type NOVA = Nova< Projective, GVar, Projective2, @@ -634,26 +644,37 @@ pub mod tests { CubicFCircuit, Pedersen, Pedersen, - >::new( - poseidon_config, - pedersen_params, - cf_pedersen_params, - F_circuit, - z_0.clone(), + >; + + // generate a Nova instance and do a step of it + let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap(); + nova.prove_step().unwrap(); + let ivc_v = nova.clone(); + let verifier_params = VerifierParams:: { + poseidon_config: poseidon_config.clone(), + r1cs: ivc_v.r1cs, + cf_r1cs: ivc_v.cf_r1cs, + }; + NOVA::verify( + verifier_params, + z_0, + ivc_v.z_i, + Fr::one(), + (ivc_v.U_i, ivc_v.W_i), + (ivc_v.u_i, ivc_v.w_i), + (ivc_v.cf_U_i, ivc_v.cf_W_i), ) .unwrap(); - ivc.prove_step().unwrap(); - ivc.verify(z_0, 1).unwrap(); - // load the DeciderCircuit from the generated IVC - let decider_circuit = DeciderCircuit::< + // load the DeciderEthCircuit from the generated Nova instance + let decider_circuit = DeciderEthCircuit::< Projective, GVar, Projective2, GVar2, Pedersen, Pedersen, - >::from_ivc(ivc); + >::from_nova(nova); let cs = ConstraintSystem::::new_ref(); diff --git a/src/folding/nova/ivc.rs b/src/folding/nova/ivc.rs deleted file mode 100644 index f9afe47..0000000 --- a/src/folding/nova/ivc.rs +++ /dev/null @@ -1,470 +0,0 @@ -use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; -use ark_ec::{AffineRepr, CurveGroup, Group}; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar}; -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; -use ark_std::{One, Zero}; -use core::marker::PhantomData; - -use super::{ - circuits::{AugmentedFCircuit, ChallengeGadget, CF2}, - cyclefold::{CycleFoldChallengeGadget, CycleFoldCircuit}, -}; -use super::{nifs::NIFS, traits::NovaR1CS, CommittedInstance, Witness}; -use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS}; -use crate::commitment::CommitmentProver; -use crate::frontend::FCircuit; -use crate::Error; - -#[cfg(test)] -use super::cyclefold::CF_IO_LEN; - -/// Implements the Incremental Verifiable Computation described in sections 1.2 and 5 of -/// [Nova](https://eprint.iacr.org/2021/370.pdf) -pub struct IVC -where - C1: CurveGroup, - GC1: CurveVar>, - C2: CurveGroup, - GC2: CurveVar>, - FC: FCircuit, - CP1: CommitmentProver, - CP2: CommitmentProver, -{ - _gc1: PhantomData, - _c2: PhantomData, - _gc2: PhantomData, - /// R1CS of the Augmented Function circuit - pub r1cs: R1CS, - /// R1CS of the CycleFold circuit - pub cf_r1cs: R1CS, - pub poseidon_config: PoseidonConfig, - /// CommitmentProver::Params over C1 - pub cm_params: CP1::Params, - /// CycleFold CommitmentProver::Params, over C2 - pub cf_cm_params: CP2::Params, - /// F circuit, the circuit that is being folded - pub F: FC, - pub i: C1::ScalarField, - /// initial state - pub z_0: Vec, - /// current i-th state - pub z_i: Vec, - /// Nova instances - pub w_i: Witness, - pub u_i: CommittedInstance, - pub W_i: Witness, - pub U_i: CommittedInstance, - - /// CycleFold running instance - pub cf_W_i: Witness, - pub cf_U_i: CommittedInstance, -} - -impl IVC -where - C1: CurveGroup, - GC1: CurveVar>, - C2: CurveGroup, - GC2: CurveVar>, - FC: FCircuit, - CP1: CommitmentProver, - CP2: CommitmentProver, - ::BaseField: PrimeField, - ::BaseField: PrimeField, - ::ScalarField: Absorb, - ::ScalarField: Absorb, - C1: CurveGroup, - for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, - for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, -{ - /// Initializes the IVC for the given parameters and initial state `z_0`. - pub fn new( - poseidon_config: PoseidonConfig, - cm_params: CP1::Params, - cf_cm_params: CP2::Params, - F: FC, - z_0: Vec, - ) -> Result { - // prepare the circuit to obtain its R1CS - let cs = ConstraintSystem::::new_ref(); - let cs2 = ConstraintSystem::::new_ref(); - - let augmented_F_circuit = AugmentedFCircuit::::empty(&poseidon_config, F); - let cf_circuit = CycleFoldCircuit::::empty(); - - augmented_F_circuit.generate_constraints(cs.clone())?; - cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let r1cs = extract_r1cs::(&cs); - - cf_circuit.generate_constraints(cs2.clone())?; - cs2.finalize(); - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let cf_r1cs = extract_r1cs::(&cs2); - - // setup the dummy instances - let (w_dummy, u_dummy) = r1cs.dummy_instance(); - let (cf_w_dummy, cf_u_dummy) = cf_r1cs.dummy_instance(); - - // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the - // R1CS that we're working with. - Ok(Self { - _gc1: PhantomData, - _c2: PhantomData, - _gc2: PhantomData, - r1cs, - cf_r1cs, - poseidon_config, - cm_params, - cf_cm_params, - F, - i: C1::ScalarField::zero(), - z_0: z_0.clone(), - z_i: z_0, - w_i: w_dummy.clone(), - u_i: u_dummy.clone(), - W_i: w_dummy, - U_i: u_dummy, - // cyclefold running instance - cf_W_i: cf_w_dummy.clone(), - cf_U_i: cf_u_dummy.clone(), - }) - } - - /// Implements IVC.P - pub fn prove_step(&mut self) -> Result<(), Error> { - let augmented_F_circuit: AugmentedFCircuit; - let cf_circuit: CycleFoldCircuit; - - let z_i1 = self.F.step_native(self.z_i.clone())?; - - // compute T and cmT for AugmentedFCircuit - let (T, cmT) = self.compute_cmT()?; - - let r_bits = ChallengeGadget::::get_challenge_native( - &self.poseidon_config, - self.u_i.clone(), - self.U_i.clone(), - cmT, - )?; - let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) - .ok_or(Error::OutOfBounds)?; - - // fold Nova instances - let (W_i1, U_i1): (Witness, CommittedInstance) = NIFS::::fold_instances( - r_Fr, &self.w_i, &self.u_i, &self.W_i, &self.U_i, &T, cmT, - )?; - - // folded instance output (public input, x) - // u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1}) - let u_i1_x = U_i1.hash( - &self.poseidon_config, - self.i + C1::ScalarField::one(), - self.z_0.clone(), - z_i1.clone(), - )?; - - if self.i == C1::ScalarField::zero() { - // base case - augmented_F_circuit = AugmentedFCircuit:: { - _gc2: PhantomData, - poseidon_config: self.poseidon_config.clone(), - i: Some(C1::ScalarField::zero()), // = i=0 - z_0: Some(self.z_0.clone()), // = z_i - z_i: Some(self.z_i.clone()), - u_i: Some(self.u_i.clone()), // = dummy - U_i: Some(self.U_i.clone()), // = dummy - U_i1: Some(U_i1.clone()), - cmT: Some(cmT), - F: self.F, - x: Some(u_i1_x), - cf_u_i: None, - cf_U_i: None, - cf_U_i1: None, - cf_cmT: None, - cf_r_nonnat: None, - }; - - #[cfg(test)] - NIFS::::verify_folded_instance(r_Fr, &self.u_i, &self.U_i, &U_i1, &cmT)?; - } else { - // CycleFold part: - // get the vector used as public inputs 'x' in the CycleFold circuit - let cf_u_i_x = [ - get_committed_instance_coordinates(&self.u_i), - get_committed_instance_coordinates(&self.U_i), - get_committed_instance_coordinates(&U_i1), - ] - .concat(); - - cf_circuit = CycleFoldCircuit:: { - _gc: PhantomData, - r_bits: Some(r_bits.clone()), - cmT: Some(cmT), - u_i: Some(self.u_i.clone()), - U_i: Some(self.U_i.clone()), - U_i1: Some(U_i1.clone()), - x: Some(cf_u_i_x.clone()), - }; - - let cs2 = ConstraintSystem::::new_ref(); - cf_circuit.generate_constraints(cs2.clone())?; - - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let (cf_w_i, cf_x_i) = extract_w_x::(&cs2); - if cf_x_i != cf_u_i_x { - return Err(Error::NotEqual); - } - - #[cfg(test)] - if cf_x_i.len() != CF_IO_LEN { - return Err(Error::NotExpectedLength(cf_x_i.len(), CF_IO_LEN)); - } - - // fold cyclefold instances - let cf_w_i = Witness::::new(cf_w_i.clone(), self.cf_r1cs.A.n_rows); - let cf_u_i: CommittedInstance = - cf_w_i.commit::(&self.cf_cm_params, cf_x_i.clone())?; - - // compute T* and cmT* for CycleFoldCircuit - let (cf_T, cf_cmT) = self.compute_cf_cmT(&cf_w_i, &cf_u_i)?; - - let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_native( - &self.poseidon_config, - cf_u_i.clone(), - self.cf_U_i.clone(), - cf_cmT, - )?; - let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) - .ok_or(Error::OutOfBounds)?; - - let (cf_W_i1, cf_U_i1) = NIFS::::fold_instances( - cf_r_Fq, - &self.cf_W_i, - &self.cf_U_i, - &cf_w_i, - &cf_u_i, - &cf_T, - cf_cmT, - )?; - - augmented_F_circuit = AugmentedFCircuit:: { - _gc2: PhantomData, - poseidon_config: self.poseidon_config.clone(), - i: Some(self.i), - z_0: Some(self.z_0.clone()), - z_i: Some(self.z_i.clone()), - u_i: Some(self.u_i.clone()), - U_i: Some(self.U_i.clone()), - U_i1: Some(U_i1.clone()), - cmT: Some(cmT), - F: self.F, - x: Some(u_i1_x), - // cyclefold values - cf_u_i: Some(cf_u_i.clone()), - cf_U_i: Some(self.cf_U_i.clone()), - cf_U_i1: Some(cf_U_i1.clone()), - cf_cmT: Some(cf_cmT), - cf_r_nonnat: Some(cf_r_Fq), - }; - - self.cf_W_i = cf_W_i1.clone(); - self.cf_U_i = cf_U_i1.clone(); - - #[cfg(test)] - { - self.cf_r1cs.check_instance_relation(&cf_w_i, &cf_u_i)?; - self.cf_r1cs - .check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?; - } - } - - let cs = ConstraintSystem::::new_ref(); - - augmented_F_circuit.generate_constraints(cs.clone())?; - - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let (w_i1, x_i1) = extract_w_x::(&cs); - if x_i1[0] != u_i1_x { - return Err(Error::NotEqual); - } - - #[cfg(test)] - if x_i1.len() != 1 { - return Err(Error::NotExpectedLength(x_i1.len(), 1)); - } - - // set values for next iteration - self.i += C1::ScalarField::one(); - self.z_i = z_i1.clone(); - self.w_i = Witness::::new(w_i1, self.r1cs.A.n_rows); - self.u_i = self.w_i.commit::(&self.cm_params, vec![u_i1_x])?; - self.W_i = W_i1.clone(); - self.U_i = U_i1.clone(); - - #[cfg(test)] - { - self.r1cs.check_instance_relation(&self.w_i, &self.u_i)?; - self.r1cs - .check_relaxed_instance_relation(&self.W_i, &self.U_i)?; - } - - Ok(()) - } - - /// Implements IVC.V - pub fn verify(&mut self, z_0: Vec, num_steps: u32) -> Result<(), Error> { - if self.i != C1::ScalarField::from(num_steps) { - return Err(Error::IVCVerificationFail); - } - - if self.u_i.x.len() != 1 || self.U_i.x.len() != 1 { - return Err(Error::IVCVerificationFail); - } - - // check that u_i's output points to the running instance - // u_i.X == H(i, z_0, z_i, U_i) - let expected_u_i_x = self - .U_i - .hash(&self.poseidon_config, self.i, z_0, self.z_i.clone())?; - if expected_u_i_x != self.u_i.x[0] { - return Err(Error::IVCVerificationFail); - } - - // check u_i.cmE==0, u_i.u==1 (=u_i is a un-relaxed instance) - if !self.u_i.cmE.is_zero() || !self.u_i.u.is_one() { - return Err(Error::IVCVerificationFail); - } - - // check R1CS satisfiability - self.r1cs.check_instance_relation(&self.w_i, &self.u_i)?; - // check RelaxedR1CS satisfiability - self.r1cs - .check_relaxed_instance_relation(&self.W_i, &self.U_i)?; - - // check CycleFold RelaxedR1CS satisfiability - self.cf_r1cs - .check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?; - - Ok(()) - } - - // computes T and cmT for the AugmentedFCircuit - fn compute_cmT(&self) -> Result<(Vec, C1), Error> { - NIFS::::compute_cmT( - &self.cm_params, - &self.r1cs, - &self.w_i, - &self.u_i, - &self.W_i, - &self.U_i, - ) - } - // computes T* and cmT* for the CycleFoldCircuit - fn compute_cf_cmT( - &self, - cf_w_i: &Witness, - cf_u_i: &CommittedInstance, - ) -> Result<(Vec, C2), Error> { - NIFS::::compute_cyclefold_cmT( - &self.cf_cm_params, - &self.cf_r1cs, - cf_w_i, - cf_u_i, - &self.cf_W_i, - &self.cf_U_i, - ) - } -} - -pub(crate) fn get_committed_instance_coordinates( - u: &CommittedInstance, -) -> Vec { - let zero = (&C::BaseField::zero(), &C::BaseField::one()); - - let cmE = u.cmE.into_affine(); - let (cmE_x, cmE_y) = cmE.xy().unwrap_or(zero); - - let cmW = u.cmW.into_affine(); - let (cmW_x, cmW_y) = cmW.xy().unwrap_or(zero); - vec![*cmE_x, *cmE_y, *cmW_x, *cmW_y] -} - -#[cfg(test)] -pub mod tests { - use super::*; - use ark_pallas::{constraints::GVar, Fr, Projective}; - use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; - - use crate::commitment::pedersen::Pedersen; - use crate::frontend::tests::CubicFCircuit; - use crate::transcript::poseidon::tests::poseidon_test_config; - - /// helper method to get the r1cs from the circuit - pub fn get_r1cs( - circuit: impl ConstraintSynthesizer, - ) -> Result, Error> { - let cs = ConstraintSystem::::new_ref(); - circuit.generate_constraints(cs.clone())?; - cs.finalize(); - let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let r1cs = extract_r1cs::(&cs); - Ok(r1cs) - } - - /// helper method to get the pedersen params length for both the AugmentedFCircuit and the - /// CycleFold circuit - pub fn get_pedersen_params_len>( - poseidon_config: &PoseidonConfig, - F_circuit: FC, - ) -> Result<(usize, usize), Error> { - let augmented_F_circuit = AugmentedFCircuit::::empty( - poseidon_config, - F_circuit, - ); - let cf_circuit = CycleFoldCircuit::::empty(); - let r1cs = get_r1cs(augmented_F_circuit)?; - let cf_r1cs = get_r1cs(cf_circuit)?; - Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows)) - } - - #[test] - fn test_ivc() { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_test_config::(); - - let F_circuit = CubicFCircuit::::new(()); - let z_0 = vec![Fr::from(3_u32)]; - - let (pedersen_len, cf_pedersen_len) = - get_pedersen_params_len::>(&poseidon_config, F_circuit).unwrap(); - // generate the Pedersen params - let pedersen_params = Pedersen::::new_params(&mut rng, pedersen_len); - let cf_pedersen_params = Pedersen::::new_params(&mut rng, cf_pedersen_len); - - let mut ivc = IVC::< - Projective, - GVar, - Projective2, - GVar2, - CubicFCircuit, - Pedersen, - Pedersen, - >::new( - poseidon_config, - pedersen_params, - cf_pedersen_params, - F_circuit, - z_0.clone(), - ) - .unwrap(); - - let num_steps: usize = 3; - for _ in 0..num_steps { - ivc.prove_step().unwrap(); - } - - ivc.verify(z_0, num_steps as u32).unwrap(); - } -} diff --git a/src/folding/nova/mod.rs b/src/folding/nova/mod.rs index d060556..d9efa2d 100644 --- a/src/folding/nova/mod.rs +++ b/src/folding/nova/mod.rs @@ -3,22 +3,37 @@ use ark_crypto_primitives::{ crh::{poseidon::CRH, CRHScheme}, sponge::{poseidon::PoseidonConfig, Absorb}, }; -use ark_ec::{CurveGroup, Group}; +use ark_ec::{AffineRepr, CurveGroup, Group}; +use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar}; use ark_std::fmt::Debug; use ark_std::{One, Zero}; +use core::marker::PhantomData; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; + +use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS}; use crate::commitment::CommitmentProver; use crate::folding::circuits::nonnative::point_to_nonnative_limbs; +use crate::frontend::FCircuit; use crate::utils::vec::is_zero_vec; use crate::Error; +use crate::FoldingScheme; pub mod circuits; pub mod cyclefold; -pub mod decider; -pub mod ivc; +pub mod decider_eth_circuit; pub mod nifs; pub mod traits; +use circuits::{AugmentedFCircuit, ChallengeGadget, CF2}; +use cyclefold::{CycleFoldChallengeGadget, CycleFoldCircuit}; +use nifs::NIFS; +use traits::NovaR1CS; + +#[cfg(test)] +use cyclefold::CF_IO_LEN; + #[derive(Debug, Clone, Eq, PartialEq)] pub struct CommittedInstance { pub cmE: C, @@ -45,7 +60,7 @@ where { /// hash implements the committed instance hash compatible with the gadget implemented in /// nova/circuits.rs::CommittedInstanceVar.hash. - /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the + /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U_i` is the /// `CommittedInstance`. pub fn hash( &self, @@ -116,3 +131,587 @@ where }) } } + +#[derive(Debug, Clone)] +pub struct ProverParams +where + C1: CurveGroup, + C2: CurveGroup, + CP1: CommitmentProver, + CP2: CommitmentProver, +{ + pub poseidon_config: PoseidonConfig, + pub cm_params: CP1::Params, + pub cf_cm_params: CP2::Params, +} + +#[derive(Debug, Clone)] +pub struct VerifierParams { + pub poseidon_config: PoseidonConfig, + pub r1cs: R1CS, + pub cf_r1cs: R1CS, +} + +/// Implements Nova+CycleFold's IVC, described in [Nova](https://eprint.iacr.org/2021/370.pdf) and +/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait +#[derive(Clone, Debug)] +pub struct Nova +where + C1: CurveGroup, + GC1: CurveVar>, + C2: CurveGroup, + GC2: CurveVar>, + FC: FCircuit, + CP1: CommitmentProver, + CP2: CommitmentProver, +{ + _gc1: PhantomData, + _c2: PhantomData, + _gc2: PhantomData, + /// R1CS of the Augmented Function circuit + pub r1cs: R1CS, + /// R1CS of the CycleFold circuit + pub cf_r1cs: R1CS, + pub poseidon_config: PoseidonConfig, + /// CommitmentProver::Params over C1 + pub cm_params: CP1::Params, + /// CycleFold CommitmentProver::Params, over C2 + pub cf_cm_params: CP2::Params, + /// F circuit, the circuit that is being folded + pub F: FC, + pub i: C1::ScalarField, + /// initial state + pub z_0: Vec, + /// current i-th state + pub z_i: Vec, + /// Nova instances + pub w_i: Witness, + pub u_i: CommittedInstance, + pub W_i: Witness, + pub U_i: CommittedInstance, + + /// CycleFold running instance + pub cf_W_i: Witness, + pub cf_U_i: CommittedInstance, +} + +impl FoldingScheme + for Nova +where + C1: CurveGroup, + GC1: CurveVar>, + C2: CurveGroup, + GC2: CurveVar>, + FC: FCircuit, + CP1: CommitmentProver, + CP2: CommitmentProver, + ::BaseField: PrimeField, + ::BaseField: PrimeField, + ::ScalarField: Absorb, + ::ScalarField: Absorb, + C1: CurveGroup, + for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + type PreprocessorParam = (Self::ProverParam, FC); + type ProverParam = ProverParams; + type VerifierParam = VerifierParams; + type Witness = Witness; + type CommittedInstanceWithWitness = (CommittedInstance, Witness); + type CFCommittedInstanceWithWitness = (CommittedInstance, Witness); + type CommittedInstance = CommittedInstance; + + fn preprocess( + prep_param: &Self::PreprocessorParam, + ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { + let (prover_params, F_circuit) = prep_param; + + let (r1cs, cf_r1cs) = + get_r1cs::(&prover_params.poseidon_config, *F_circuit)?; + + let verifier_params = VerifierParams:: { + poseidon_config: prover_params.poseidon_config.clone(), + r1cs, + cf_r1cs, + }; + Ok((prover_params.clone(), verifier_params)) + } + + /// Initializes the Nova+CycleFold's IVC for the given parameters and initial state `z_0`. + fn init(pp: &Self::ProverParam, F: FC, z_0: Vec) -> Result { + // prepare the circuit to obtain its R1CS + let cs = ConstraintSystem::::new_ref(); + let cs2 = ConstraintSystem::::new_ref(); + + let augmented_F_circuit = + AugmentedFCircuit::::empty(&pp.poseidon_config, F); + let cf_circuit = CycleFoldCircuit::::empty(); + + augmented_F_circuit.generate_constraints(cs.clone())?; + cs.finalize(); + let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let r1cs = extract_r1cs::(&cs); + + cf_circuit.generate_constraints(cs2.clone())?; + cs2.finalize(); + let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let cf_r1cs = extract_r1cs::(&cs2); + + // setup the dummy instances + let (w_dummy, u_dummy) = r1cs.dummy_instance(); + let (cf_w_dummy, cf_u_dummy) = cf_r1cs.dummy_instance(); + + // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the + // R1CS that we're working with. + Ok(Self { + _gc1: PhantomData, + _c2: PhantomData, + _gc2: PhantomData, + r1cs, + cf_r1cs, + poseidon_config: pp.poseidon_config.clone(), + cm_params: pp.cm_params.clone(), + cf_cm_params: pp.cf_cm_params.clone(), + F, + i: C1::ScalarField::zero(), + z_0: z_0.clone(), + z_i: z_0, + w_i: w_dummy.clone(), + u_i: u_dummy.clone(), + W_i: w_dummy, + U_i: u_dummy, + // cyclefold running instance + cf_W_i: cf_w_dummy.clone(), + cf_U_i: cf_u_dummy.clone(), + }) + } + + /// Implements IVC.P of Nova+CycleFold + fn prove_step(&mut self) -> Result<(), Error> { + let augmented_F_circuit: AugmentedFCircuit; + let cf_circuit: CycleFoldCircuit; + + let z_i1 = self.F.step_native(self.z_i.clone())?; + + // compute T and cmT for AugmentedFCircuit + let (T, cmT) = self.compute_cmT()?; + + let r_bits = ChallengeGadget::::get_challenge_native( + &self.poseidon_config, + self.u_i.clone(), + self.U_i.clone(), + cmT, + )?; + let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; + + // fold Nova instances + let (W_i1, U_i1): (Witness, CommittedInstance) = NIFS::::fold_instances( + r_Fr, &self.w_i, &self.u_i, &self.W_i, &self.U_i, &T, cmT, + )?; + + // folded instance output (public input, x) + // u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1}) + let u_i1_x = U_i1.hash( + &self.poseidon_config, + self.i + C1::ScalarField::one(), + self.z_0.clone(), + z_i1.clone(), + )?; + + if self.i == C1::ScalarField::zero() { + // base case + augmented_F_circuit = AugmentedFCircuit:: { + _gc2: PhantomData, + poseidon_config: self.poseidon_config.clone(), + i: Some(C1::ScalarField::zero()), // = i=0 + z_0: Some(self.z_0.clone()), // = z_i + z_i: Some(self.z_i.clone()), + u_i: Some(self.u_i.clone()), // = dummy + U_i: Some(self.U_i.clone()), // = dummy + U_i1: Some(U_i1.clone()), + cmT: Some(cmT), + F: self.F, + x: Some(u_i1_x), + cf_u_i: None, + cf_U_i: None, + cf_U_i1: None, + cf_cmT: None, + cf_r_nonnat: None, + }; + + #[cfg(test)] + NIFS::::verify_folded_instance(r_Fr, &self.u_i, &self.U_i, &U_i1, &cmT)?; + } else { + // CycleFold part: + // get the vector used as public inputs 'x' in the CycleFold circuit + let cf_u_i_x = [ + get_committed_instance_coordinates(&self.u_i), + get_committed_instance_coordinates(&self.U_i), + get_committed_instance_coordinates(&U_i1), + ] + .concat(); + + cf_circuit = CycleFoldCircuit:: { + _gc: PhantomData, + r_bits: Some(r_bits.clone()), + cmT: Some(cmT), + u_i: Some(self.u_i.clone()), + U_i: Some(self.U_i.clone()), + U_i1: Some(U_i1.clone()), + x: Some(cf_u_i_x.clone()), + }; + + let cs2 = ConstraintSystem::::new_ref(); + cf_circuit.generate_constraints(cs2.clone())?; + + let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let (cf_w_i, cf_x_i) = extract_w_x::(&cs2); + if cf_x_i != cf_u_i_x { + return Err(Error::NotEqual); + } + + #[cfg(test)] + if cf_x_i.len() != CF_IO_LEN { + return Err(Error::NotExpectedLength(cf_x_i.len(), CF_IO_LEN)); + } + + // fold cyclefold instances + let cf_w_i = Witness::::new(cf_w_i.clone(), self.cf_r1cs.A.n_rows); + let cf_u_i: CommittedInstance = + cf_w_i.commit::(&self.cf_cm_params, cf_x_i.clone())?; + + // compute T* and cmT* for CycleFoldCircuit + let (cf_T, cf_cmT) = self.compute_cf_cmT(&cf_w_i, &cf_u_i)?; + + let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_native( + &self.poseidon_config, + cf_u_i.clone(), + self.cf_U_i.clone(), + cf_cmT, + )?; + let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) + .ok_or(Error::OutOfBounds)?; + + let (cf_W_i1, cf_U_i1) = NIFS::::fold_instances( + cf_r_Fq, + &self.cf_W_i, + &self.cf_U_i, + &cf_w_i, + &cf_u_i, + &cf_T, + cf_cmT, + )?; + + augmented_F_circuit = AugmentedFCircuit:: { + _gc2: PhantomData, + poseidon_config: self.poseidon_config.clone(), + i: Some(self.i), + z_0: Some(self.z_0.clone()), + z_i: Some(self.z_i.clone()), + u_i: Some(self.u_i.clone()), + U_i: Some(self.U_i.clone()), + U_i1: Some(U_i1.clone()), + cmT: Some(cmT), + F: self.F, + x: Some(u_i1_x), + // cyclefold values + cf_u_i: Some(cf_u_i.clone()), + cf_U_i: Some(self.cf_U_i.clone()), + cf_U_i1: Some(cf_U_i1.clone()), + cf_cmT: Some(cf_cmT), + cf_r_nonnat: Some(cf_r_Fq), + }; + + self.cf_W_i = cf_W_i1.clone(); + self.cf_U_i = cf_U_i1.clone(); + + #[cfg(test)] + { + self.cf_r1cs.check_instance_relation(&cf_w_i, &cf_u_i)?; + self.cf_r1cs + .check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?; + } + } + + let cs = ConstraintSystem::::new_ref(); + + augmented_F_circuit.generate_constraints(cs.clone())?; + + let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let (w_i1, x_i1) = extract_w_x::(&cs); + if x_i1[0] != u_i1_x { + return Err(Error::NotEqual); + } + + #[cfg(test)] + if x_i1.len() != 1 { + return Err(Error::NotExpectedLength(x_i1.len(), 1)); + } + + // set values for next iteration + self.i += C1::ScalarField::one(); + self.z_i = z_i1.clone(); + self.w_i = Witness::::new(w_i1, self.r1cs.A.n_rows); + self.u_i = self.w_i.commit::(&self.cm_params, vec![u_i1_x])?; + self.W_i = W_i1.clone(); + self.U_i = U_i1.clone(); + + #[cfg(test)] + { + self.r1cs.check_instance_relation(&self.w_i, &self.u_i)?; + self.r1cs + .check_relaxed_instance_relation(&self.W_i, &self.U_i)?; + } + + Ok(()) + } + + fn state(&self) -> Vec { + self.z_i.clone() + } + fn instances( + &self, + ) -> ( + Self::CommittedInstanceWithWitness, + Self::CommittedInstanceWithWitness, + Self::CFCommittedInstanceWithWitness, + ) { + ( + (self.U_i.clone(), self.W_i.clone()), + (self.u_i.clone(), self.w_i.clone()), + (self.cf_U_i.clone(), self.cf_W_i.clone()), + ) + } + + /// Implements IVC.V of Nova+CycleFold + fn verify( + vp: Self::VerifierParam, + z_0: Vec, // initial state + z_i: Vec, // last state + num_steps: C1::ScalarField, + running_instance: Self::CommittedInstanceWithWitness, + incomming_instance: Self::CommittedInstanceWithWitness, + cyclefold_instance: Self::CFCommittedInstanceWithWitness, + ) -> Result<(), Error> { + let (U_i, W_i) = running_instance; + let (u_i, w_i) = incomming_instance; + let (cf_U_i, cf_W_i) = cyclefold_instance; + + if u_i.x.len() != 1 || U_i.x.len() != 1 { + return Err(Error::IVCVerificationFail); + } + + // check that u_i's output points to the running instance + // u_i.X == H(i, z_0, z_i, U_i) + let expected_u_i_x = U_i.hash(&vp.poseidon_config, num_steps, z_0, z_i.clone())?; + if expected_u_i_x != u_i.x[0] { + return Err(Error::IVCVerificationFail); + } + + // check u_i.cmE==0, u_i.u==1 (=u_i is a un-relaxed instance) + if !u_i.cmE.is_zero() || !u_i.u.is_one() { + return Err(Error::IVCVerificationFail); + } + + // check R1CS satisfiability + vp.r1cs.check_instance_relation(&w_i, &u_i)?; + // check RelaxedR1CS satisfiability + vp.r1cs.check_relaxed_instance_relation(&W_i, &U_i)?; + + // check CycleFold RelaxedR1CS satisfiability + vp.cf_r1cs + .check_relaxed_instance_relation(&cf_W_i, &cf_U_i)?; + + Ok(()) + } +} + +impl Nova +where + C1: CurveGroup, + GC1: CurveVar>, + C2: CurveGroup, + GC2: CurveVar>, + FC: FCircuit, + CP1: CommitmentProver, + CP2: CommitmentProver, + ::BaseField: PrimeField, + ::ScalarField: Absorb, + ::ScalarField: Absorb, + C1: CurveGroup, +{ + // computes T and cmT for the AugmentedFCircuit + fn compute_cmT(&self) -> Result<(Vec, C1), Error> { + NIFS::::compute_cmT( + &self.cm_params, + &self.r1cs, + &self.w_i, + &self.u_i, + &self.W_i, + &self.U_i, + ) + } + // computes T* and cmT* for the CycleFoldCircuit + fn compute_cf_cmT( + &self, + cf_w_i: &Witness, + cf_u_i: &CommittedInstance, + ) -> Result<(Vec, C2), Error> { + NIFS::::compute_cyclefold_cmT( + &self.cf_cm_params, + &self.cf_r1cs, + cf_w_i, + cf_u_i, + &self.cf_W_i, + &self.cf_U_i, + ) + } +} + +/// helper method to get the r1cs from the ConstraintSynthesizer +pub fn get_r1cs_from_cs( + circuit: impl ConstraintSynthesizer, +) -> Result, Error> { + let cs = ConstraintSystem::::new_ref(); + circuit.generate_constraints(cs.clone())?; + cs.finalize(); + let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let r1cs = extract_r1cs::(&cs); + Ok(r1cs) +} + +/// helper method to get the R1CS for both the AugmentedFCircuit and the CycleFold circuit +#[allow(clippy::type_complexity)] +pub fn get_r1cs( + poseidon_config: &PoseidonConfig, + F_circuit: FC, +) -> Result<(R1CS, R1CS), Error> +where + C1: CurveGroup, + GC1: CurveVar>, + C2: CurveGroup, + GC2: CurveVar>, + FC: FCircuit, + ::BaseField: PrimeField, + ::BaseField: PrimeField, + ::ScalarField: Absorb, + ::ScalarField: Absorb, + C1: CurveGroup, + for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + let augmented_F_circuit = + AugmentedFCircuit::::empty(poseidon_config, F_circuit); + let cf_circuit = CycleFoldCircuit::::empty(); + let r1cs = get_r1cs_from_cs::(augmented_F_circuit)?; + let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; + Ok((r1cs, cf_r1cs)) +} + +/// helper method to get the pedersen params length for both the AugmentedFCircuit and the +/// CycleFold circuit +pub fn get_pedersen_params_len( + poseidon_config: &PoseidonConfig, + F_circuit: FC, +) -> Result<(usize, usize), Error> +where + C1: CurveGroup, + GC1: CurveVar>, + C2: CurveGroup, + GC2: CurveVar>, + FC: FCircuit, + ::BaseField: PrimeField, + ::BaseField: PrimeField, + ::ScalarField: Absorb, + ::ScalarField: Absorb, + C1: CurveGroup, + for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + let (r1cs, cf_r1cs) = get_r1cs::(poseidon_config, F_circuit)?; + Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows)) +} + +pub(crate) fn get_committed_instance_coordinates( + u: &CommittedInstance, +) -> Vec { + let zero = (&C::BaseField::zero(), &C::BaseField::one()); + + let cmE = u.cmE.into_affine(); + let (cmE_x, cmE_y) = cmE.xy().unwrap_or(zero); + + let cmW = u.cmW.into_affine(); + let (cmW_x, cmW_y) = cmW.xy().unwrap_or(zero); + vec![*cmE_x, *cmE_y, *cmW_x, *cmW_y] +} + +#[cfg(test)] +pub mod tests { + use super::*; + use ark_pallas::{constraints::GVar, Fr, Projective}; + use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; + + use crate::commitment::pedersen::Pedersen; + use crate::frontend::tests::CubicFCircuit; + use crate::transcript::poseidon::poseidon_test_config; + + #[test] + fn test_ivc() { + type NOVA = Nova< + Projective, + GVar, + Projective2, + GVar2, + CubicFCircuit, + Pedersen, + Pedersen, + >; + + let mut rng = ark_std::test_rng(); + let poseidon_config = poseidon_test_config::(); + + let F_circuit = CubicFCircuit::::new(()); + let z_0 = vec![Fr::from(3_u32)]; + + let (cm_len, cf_cm_len) = + get_pedersen_params_len::>( + &poseidon_config, + F_circuit, + ) + .unwrap(); + let pedersen_params = Pedersen::::new_params(&mut rng, cm_len); + let cf_pedersen_params = Pedersen::::new_params(&mut rng, cf_cm_len); + + let prover_params = + ProverParams::, Pedersen> { + poseidon_config: poseidon_config.clone(), + cm_params: pedersen_params, + cf_cm_params: cf_pedersen_params, + }; + + let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap(); + + let num_steps: usize = 3; + for _ in 0..num_steps { + nova.prove_step().unwrap(); + } + assert_eq!(Fr::from(num_steps as u32), nova.i); + + let verifier_params = VerifierParams:: { + poseidon_config, + r1cs: nova.r1cs, + cf_r1cs: nova.cf_r1cs, + }; + NOVA::verify( + verifier_params, + z_0, + nova.z_i, + nova.i, + (nova.U_i, nova.W_i), + (nova.u_i, nova.w_i), + (nova.cf_U_i, nova.cf_W_i), + ) + .unwrap(); + } +} diff --git a/src/folding/nova/nifs.rs b/src/folding/nova/nifs.rs index a8670a1..b95053f 100644 --- a/src/folding/nova/nifs.rs +++ b/src/folding/nova/nifs.rs @@ -209,7 +209,7 @@ pub mod tests { use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; use crate::folding::nova::circuits::ChallengeGadget; use crate::folding::nova::traits::NovaR1CS; - use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript}; + use crate::transcript::poseidon::{poseidon_test_config, PoseidonTranscript}; use crate::utils::vec::vec_scalar_mul; #[allow(clippy::type_complexity)] diff --git a/src/folding/protogalaxy/folding.rs b/src/folding/protogalaxy/folding.rs index ba5f8e7..21a65fa 100644 --- a/src/folding/protogalaxy/folding.rs +++ b/src/folding/protogalaxy/folding.rs @@ -370,7 +370,7 @@ mod tests { use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z}; use crate::commitment::{pedersen::Pedersen, CommitmentProver}; - use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript}; + use crate::transcript::poseidon::{poseidon_test_config, PoseidonTranscript}; pub(crate) fn check_instance( r1cs: &R1CS, diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index dda6ab5..fced191 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -8,6 +8,8 @@ use ark_std::fmt::Debug; /// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie. /// inside the agmented F' function). +/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state after applying +/// the step. pub trait FCircuit: Clone + Copy + Debug { type Params: Debug; diff --git a/src/lib.rs b/src/lib.rs index 3700f49..7e32f94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,15 @@ #![allow(non_snake_case)] #![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(clippy::upper_case_acronyms)] use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_std::{fmt::Debug, rand::RngCore}; use thiserror::Error; +use crate::frontend::FCircuit; + pub mod transcript; use transcript::Transcript; pub mod ccs; @@ -69,38 +73,53 @@ pub enum Error { /// - C2 is the auxiliary curve, which we use for the commitments, whose BaseField (for point /// coordinates) are in the C1::ScalarField. /// In other words, C1.Fq == C2.Fr, and C1.Fr == C2.Fq. -pub trait FoldingScheme: Clone + Debug +pub trait FoldingScheme: Clone + Debug where C1: CurveGroup, C2::BaseField: PrimeField, + FC: FCircuit, { type PreprocessorParam: Debug; type ProverParam: Debug; type VerifierParam: Debug; type Witness: Debug; type CommittedInstanceWithWitness: Debug; + type CFCommittedInstanceWithWitness: Debug; // CycleFold CommittedInstance & Witness type CommittedInstance: Clone + Debug; fn preprocess( prep_param: &Self::PreprocessorParam, ) -> Result<(Self::ProverParam, Self::VerifierParam), Error>; - fn init_accumulator( + fn init( pp: &Self::ProverParam, - ) -> Result; + step_circuit: FC, + z_0: Vec, // initial state + ) -> Result; - fn prove( - pp: &Self::ProverParam, - running_instance: &mut Self::CommittedInstanceWithWitness, - incomming_instances: &[Self::Witness], - transcript: &mut impl Transcript, - ) -> Result<(), Error>; + fn prove_step(&mut self) -> Result<(), Error>; + + // returns the state at the current step + fn state(&self) -> Vec; + + // returns the instances at the current step + fn instances( + &self, + ) -> ( + Self::CommittedInstanceWithWitness, + Self::CommittedInstanceWithWitness, + Self::CFCommittedInstanceWithWitness, + ); fn verify( - vp: &Self::VerifierParam, - running_instance: &mut Self::CommittedInstance, - incomming_instances: &[Self::CommittedInstance], - transcript: &mut impl Transcript, + vp: Self::VerifierParam, + z_0: Vec, // initial state + z_i: Vec, // last state + // number of steps between the initial state and the last state + num_steps: C1::ScalarField, + running_instance: Self::CommittedInstanceWithWitness, + incomming_instance: Self::CommittedInstanceWithWitness, + cyclefold_instance: Self::CFCommittedInstanceWithWitness, ) -> Result<(), Error>; } diff --git a/src/transcript/poseidon.rs b/src/transcript/poseidon.rs index 7d57adb..45431b5 100644 --- a/src/transcript/poseidon.rs +++ b/src/transcript/poseidon.rs @@ -115,43 +115,41 @@ impl TranscriptVar for PoseidonTranscriptVar { } } +/// WARNING the method poseidon_test_config is for tests only +pub fn poseidon_test_config() -> PoseidonConfig { + let full_rounds = 8; + let partial_rounds = 31; + let alpha = 5; + let rate = 2; + + let (ark, mds) = ark_crypto_primitives::sponge::poseidon::find_poseidon_ark_and_mds::( + F::MODULUS_BIT_SIZE as u64, + rate, + full_rounds, + partial_rounds, + 0, + ); + + PoseidonConfig::new( + full_rounds as usize, + partial_rounds as usize, + alpha, + mds, + ark, + rate, + 1, + ) +} + #[cfg(test)] pub mod tests { use super::*; - use ark_crypto_primitives::sponge::poseidon::find_poseidon_ark_and_mds; use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, R1CSVar}; use ark_relations::r1cs::ConstraintSystem; use ark_vesta::Projective as E2Projective; use std::ops::Mul; - /// WARNING the method poseidon_test_config is for tests only - #[cfg(test)] - pub fn poseidon_test_config() -> PoseidonConfig { - let full_rounds = 8; - let partial_rounds = 31; - let alpha = 5; - let rate = 2; - - let (ark, mds) = find_poseidon_ark_and_mds::( - F::MODULUS_BIT_SIZE as u64, - rate, - full_rounds, - partial_rounds, - 0, - ); - - PoseidonConfig::new( - full_rounds as usize, - partial_rounds as usize, - alpha, - mds, - ark, - rate, - 1, - ) - } - #[test] fn test_transcript_and_transcriptvar_get_challenge() { // use 'native' transcript diff --git a/src/utils/espresso/sum_check/mod.rs b/src/utils/espresso/sum_check/mod.rs index 08ca033..25c228c 100644 --- a/src/utils/espresso/sum_check/mod.rs +++ b/src/utils/espresso/sum_check/mod.rs @@ -211,7 +211,7 @@ pub mod tests { use ark_poly::MultilinearExtension; use ark_std::test_rng; - use crate::transcript::poseidon::tests::poseidon_test_config; + use crate::transcript::poseidon::poseidon_test_config; use crate::transcript::poseidon::PoseidonTranscript; use crate::transcript::Transcript; use crate::utils::sum_check::SumCheck;