From e1183877e7aed1fe76139b305e07fc0908579a72 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 12 Nov 2024 16:34:02 +0100 Subject: [PATCH] Add NIFSGadgetTrait, implement Mova's NIFSGadget, adapt Nova NIFSGadget into NIFSGadgetTrait (#173) * add new NIFSGadgetTrait * implement Mova's NIFSGadget * refactor Nova NIFSGadget to fit into the new NIFSGadgetTrait * abstract NIFSGadget related tests for all implementors of NIFSGadgetTrait to avoid duplicated code in the tests between the different Nova variants gadget tests * frontends/noir update mimc usage since it has been migrated from noir's std into it's own repo --- examples/full_flow.rs | 2 +- examples/noir_full_flow.rs | 9 +- folding-schemes/src/arith/r1cs/circuits.rs | 4 +- .../src/folding/circuits/decider/mod.rs | 2 +- .../src/folding/circuits/decider/off_chain.rs | 2 +- folding-schemes/src/folding/nova/circuits.rs | 375 ++---------------- folding-schemes/src/folding/nova/decider.rs | 1 - .../src/folding/nova/decider_circuits.rs | 1 + .../src/folding/nova/decider_eth.rs | 4 +- .../src/folding/nova/decider_eth_circuit.rs | 22 +- folding-schemes/src/folding/nova/mod.rs | 6 +- folding-schemes/src/folding/nova/nifs/mod.rs | 188 ++++++++- folding-schemes/src/folding/nova/nifs/nova.rs | 66 ++- .../src/folding/nova/nifs/nova_circuits.rs | 237 +++++++++++ folding-schemes/src/folding/nova/nifs/ova.rs | 33 +- .../src/folding/nova/nifs/ova_circuits.rs | 224 +++++++++++ folding-schemes/src/folding/nova/traits.rs | 2 +- .../src/noir/test_folder/test_mimc/Nargo.toml | 2 +- .../noir/test_folder/test_mimc/src/main.nr | 4 +- .../nova_cyclefold_decider.askama.sol | 3 - 20 files changed, 782 insertions(+), 405 deletions(-) create mode 100644 folding-schemes/src/folding/nova/nifs/nova_circuits.rs create mode 100644 folding-schemes/src/folding/nova/nifs/ova_circuits.rs diff --git a/examples/full_flow.rs b/examples/full_flow.rs index 37291f3..8125c22 100644 --- a/examples/full_flow.rs +++ b/examples/full_flow.rs @@ -78,7 +78,7 @@ impl FCircuit for CubicFCircuit { } fn main() { - let n_steps = 10; + let n_steps = 5; // set the initial state let z_0 = vec![Fr::from(3_u32)]; diff --git a/examples/noir_full_flow.rs b/examples/noir_full_flow.rs index 454639e..4e14483 100644 --- a/examples/noir_full_flow.rs +++ b/examples/noir_full_flow.rs @@ -28,7 +28,7 @@ use folding_schemes::{ Decider, FoldingScheme, }; use frontends::noir::{load_noir_circuit, NoirFCircuit}; -use std::{env, time::Instant}; +use std::time::Instant; use solidity_verifiers::{ evm::{compile_solidity, Evm}, @@ -42,12 +42,7 @@ fn main() { let z_0 = vec![Fr::from(1)]; // initialize the noir fcircuit - let cur_path = env::current_dir().unwrap(); - - let circuit_path = format!( - "{}/frontends/src/noir/test_folder/test_mimc/target/test_mimc.json", - cur_path.to_str().unwrap() - ); + let circuit_path = format!("./frontends/src/noir/test_folder/test_mimc/target/test_mimc.json",); let circuit = load_noir_circuit(circuit_path).unwrap(); let f_circuit = NoirFCircuit { diff --git a/folding-schemes/src/arith/r1cs/circuits.rs b/folding-schemes/src/arith/r1cs/circuits.rs index 192a538..72f2b32 100644 --- a/folding-schemes/src/arith/r1cs/circuits.rs +++ b/folding-schemes/src/arith/r1cs/circuits.rs @@ -121,8 +121,8 @@ pub mod tests { nonnative::uint::NonNativeUintVar, }, nova::{ - circuits::CommittedInstanceVar, decider_eth_circuit::WitnessVar, CommittedInstance, - Witness, + decider_eth_circuit::WitnessVar, nifs::nova_circuits::CommittedInstanceVar, + CommittedInstance, Witness, }, }; use crate::frontend::{ diff --git a/folding-schemes/src/folding/circuits/decider/mod.rs b/folding-schemes/src/folding/circuits/decider/mod.rs index ec63f90..f2513a7 100644 --- a/folding-schemes/src/folding/circuits/decider/mod.rs +++ b/folding-schemes/src/folding/circuits/decider/mod.rs @@ -150,7 +150,7 @@ pub mod tests { use ark_std::UniformRand; use super::*; - use crate::folding::nova::{circuits::CommittedInstanceVar, CommittedInstance}; + use crate::folding::nova::{nifs::nova_circuits::CommittedInstanceVar, CommittedInstance}; use crate::transcript::poseidon::poseidon_canonical_config; // checks that the gadget and native implementations of the challenge computation match diff --git a/folding-schemes/src/folding/circuits/decider/off_chain.rs b/folding-schemes/src/folding/circuits/decider/off_chain.rs index 3bb0905..c0d4ff5 100644 --- a/folding-schemes/src/folding/circuits/decider/off_chain.rs +++ b/folding-schemes/src/folding/circuits/decider/off_chain.rs @@ -28,7 +28,7 @@ use crate::{ nonnative::affine::NonNativeAffineVar, CF1, CF2, }, - nova::{circuits::CommittedInstanceVar, decider_eth_circuit::WitnessVar}, + nova::{decider_eth_circuit::WitnessVar, nifs::nova_circuits::CommittedInstanceVar}, traits::{CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps, WitnessVarOps}, }, }; diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index 6507674..b307b3e 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -1,25 +1,30 @@ /// contains [Nova](https://eprint.iacr.org/2021/370.pdf) related circuits use ark_crypto_primitives::sponge::{ - constraints::{AbsorbGadget, CryptographicSpongeVar}, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, - Absorb, CryptographicSponge, + constraints::CryptographicSpongeVar, + poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, + Absorb, }; use ark_ec::{CurveGroup, Group}; use ark_ff::PrimeField; use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, + alloc::AllocVar, boolean::Boolean, eq::EqGadget, fields::{fp::FpVar, FieldVar}, prelude::CurveVar, - uint8::UInt8, R1CSVar, ToConstraintFieldGadget, }; -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use ark_std::{fmt::Debug, One, Zero}; -use core::{borrow::Borrow, marker::PhantomData}; +use core::marker::PhantomData; -use super::{CommittedInstance, NovaCycleFoldConfig}; +use super::{ + nifs::{ + nova_circuits::{CommittedInstanceVar, NIFSGadget}, + NIFSGadgetTrait, + }, + CommittedInstance, NovaCycleFoldConfig, +}; use crate::folding::circuits::{ cyclefold::{ CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, @@ -28,180 +33,9 @@ use crate::folding::circuits::{ nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, CF1, CF2, }; +use crate::folding::traits::{CommittedInstanceVarOps, Dummy}; use crate::frontend::FCircuit; -use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar}; -use crate::{ - constants::NOVA_N_BITS_RO, - folding::traits::{CommittedInstanceVarOps, Dummy}, -}; - -/// CommittedInstanceVar contains the u, x, cmE and cmW values which are folded on the main Nova -/// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are -/// represented non-natively over the constraint field. -#[derive(Debug, Clone)] -pub struct CommittedInstanceVar { - pub u: FpVar, - pub x: Vec>, - pub cmE: NonNativeAffineVar, - pub cmW: NonNativeAffineVar, -} - -impl AllocVar, CF1> for CommittedInstanceVar -where - C: CurveGroup, -{ - fn new_variable>>( - cs: impl Into>>, - f: impl FnOnce() -> Result, - mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - - let u = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; - let x: Vec> = - Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; - - let cmE = - NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; - let cmW = - NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?; - - Ok(Self { u, x, cmE, cmW }) - }) - } -} - -impl AbsorbGadget for CommittedInstanceVar { - fn to_sponge_bytes(&self) -> Result>, SynthesisError> { - FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) - } - - fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { - Ok([ - vec![self.u.clone()], - self.x.clone(), - self.cmE.to_constraint_field()?, - self.cmW.to_constraint_field()?, - ] - .concat()) - } -} - -impl CommittedInstanceVarOps for CommittedInstanceVar { - type PointVar = NonNativeAffineVar; - - fn get_commitments(&self) -> Vec { - vec![self.cmW.clone(), self.cmE.clone()] - } - - fn get_public_inputs(&self) -> &[FpVar>] { - &self.x - } - - fn enforce_incoming(&self) -> Result<(), SynthesisError> { - let zero = NonNativeUintVar::new_constant(ConstraintSystemRef::None, CF2::::zero())?; - self.cmE.x.enforce_equal_unaligned(&zero)?; - self.cmE.y.enforce_equal_unaligned(&zero)?; - self.u.enforce_equal(&FpVar::one()) - } - - fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { - self.u.enforce_equal(&other.u)?; - self.x.enforce_equal(&other.x) - } -} - -/// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier -/// described in section 4 of [Nova](https://eprint.iacr.org/2021/370.pdf), where the cmE & cmW checks are -/// delegated to the NIFSCycleFoldGadget. -pub struct NIFSGadget { - _c: PhantomData, -} - -impl NIFSGadget { - pub fn fold_committed_instance( - r: FpVar>, - ci1: CommittedInstanceVar, // U_i - ci2: CommittedInstanceVar, // u_i - ) -> Result, SynthesisError> { - Ok(CommittedInstanceVar { - cmE: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, - cmW: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, - // ci3.u = ci1.u + r * ci2.u - u: ci1.u + &r * ci2.u, - // ci3.x = ci1.x + r * ci2.x - x: ci1 - .x - .iter() - .zip(ci2.x) - .map(|(a, b)| a + &r * &b) - .collect::>>>(), - }) - } - - /// Implements the constraints for NIFS.V for u and x, since cm(E) and cm(W) are delegated to - /// the CycleFold circuit. - pub fn verify( - r: FpVar>, - ci1: CommittedInstanceVar, // U_i - ci2: CommittedInstanceVar, // u_i - ci3: CommittedInstanceVar, // U_{i+1} - ) -> Result<(), SynthesisError> { - let ci = Self::fold_committed_instance(r, ci1, ci2)?; - - ci.u.enforce_equal(&ci3.u)?; - ci.x.enforce_equal(&ci3.x)?; - - Ok(()) - } -} - -/// ChallengeGadget computes the RO challenge used for the Nova instances NIFS, it contains a -/// rust-native and a in-circuit compatible versions. -pub struct ChallengeGadget { - _c: PhantomData, - _ci: PhantomData, -} -impl ChallengeGadget -where - ::ScalarField: Absorb, -{ - pub fn get_challenge_native>( - transcript: &mut T, - pp_hash: C::ScalarField, // public params hash - U_i: &CI, - u_i: &CI, - cmT: Option<&C>, - ) -> Vec { - transcript.absorb(&pp_hash); - transcript.absorb(&U_i); - transcript.absorb(&u_i); - // in the Nova case we absorb the cmT, in Ova case we don't since it is not used. - if let Some(cmT_value) = cmT { - transcript.absorb_nonnative(cmT_value); - } - transcript.squeeze_bits(NOVA_N_BITS_RO) - } - - // compatible with the native get_challenge_native - pub fn get_challenge_gadget, S>>( - transcript: &mut T, - pp_hash: FpVar>, // public params hash - U_i_vec: Vec>>, // apready processed input, so we don't have to recompute these values - u_i: CommittedInstanceVar, - cmT: Option>, - ) -> Result>, SynthesisError> { - transcript.absorb(&pp_hash)?; - transcript.absorb(&U_i_vec)?; - transcript.absorb(&u_i)?; - // in the Nova case we absorb the cmT, in Ova case we don't since it is not used. - if let Some(cmT_value) = cmT { - transcript.absorb_nonnative(&cmT_value)?; - } - transcript.squeeze_bits(NOVA_N_BITS_RO) - } -} +use crate::transcript::AbsorbNonNativeGadget; /// `AugmentedFCircuit` enhances the original step function `F`, so that it can /// be used in recursive arguments such as IVC. @@ -364,32 +198,33 @@ where x: vec![u_i_x, cf_u_i_x], }; - // P.3. nifs.verify, obtains U_{i+1} by folding u_i & U_i . - - // compute r = H(u_i, U_i, cmT) - let r_bits = ChallengeGadget::>::get_challenge_gadget( + // P.3. nifs.verify, obtains U_{i+1} by folding u_i & U_i. + // Notice that NIFSGadget::verify does not fold cmE & cmW. + // We set `U_i1.cmE` and `U_i1.cmW` to unconstrained witnesses `U_i1_cmE` and `U_i1_cmW` + // respectively. + // The correctness of them will be checked on the other curve. + let (mut U_i1, r_bits) = NIFSGadget::< + C1, + PoseidonSponge, + PoseidonSpongeVar, + >::verify( &mut transcript, pp_hash.clone(), + U_i.clone(), U_i_vec, u_i.clone(), Some(cmT.clone()), )?; - let r = Boolean::le_bits_to_fp_var(&r_bits)?; - // Also convert r_bits to a `NonNativeFieldVar` + U_i1.cmE = U_i1_cmE; + U_i1.cmW = U_i1_cmW; + + // convert r_bits to a `NonNativeFieldVar` let r_nonnat = { let mut bits = r_bits; bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); NonNativeUintVar::from(&bits) }; - // Notice that NIFSGadget::fold_committed_instance does not fold cmE & cmW. - // We set `U_i1.cmE` and `U_i1.cmW` to unconstrained witnesses `U_i1_cmE` and `U_i1_cmW` - // respectively. - // The correctness of them will be checked on the other curve. - let mut U_i1 = NIFSGadget::::fold_committed_instance(r, U_i.clone(), u_i.clone())?; - U_i1.cmE = U_i1_cmE; - U_i1.cmW = U_i1_cmW; - // P.4.a compute and check the first output of F' // get z_{i+1} from the F circuit @@ -507,160 +342,14 @@ where pub mod tests { use super::*; use ark_bn254::{Fr, G1Projective as Projective}; - use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; use ark_ff::BigInteger; use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; - use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::nifs::{nova::NIFS, NIFSTrait}; - use crate::folding::traits::CommittedInstanceOps; + use crate::folding::nova::nifs::nova::ChallengeGadget; use crate::transcript::poseidon::poseidon_canonical_config; - #[test] - fn test_committed_instance_var() { - let mut rng = ark_std::test_rng(); - - let ci = CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }; - - let cs = ConstraintSystem::::new_ref(); - let ciVar = - CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci.clone())).unwrap(); - assert_eq!(ciVar.u.value().unwrap(), ci.u); - assert_eq!(ciVar.x.value().unwrap(), ci.x); - // the values cmE and cmW are checked in the CycleFold's circuit - // CommittedInstanceInCycleFoldVar in - // cyclefold::tests::test_committed_instance_cyclefold_var - } - - #[test] - fn test_nifs_gadget() { - let mut rng = ark_std::test_rng(); - - // prepare the committed instances to test in-circuit - let ci: Vec> = (0..2) - .into_iter() - .map(|_| CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }) - .collect(); - let (ci1, ci2) = (ci[0].clone(), ci[1].clone()); - let pp_hash = Fr::rand(&mut rng); - let cmT = Projective::rand(&mut rng); - let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); - let (ci3, r_bits) = NIFS::, PoseidonSponge>::verify( - &mut transcript, - pp_hash, - &ci1, - &ci2, - &cmT, - ) - .unwrap(); - let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); - - let cs = ConstraintSystem::::new_ref(); - - let rVar = FpVar::::new_witness(cs.clone(), || Ok(r_Fr)).unwrap(); - let ci1Var = - CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci1.clone())) - .unwrap(); - let ci2Var = - CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci2.clone())) - .unwrap(); - let ci3Var = - CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci3.clone())) - .unwrap(); - - NIFSGadget::::verify( - rVar.clone(), - ci1Var.clone(), - ci2Var.clone(), - ci3Var.clone(), - ) - .unwrap(); - assert!(cs.is_satisfied().unwrap()); - } - - /// test that checks the native CommittedInstance.to_sponge_{bytes,field_elements} - /// vs the R1CS constraints version - #[test] - pub fn test_committed_instance_to_sponge_preimage() { - let mut rng = ark_std::test_rng(); - - let ci = CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }; - - let bytes = ci.to_sponge_bytes_as_vec(); - let field_elements = ci.to_sponge_field_elements_as_vec(); - - let cs = ConstraintSystem::::new_ref(); - - let ciVar = - CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci.clone())).unwrap(); - let bytes_var = ciVar.to_sponge_bytes().unwrap(); - let field_elements_var = ciVar.to_sponge_field_elements().unwrap(); - - assert!(cs.is_satisfied().unwrap()); - - // check that the natively computed and in-circuit computed hashes match - assert_eq!(bytes_var.value().unwrap(), bytes); - assert_eq!(field_elements_var.value().unwrap(), field_elements); - } - - #[test] - fn test_committed_instance_hash() { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - let sponge = PoseidonSponge::::new(&poseidon_config); - let pp_hash = Fr::from(42u32); // only for test - - let i = Fr::from(3_u32); - let z_0 = vec![Fr::from(3_u32)]; - let z_i = vec![Fr::from(3_u32)]; - let ci = CommittedInstance:: { - cmE: Projective::rand(&mut rng), - u: Fr::rand(&mut rng), - cmW: Projective::rand(&mut rng), - x: vec![Fr::rand(&mut rng); 1], - }; - - // compute the CommittedInstance hash natively - let h = ci.hash(&sponge, pp_hash, i, &z_0, &z_i); - - let cs = ConstraintSystem::::new_ref(); - - let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash)).unwrap(); - let iVar = FpVar::::new_witness(cs.clone(), || Ok(i)).unwrap(); - let z_0Var = Vec::>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap(); - let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap(); - let ciVar = - CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci.clone())).unwrap(); - - let sponge = PoseidonSpongeVar::::new(cs.clone(), &poseidon_config); - - // compute the CommittedInstance hash in-circuit - let (hVar, _) = ciVar - .hash(&sponge, &pp_hashVar, &iVar, &z_0Var, &z_iVar) - .unwrap(); - assert!(cs.is_satisfied().unwrap()); - - // check that the natively computed and in-circuit computed hashes match - assert_eq!(hVar.value().unwrap(), h); - } - // checks that the gadget and native implementations of the challenge computation match #[test] fn test_challenge_gadget() { diff --git a/folding-schemes/src/folding/nova/decider.rs b/folding-schemes/src/folding/nova/decider.rs index d651091..1f5e009 100644 --- a/folding-schemes/src/folding/nova/decider.rs +++ b/folding-schemes/src/folding/nova/decider.rs @@ -288,7 +288,6 @@ where &proof.cs1_challenges, &proof.cs1_proofs.iter().map(|p| p.eval).collect::>(), &proof.cmT.inputize(), - &[proof.r], ] .concat(); diff --git a/folding-schemes/src/folding/nova/decider_circuits.rs b/folding-schemes/src/folding/nova/decider_circuits.rs index 82e5411..cba00ed 100644 --- a/folding-schemes/src/folding/nova/decider_circuits.rs +++ b/folding-schemes/src/folding/nova/decider_circuits.rs @@ -55,6 +55,7 @@ impl< > TryFrom> for DeciderCircuit1 where CF1: Absorb, + ::BaseField: PrimeField, { type Error = Error; diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index 55e3a4d..e765ed3 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -22,8 +22,7 @@ use crate::commitment::{ pedersen::Params as PedersenParams, CommitmentScheme, }; -use crate::folding::circuits::decider::DeciderEnabledNIFS; -use crate::folding::circuits::CF2; +use crate::folding::circuits::{decider::DeciderEnabledNIFS, CF2}; use crate::folding::traits::{Inputize, WitnessOps}; use crate::frontend::FCircuit; use crate::Error; @@ -228,7 +227,6 @@ where &proof.kzg_challenges, &proof.kzg_proofs.iter().map(|p| p.eval).collect::>(), &proof.cmT.inputize(), - &[proof.r], ] .concat(); diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index a2b2a3e..5a1908f 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -11,8 +11,6 @@ use ark_ec::CurveGroup; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, - boolean::Boolean, - eq::EqGadget, fields::fp::FpVar, prelude::CurveVar, ToConstraintFieldGadget, @@ -21,8 +19,8 @@ use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_std::{borrow::Borrow, marker::PhantomData}; use super::{ - circuits::{ChallengeGadget, CommittedInstanceVar, NIFSGadget}, - nifs::{nova::NIFS, NIFSTrait}, + nifs::nova_circuits::{CommittedInstanceVar, NIFSGadget}, + nifs::{nova::NIFS, NIFSGadgetTrait, NIFSTrait}, CommittedInstance, Nova, Witness, }; use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; @@ -108,6 +106,7 @@ impl< > TryFrom> for DeciderEthCircuit where CF1: Absorb, + ::BaseField: PrimeField, { type Error = Error; @@ -187,21 +186,12 @@ where U_vec: Vec>>, u: CommittedInstanceVar, proof: C, - randomness: CF1, + _randomness: CF1, ) -> Result, SynthesisError> { let cs = transcript.cs(); let cmT = NonNativeAffineVar::new_input(cs.clone(), || Ok(proof))?; - let r = FpVar::new_input(cs.clone(), || Ok(randomness))?; - let r_bits = ChallengeGadget::>::get_challenge_gadget( - transcript, - pp_hash, - U_vec, - u.clone(), - Some(cmT), - )?; - Boolean::le_bits_to_fp_var(&r_bits)?.enforce_equal(&r)?; - - NIFSGadget::::fold_committed_instance(r, U, u) + let (new_U, _) = NIFSGadget::verify(transcript, pp_hash, U, U_vec, u, Some(cmT))?; + Ok(new_U) } fn fold_group_elements_native( diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index ae358ee..ea24dbc 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -17,7 +17,6 @@ use ark_std::fmt::Debug; use ark_std::rand::RngCore; use ark_std::{One, UniformRand, Zero}; use core::marker::PhantomData; -use decider_eth_circuit::WitnessVar; use crate::folding::circuits::cyclefold::{ fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig, @@ -38,6 +37,7 @@ use crate::{ utils::{get_cm_coordinates, pp_hash}, }; use crate::{arith::Arith, commitment::CommitmentScheme}; +use decider_eth_circuit::WitnessVar; pub mod circuits; pub mod traits; @@ -46,8 +46,8 @@ pub mod zk; // NIFS related: pub mod nifs; -use circuits::{AugmentedFCircuit, CommittedInstanceVar}; -use nifs::{nova::NIFS, NIFSTrait}; +use circuits::AugmentedFCircuit; +use nifs::{nova::NIFS, nova_circuits::CommittedInstanceVar, NIFSTrait}; // offchain decider pub mod decider; diff --git a/folding-schemes/src/folding/nova/nifs/mod.rs b/folding-schemes/src/folding/nova/nifs/mod.rs index 98e40cf..e233e3d 100644 --- a/folding-schemes/src/folding/nova/nifs/mod.rs +++ b/folding-schemes/src/folding/nova/nifs/mod.rs @@ -1,18 +1,28 @@ -/// This module defines the NIFSTrait, which is set to implement the NIFS (Non-Interactive Folding -/// Scheme) by the various schemes (Nova, Mova, Ova). -use ark_crypto_primitives::sponge::Absorb; +/// This module defines the traits related to the NIFS (Non-Interactive Folding Scheme). +/// - NIFSTrait, which implements the NIFS interface +/// - NIFSGadget, which implements the NIFS in-circuit +/// both traits implemented by the various Nova variants schemes; ie. +/// [Nova](https://eprint.iacr.org/2021/370.pdf), [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw), +/// [Mova](https://eprint.iacr.org/2024/1220.pdf). +use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, Absorb, CryptographicSponge}; use ark_ec::CurveGroup; +use ark_r1cs_std::{alloc::AllocVar, boolean::Boolean, fields::fp::FpVar}; +use ark_relations::r1cs::SynthesisError; use ark_std::fmt::Debug; use ark_std::rand::RngCore; use crate::arith::r1cs::R1CS; use crate::commitment::CommitmentScheme; -use crate::transcript::Transcript; +use crate::folding::circuits::CF1; +use crate::folding::traits::{CommittedInstanceOps, CommittedInstanceVarOps}; +use crate::transcript::{Transcript, TranscriptVar}; use crate::Error; pub mod mova; pub mod nova; +pub mod nova_circuits; pub mod ova; +pub mod ova_circuits; pub mod pointvsline; /// Defines the NIFS (Non-Interactive Folding Scheme) trait, initially defined in @@ -27,7 +37,7 @@ pub trait NIFSTrait< const H: bool = false, > { - type CommittedInstance: Debug + Clone + Absorb; + type CommittedInstance: Debug + Clone + Absorb; // + CommittedInstanceOps; type Witness: Debug + Clone; type ProverAux: Debug + Clone; // Prover's aux params. eg. in Nova is T type Proof: Debug + Clone; // proof. eg. in Nova is cmT @@ -83,17 +93,56 @@ pub trait NIFSTrait< ) -> Result<(Self::CommittedInstance, Vec), Error>; } +/// Defines the NIFS (Non-Interactive Folding Scheme) Gadget trait, which specifies the in-circuit +/// logic of the NIFS.Verify defined in [Nova](https://eprint.iacr.org/2021/370.pdf) and it's +/// variants [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) and +/// [Mova](https://eprint.iacr.org/2024/1220.pdf). +pub trait NIFSGadgetTrait, S>> { + type CommittedInstance: Debug + Clone + Absorb + CommittedInstanceOps; + type CommittedInstanceVar: Debug + + Clone + + AbsorbGadget + + AllocVar> + + CommittedInstanceVarOps; + type Proof: Debug + Clone; + type ProofVar: Debug + Clone + AllocVar>; + + /// Implements the constraints for NIFS.V for u and x, since cm(E) and cm(W) are delegated to + /// the CycleFold circuit. + #[allow(clippy::type_complexity)] + fn verify( + transcript: &mut T, + pp_hash: FpVar>, + U_i: Self::CommittedInstanceVar, + // U_i_vec is passed to reuse the already computed U_i_vec from previous methods + U_i_vec: Vec>>, + u_i: Self::CommittedInstanceVar, + proof: Option, + ) -> Result<(Self::CommittedInstanceVar, Vec>>), SynthesisError>; +} + +/// These tests are the generic tests so that in the tests of Nova, Mova, Ova, we just need to +/// instantiate these tests to test both the NIFSTrait and NIFSGadgetTrait implementations for each +/// of the schemes. #[cfg(test)] pub mod tests { - use super::*; - use crate::transcript::poseidon::poseidon_canonical_config; + use ark_crypto_primitives::sponge::{ + constraints::{AbsorbGadget, CryptographicSpongeVar}, + poseidon::constraints::PoseidonSpongeVar, + Absorb, + }; use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; use ark_pallas::{Fr, Projective}; + use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar}; + use ark_relations::r1cs::ConstraintSystem; use ark_std::{test_rng, UniformRand}; use super::NIFSTrait; + use super::*; use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z}; use crate::commitment::pedersen::Pedersen; + use crate::folding::traits::{CommittedInstanceOps, CommittedInstanceVarOps}; + use crate::transcript::poseidon::poseidon_canonical_config; /// Test method used to test the different implementations of the NIFSTrait (ie. Nova, Mova, /// Ova). Runs a loop using the NIFS trait, and returns the last Witness and CommittedInstance @@ -101,9 +150,7 @@ pub mod tests { pub(crate) fn test_nifs_opt< N: NIFSTrait, PoseidonSponge>, >() -> (N::Witness, N::CommittedInstance) { - let r1cs = get_test_r1cs(); - let z = get_test_z(3); - let (w, x) = r1cs.split_z(&z); + let r1cs: R1CS = get_test_r1cs(); let mut rng = ark_std::test_rng(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); @@ -114,6 +161,8 @@ pub mod tests { let pp_hash = Fr::rand(&mut rng); // prepare the running instance + let z = get_test_z(3); + let (w, x) = r1cs.split_z(&z); let mut W_i = N::new_witness(w.clone(), r1cs.A.n_rows, test_rng()); let mut U_i = N::new_instance(&mut rng, &pedersen_params, &W_i, x, vec![]).unwrap(); @@ -149,4 +198,123 @@ pub mod tests { (W_i, U_i) } + + /// Test method used to test the different implementations of the NIFSGadgetTrait (ie. Nova, + /// Mova, Ova). It returns the last Witness and CommittedInstance so that it can be checked at + /// the parent test that their values match. + pub(crate) fn test_nifs_gadget_opt( + ci: Vec, + proof: NG::Proof, + ) -> Result<(NG::CommittedInstance, NG::CommittedInstanceVar), Error> + where + N: NIFSTrait, PoseidonSponge>, + NG: NIFSGadgetTrait< + Projective, + PoseidonSponge, + PoseidonSpongeVar, + CommittedInstance = N::CommittedInstance, // constrain that N::CI==NG::CI + Proof = N::Proof, // constrain that N::Proof==NG::Proof + >, + { + let mut rng = ark_std::test_rng(); + + let (U_i, u_i) = (ci[0].clone(), ci[1].clone()); + let pp_hash = Fr::rand(&mut rng); + let poseidon_config = poseidon_canonical_config::(); + let mut transcript = PoseidonSponge::::new(&poseidon_config); + let (ci3, _) = N::verify(&mut transcript, pp_hash, &U_i, &u_i, &proof)?; + + let cs = ConstraintSystem::::new_ref(); + + let mut transcriptVar = PoseidonSpongeVar::::new(cs.clone(), &poseidon_config); + let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash))?; + let ci1Var = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(U_i.clone()))?; + let ci2Var = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(u_i.clone()))?; + let proofVar = NG::ProofVar::new_witness(cs.clone(), || Ok(proof))?; + + let ci1Var_vec = ci1Var.to_sponge_field_elements()?; + let (out, _) = NG::verify( + &mut transcriptVar, + pp_hashVar, + ci1Var.clone(), + ci1Var_vec, + ci2Var.clone(), + Some(proofVar.clone()), + )?; + assert!(cs.is_satisfied()?); + + // return the NIFS.V and the NIFSGadget.V obtained values, so that they are checked at the + // parent test + Ok((ci3, out)) + } + + /// test that checks the native CommittedInstance.to_sponge_{bytes,field_elements} + /// vs the R1CS constraints version + pub(crate) fn test_committed_instance_to_sponge_preimage_opt(ci: N::CommittedInstance) + where + N: NIFSTrait, PoseidonSponge>, + NG: NIFSGadgetTrait< + Projective, + PoseidonSponge, + PoseidonSpongeVar, + CommittedInstance = N::CommittedInstance, // constrain that N::CI==NG::CI + >, + { + let bytes = ci.to_sponge_bytes_as_vec(); + let field_elements = ci.to_sponge_field_elements_as_vec(); + + let cs = ConstraintSystem::::new_ref(); + + let ciVar = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(ci.clone())).unwrap(); + let bytes_var = ciVar.to_sponge_bytes().unwrap(); + let field_elements_var = ciVar.to_sponge_field_elements().unwrap(); + + assert!(cs.is_satisfied().unwrap()); + + // check that the natively computed and in-circuit computed hashes match + assert_eq!(bytes_var.value().unwrap(), bytes); + assert_eq!(field_elements_var.value().unwrap(), field_elements); + } + + pub(crate) fn test_committed_instance_hash_opt(ci: NG::CommittedInstance) + where + N: NIFSTrait, PoseidonSponge>, + NG: NIFSGadgetTrait< + Projective, + PoseidonSponge, + PoseidonSpongeVar, + CommittedInstance = N::CommittedInstance, // constrain that N::CI==NG::CI + >, + N::CommittedInstance: CommittedInstanceOps, + { + let poseidon_config = poseidon_canonical_config::(); + let sponge = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::from(42u32); // only for test + + let i = Fr::from(3_u32); + let z_0 = vec![Fr::from(3_u32)]; + let z_i = vec![Fr::from(3_u32)]; + + // compute the CommittedInstance hash natively + let h = ci.hash(&sponge, pp_hash, i, &z_0, &z_i); + + let cs = ConstraintSystem::::new_ref(); + + let pp_hashVar = FpVar::::new_witness(cs.clone(), || Ok(pp_hash)).unwrap(); + let iVar = FpVar::::new_witness(cs.clone(), || Ok(i)).unwrap(); + let z_0Var = Vec::>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap(); + let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap(); + let ciVar = NG::CommittedInstanceVar::new_witness(cs.clone(), || Ok(ci.clone())).unwrap(); + + let sponge = PoseidonSpongeVar::::new(cs.clone(), &poseidon_config); + + // compute the CommittedInstance hash in-circuit + let (hVar, _) = ciVar + .hash(&sponge, &pp_hashVar, &iVar, &z_0Var, &z_iVar) + .unwrap(); + assert!(cs.is_satisfied().unwrap()); + + // check that the natively computed and in-circuit computed hashes match + assert_eq!(hVar.value().unwrap(), h); + } } diff --git a/folding-schemes/src/folding/nova/nifs/nova.rs b/folding-schemes/src/folding/nova/nifs/nova.rs index 95aaef8..4b232e8 100644 --- a/folding-schemes/src/folding/nova/nifs/nova.rs +++ b/folding-schemes/src/folding/nova/nifs/nova.rs @@ -1,8 +1,10 @@ /// This module contains the implementation the NIFSTrait for the /// [Nova](https://eprint.iacr.org/2021/370.pdf) NIFS (Non-Interactive Folding Scheme). -use ark_crypto_primitives::sponge::Absorb; +use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, Absorb, CryptographicSponge}; use ark_ec::{CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; +use ark_relations::r1cs::SynthesisError; use ark_std::rand::RngCore; use ark_std::Zero; use std::marker::PhantomData; @@ -10,13 +12,69 @@ use std::marker::PhantomData; use super::NIFSTrait; use crate::arith::r1cs::R1CS; use crate::commitment::CommitmentScheme; -use crate::folding::circuits::cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness}; -use crate::folding::nova::circuits::ChallengeGadget; +use crate::constants::NOVA_N_BITS_RO; +use crate::folding::circuits::{ + cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness}, + nonnative::affine::NonNativeAffineVar, + CF1, +}; use crate::folding::nova::{CommittedInstance, Witness}; -use crate::transcript::Transcript; +use crate::transcript::{Transcript, TranscriptVar}; use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub}; use crate::Error; +/// ChallengeGadget computes the RO challenge used for the Nova instances NIFS, it contains a +/// rust-native and a in-circuit compatible versions. +pub struct ChallengeGadget { + _c: PhantomData, + _ci: PhantomData, +} +impl ChallengeGadget +where + C: CurveGroup, + // ::BaseField: PrimeField, + ::ScalarField: Absorb, +{ + pub fn get_challenge_native>( + transcript: &mut T, + pp_hash: C::ScalarField, // public params hash + U_i: &CI, + u_i: &CI, + cmT: Option<&C>, + ) -> Vec { + transcript.absorb(&pp_hash); + transcript.absorb(&U_i); + transcript.absorb(&u_i); + // in the Nova case we absorb the cmT, in Ova case we don't since it is not used. + if let Some(cmT_value) = cmT { + transcript.absorb_nonnative(cmT_value); + } + transcript.squeeze_bits(NOVA_N_BITS_RO) + } + + // compatible with the native get_challenge_native + pub fn get_challenge_gadget< + S: CryptographicSponge, + T: TranscriptVar, S>, + CIVar: AbsorbGadget>, + >( + transcript: &mut T, + pp_hash: FpVar>, // public params hash + U_i_vec: Vec>>, // apready processed input, so we don't have to recompute these values + u_i: CIVar, + cmT: Option>, + ) -> Result>, SynthesisError> { + transcript.absorb(&pp_hash)?; + transcript.absorb(&U_i_vec)?; + transcript.absorb(&u_i)?; + // in the Nova case we absorb the cmT, in Ova case we don't since it is not used. + if let Some(cmT_value) = cmT { + transcript.absorb_nonnative(&cmT_value)?; + } + transcript.squeeze_bits(NOVA_N_BITS_RO) + } +} + /// Implements the Non-Interactive Folding Scheme described in section 4 of /// [Nova](https://eprint.iacr.org/2021/370.pdf). /// `H` specifies whether the NIFS will use a blinding factor diff --git a/folding-schemes/src/folding/nova/nifs/nova_circuits.rs b/folding-schemes/src/folding/nova/nifs/nova_circuits.rs new file mode 100644 index 0000000..544d087 --- /dev/null +++ b/folding-schemes/src/folding/nova/nifs/nova_circuits.rs @@ -0,0 +1,237 @@ +/// contains [Nova](https://eprint.iacr.org/2021/370.pdf) NIFS related circuits +use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, Absorb, CryptographicSponge}; +use ark_ec::{CurveGroup, Group}; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + boolean::Boolean, + eq::EqGadget, + fields::{fp::FpVar, FieldVar}, + uint8::UInt8, + ToConstraintFieldGadget, +}; +use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::{fmt::Debug, Zero}; +use core::{borrow::Borrow, marker::PhantomData}; + +use super::NIFSGadgetTrait; +use crate::folding::circuits::{ + nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, + CF1, CF2, +}; +use crate::folding::nova::CommittedInstance; +use crate::folding::traits::CommittedInstanceVarOps; +use crate::transcript::TranscriptVar; + +use super::nova::ChallengeGadget; + +/// CommittedInstanceVar contains the u, x, cmE and cmW values which are folded on the main Nova +/// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are +/// represented non-natively over the constraint field. +#[derive(Debug, Clone)] +pub struct CommittedInstanceVar { + pub u: FpVar, + pub x: Vec>, + pub cmE: NonNativeAffineVar, + pub cmW: NonNativeAffineVar, +} + +impl AllocVar, CF1> for CommittedInstanceVar +where + C: CurveGroup, +{ + fn new_variable>>( + cs: impl Into>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|val| { + let cs = cs.into(); + + let u = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; + let x: Vec> = + Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; + + let cmE = + NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; + let cmW = + NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?; + + Ok(Self { u, x, cmE, cmW }) + }) + } +} + +impl AbsorbGadget for CommittedInstanceVar +where + C: CurveGroup, +{ + fn to_sponge_bytes(&self) -> Result>, SynthesisError> { + FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) + } + + fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { + Ok([ + vec![self.u.clone()], + self.x.clone(), + self.cmE.to_constraint_field()?, + self.cmW.to_constraint_field()?, + ] + .concat()) + } +} + +impl CommittedInstanceVarOps for CommittedInstanceVar { + type PointVar = NonNativeAffineVar; + + fn get_commitments(&self) -> Vec { + vec![self.cmW.clone(), self.cmE.clone()] + } + + fn get_public_inputs(&self) -> &[FpVar>] { + &self.x + } + + fn enforce_incoming(&self) -> Result<(), SynthesisError> { + let zero = NonNativeUintVar::new_constant(ConstraintSystemRef::None, CF2::::zero())?; + self.cmE.x.enforce_equal_unaligned(&zero)?; + self.cmE.y.enforce_equal_unaligned(&zero)?; + self.u.enforce_equal(&FpVar::one()) + } + + fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { + self.u.enforce_equal(&other.u)?; + self.x.enforce_equal(&other.x) + } +} + +/// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier +/// described in section 4 of [Nova](https://eprint.iacr.org/2021/370.pdf), where the cmE & cmW checks are +/// delegated to the NIFSCycleFoldGadget. +pub struct NIFSGadget, S>> { + _c: PhantomData, + _s: PhantomData, + _t: PhantomData, +} + +impl NIFSGadgetTrait for NIFSGadget +where + C: CurveGroup, + S: CryptographicSponge, + T: TranscriptVar, S>, + ::ScalarField: Absorb, +{ + type CommittedInstance = CommittedInstance; + type CommittedInstanceVar = CommittedInstanceVar; + type Proof = C; + type ProofVar = NonNativeAffineVar; + + fn verify( + transcript: &mut T, + pp_hash: FpVar>, + U_i: Self::CommittedInstanceVar, + // U_i_vec is passed to reuse the already computed U_i_vec from previous methods + U_i_vec: Vec>>, + u_i: Self::CommittedInstanceVar, + cmT: Option, + ) -> Result<(Self::CommittedInstanceVar, Vec>>), SynthesisError> { + let r_bits = ChallengeGadget::>::get_challenge_gadget( + transcript, + pp_hash.clone(), + U_i_vec, + u_i.clone(), + cmT.clone(), + )?; + let r = Boolean::le_bits_to_fp_var(&r_bits)?; + + Ok(( + Self::CommittedInstanceVar { + cmE: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, + cmW: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, + // ci3.u = U_i.u + r * u_i.u + u: U_i.u + &r * u_i.u, + // ci3.x = U_i.x + r * u_i.x + x: U_i + .x + .iter() + .zip(u_i.x) + .map(|(a, b)| a + &r * &b) + .collect::>>>(), + }, + r_bits, + )) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use ark_crypto_primitives::sponge::poseidon::constraints::PoseidonSpongeVar; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_pallas::{Fr, Projective}; + use ark_r1cs_std::R1CSVar; + use ark_std::UniformRand; + + use crate::commitment::pedersen::Pedersen; + use crate::folding::nova::nifs::{ + nova::NIFS, + tests::{ + test_committed_instance_hash_opt, test_committed_instance_to_sponge_preimage_opt, + test_nifs_gadget_opt, + }, + }; + + #[test] + fn test_nifs_gadget() { + let mut rng = ark_std::test_rng(); + // prepare the committed instances to test in-circuit + let ci: Vec> = (0..2) + .into_iter() + .map(|_| CommittedInstance:: { + cmE: Projective::rand(&mut rng), + u: Fr::rand(&mut rng), + cmW: Projective::rand(&mut rng), + x: vec![Fr::rand(&mut rng); 1], + }) + .collect(); + let cmT = Projective::rand(&mut rng); + + let (ci_out, ciVar_out) = test_nifs_gadget_opt::< + NIFS, PoseidonSponge>, + NIFSGadget, PoseidonSpongeVar>, + >(ci, cmT) + .unwrap(); + assert_eq!(ciVar_out.u.value().unwrap(), ci_out.u); + assert_eq!(ciVar_out.x.value().unwrap(), ci_out.x); + } + + #[test] + fn test_committed_instance_to_sponge_preimage() { + let mut rng = ark_std::test_rng(); + let ci = CommittedInstance:: { + cmE: Projective::rand(&mut rng), + u: Fr::rand(&mut rng), + cmW: Projective::rand(&mut rng), + x: vec![Fr::rand(&mut rng); 1], + }; + + test_committed_instance_to_sponge_preimage_opt::< + NIFS, PoseidonSponge>, + NIFSGadget, PoseidonSpongeVar>, + >(ci); + } + + #[test] + fn test_committed_instance_hash() { + let mut rng = ark_std::test_rng(); + let ci = CommittedInstance:: { + cmE: Projective::rand(&mut rng), + u: Fr::rand(&mut rng), + cmW: Projective::rand(&mut rng), + x: vec![Fr::rand(&mut rng); 1], + }; + test_committed_instance_hash_opt::< + NIFS, PoseidonSponge>, + NIFSGadget, PoseidonSpongeVar>, + >(ci); + } +} diff --git a/folding-schemes/src/folding/nova/nifs/ova.rs b/folding-schemes/src/folding/nova/nifs/ova.rs index f45df1c..b15e4e4 100644 --- a/folding-schemes/src/folding/nova/nifs/ova.rs +++ b/folding-schemes/src/folding/nova/nifs/ova.rs @@ -9,10 +9,12 @@ use ark_std::rand::RngCore; use ark_std::{One, UniformRand, Zero}; use std::marker::PhantomData; +use super::nova::ChallengeGadget; +use super::ova_circuits::CommittedInstanceVar; use super::NIFSTrait; use crate::arith::r1cs::R1CS; use crate::commitment::CommitmentScheme; -use crate::folding::nova::circuits::ChallengeGadget; +use crate::folding::traits::{CommittedInstanceOps, Inputize}; use crate::folding::{circuits::CF1, traits::Dummy}; use crate::transcript::{AbsorbNonNative, Transcript}; use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub}; @@ -50,6 +52,24 @@ where } } +impl CommittedInstanceOps for CommittedInstance { + type Var = CommittedInstanceVar; + + fn get_commitments(&self) -> Vec { + vec![self.cmWE] + } + + fn is_incoming(&self) -> bool { + self.u == One::one() + } +} + +impl Inputize> for CommittedInstance { + fn inputize(&self) -> Vec { + [&[self.u][..], &self.x, &self.cmWE.inputize()].concat() + } +} + /// A Witness in Ova is represented by `w`. It also contains a blinder which can or not be used /// when committing to the witness itself. #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] @@ -121,7 +141,9 @@ where type CommittedInstance = CommittedInstance; type Witness = Witness; type ProverAux = (); - type Proof = (); + // Proof is unused, but set to C::ScalarField so that the NIFSGadgetTrait abstraction can + // define the ProofsVar implementing the AllocVar from Proof + type Proof = C::ScalarField; fn new_witness(w: Vec, _e_len: usize, rng: impl RngCore) -> Self::Witness { Witness::new::(w, rng) @@ -182,11 +204,12 @@ where let w = Self::fold_witness(r_Fr, W_i, w_i, &())?; - let (ci, _r_bits_v) = Self::verify(&mut transcript_v, pp_hash, U_i, u_i, &())?; + let proof = C::ScalarField::zero(); + let (ci, _r_bits_v) = Self::verify(&mut transcript_v, pp_hash, U_i, u_i, &proof)?; #[cfg(test)] assert_eq!(_r_bits_v, r_bits); - Ok((w, ci, (), r_bits)) + Ok((w, ci, proof, r_bits)) } fn verify( @@ -203,7 +226,7 @@ where .ok_or(Error::OutOfBounds)?; // recall that r=alpha, and u=mu between Nova and Ova respectively - let u = U_i.u + r; // u_i.u is always 1 IN ova as we just can do sequential IVC. + let u = U_i.u + r; // u_i.u is always 1 in Ova as we just can do IVC (not PCD). let cmWE = U_i.cmWE + u_i.cmWE.mul(r); let x = U_i .x diff --git a/folding-schemes/src/folding/nova/nifs/ova_circuits.rs b/folding-schemes/src/folding/nova/nifs/ova_circuits.rs new file mode 100644 index 0000000..b42e6b0 --- /dev/null +++ b/folding-schemes/src/folding/nova/nifs/ova_circuits.rs @@ -0,0 +1,224 @@ +/// contains [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) NIFS related circuits +use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, Absorb, CryptographicSponge}; +use ark_ec::{CurveGroup, Group}; +use ark_ff::PrimeField; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + boolean::Boolean, + eq::EqGadget, + fields::{fp::FpVar, FieldVar}, + uint8::UInt8, + ToConstraintFieldGadget, +}; +use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::fmt::Debug; +use core::{borrow::Borrow, marker::PhantomData}; + +use super::ova::CommittedInstance; +use super::NIFSGadgetTrait; +use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF1}; +use crate::folding::traits::CommittedInstanceVarOps; +use crate::transcript::TranscriptVar; + +use crate::folding::nova::nifs::nova::ChallengeGadget; + +#[derive(Debug, Clone)] +pub struct CommittedInstanceVar { + pub u: FpVar, + pub x: Vec>, + pub cmWE: NonNativeAffineVar, +} + +impl AllocVar, CF1> for CommittedInstanceVar +where + C: CurveGroup, +{ + fn new_variable>>( + cs: impl Into>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|val| { + let cs = cs.into(); + + let u = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; + let x: Vec> = + Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; + + let cmWE = + NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmWE), mode)?; + + Ok(Self { u, x, cmWE }) + }) + } +} + +impl AbsorbGadget for CommittedInstanceVar +where + C: CurveGroup, + ::BaseField: ark_ff::PrimeField, +{ + fn to_sponge_bytes(&self) -> Result>, SynthesisError> { + FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) + } + + fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { + Ok([ + vec![self.u.clone()], + self.x.clone(), + self.cmWE.to_constraint_field()?, + ] + .concat()) + } +} + +impl CommittedInstanceVarOps for CommittedInstanceVar { + type PointVar = NonNativeAffineVar; + + fn get_commitments(&self) -> Vec { + vec![self.cmWE.clone()] + } + + fn get_public_inputs(&self) -> &[FpVar>] { + &self.x + } + + fn enforce_incoming(&self) -> Result<(), SynthesisError> { + self.u.enforce_equal(&FpVar::one()) + } + + fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { + self.u.enforce_equal(&other.u)?; + self.x.enforce_equal(&other.x) + } +} + +/// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier +/// described of the Ova variant, where the cmWE check is delegated to the NIFSCycleFoldGadget. +pub struct NIFSGadget, S>> { + _c: PhantomData, + _s: PhantomData, + _t: PhantomData, +} + +impl NIFSGadgetTrait for NIFSGadget +where + C: CurveGroup, + S: CryptographicSponge, + T: TranscriptVar, S>, + ::BaseField: ark_ff::PrimeField, + + ::ScalarField: Absorb, + ::BaseField: PrimeField, +{ + type CommittedInstance = CommittedInstance; + type CommittedInstanceVar = CommittedInstanceVar; + type Proof = C::ScalarField; + type ProofVar = FpVar; // unused + + fn verify( + transcript: &mut T, + pp_hash: FpVar>, + U_i: Self::CommittedInstanceVar, + // U_i_vec is passed to reuse the already computed U_i_vec from previous methods + U_i_vec: Vec>>, + u_i: Self::CommittedInstanceVar, + _proof: Option, + ) -> Result<(Self::CommittedInstanceVar, Vec>>), SynthesisError> { + let r_bits = ChallengeGadget::>::get_challenge_gadget( + transcript, + pp_hash.clone(), + U_i_vec, + u_i.clone(), + None, + )?; + let r = Boolean::le_bits_to_fp_var(&r_bits)?; + + Ok(( + Self::CommittedInstanceVar { + cmWE: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?, + // ci3.u = U_i.u + r * u_i.u (u_i.u is always 1 in Ova) + u: U_i.u + &r, + // ci3.x = U_i.x + r * u_i.x + x: U_i + .x + .iter() + .zip(u_i.x) + .map(|(a, b)| a + &r * &b) + .collect::>>>(), + }, + r_bits, + )) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use ark_crypto_primitives::sponge::poseidon::constraints::PoseidonSpongeVar; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_pallas::{Fr, Projective}; + use ark_r1cs_std::R1CSVar; + use ark_std::UniformRand; + use ark_std::Zero; + + use crate::commitment::pedersen::Pedersen; + use crate::folding::nova::nifs::{ + ova::NIFS, + tests::{ + test_committed_instance_hash_opt, test_committed_instance_to_sponge_preimage_opt, + test_nifs_gadget_opt, + }, + }; + + #[test] + fn test_nifs_gadget() { + let mut rng = ark_std::test_rng(); + // prepare the committed instances to test in-circuit + let ci: Vec> = (0..2) + .into_iter() + .map(|_| CommittedInstance:: { + u: Fr::rand(&mut rng), + x: vec![Fr::rand(&mut rng); 1], + cmWE: Projective::rand(&mut rng), + }) + .collect(); + + let (ci_out, ciVar_out) = test_nifs_gadget_opt::< + NIFS, PoseidonSponge>, + NIFSGadget, PoseidonSpongeVar>, + >(ci, Fr::zero()) + .unwrap(); + assert_eq!(ciVar_out.u.value().unwrap(), ci_out.u); + assert_eq!(ciVar_out.x.value().unwrap(), ci_out.x); + } + + #[test] + fn test_committed_instance_to_sponge_preimage() { + let mut rng = ark_std::test_rng(); + let ci = CommittedInstance:: { + u: Fr::rand(&mut rng), + x: vec![Fr::rand(&mut rng); 1], + cmWE: Projective::rand(&mut rng), + }; + + test_committed_instance_to_sponge_preimage_opt::< + NIFS, PoseidonSponge>, + NIFSGadget, PoseidonSpongeVar>, + >(ci); + } + + #[test] + fn test_committed_instance_hash() { + let mut rng = ark_std::test_rng(); + let ci = CommittedInstance:: { + u: Fr::rand(&mut rng), + x: vec![Fr::rand(&mut rng); 1], + cmWE: Projective::rand(&mut rng), + }; + test_committed_instance_hash_opt::< + NIFS, PoseidonSponge>, + NIFSGadget, PoseidonSpongeVar>, + >(ci); + } +} diff --git a/folding-schemes/src/folding/nova/traits.rs b/folding-schemes/src/folding/nova/traits.rs index 1df5339..2f13464 100644 --- a/folding-schemes/src/folding/nova/traits.rs +++ b/folding-schemes/src/folding/nova/traits.rs @@ -3,8 +3,8 @@ use ark_r1cs_std::fields::fp::FpVar; use ark_relations::r1cs::SynthesisError; use ark_std::{rand::RngCore, UniformRand}; -use super::circuits::CommittedInstanceVar; use super::decider_eth_circuit::WitnessVar; +use super::nifs::nova_circuits::CommittedInstanceVar; use super::{CommittedInstance, Witness}; use crate::arith::{ r1cs::{circuits::R1CSMatricesVar, R1CS}, diff --git a/frontends/src/noir/test_folder/test_mimc/Nargo.toml b/frontends/src/noir/test_folder/test_mimc/Nargo.toml index f946848..2c19909 100644 --- a/frontends/src/noir/test_folder/test_mimc/Nargo.toml +++ b/frontends/src/noir/test_folder/test_mimc/Nargo.toml @@ -5,4 +5,4 @@ authors = [""] compiler_version = ">=0.30.0" [dependencies] - +mimc = { tag = "v0.1.0", git = "https://github.com/noir-lang/mimc" } diff --git a/frontends/src/noir/test_folder/test_mimc/src/main.nr b/frontends/src/noir/test_folder/test_mimc/src/main.nr index 2670d3d..9956da7 100644 --- a/frontends/src/noir/test_folder/test_mimc/src/main.nr +++ b/frontends/src/noir/test_folder/test_mimc/src/main.nr @@ -1,6 +1,4 @@ -use dep::std; - pub fn main(x: pub [Field; 1]) -> pub Field { - let hash = std::hash::mimc::mimc_bn254(x); + let hash = mimc::mimc_bn254(x); hash } diff --git a/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol b/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol index c9d1d79..e9ae00b 100644 --- a/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol +++ b/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol @@ -137,9 +137,6 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier { public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 }} + 4 + k] = cmT_x_limbs[k]; public_inputs[{{ z_len * 2 + 2 + num_limbs * 5 }} + 4 + k] = cmT_y_limbs[k]; } - - // last element of the groth16 proof's public inputs is `r` - public_inputs[{{ public_inputs_len - 2 }}] = cmT_r[2]; bool success_g16 = this.verifyProof(pA, pB, pC, public_inputs); require(success_g16 == true, "Groth16: verifying proof failed");