diff --git a/examples/circom_full_flow.rs b/examples/circom_full_flow.rs index dccf0d8e..31755c74 100644 --- a/examples/circom_full_flow.rs +++ b/examples/circom_full_flow.rs @@ -84,6 +84,7 @@ fn main() { // prepare the Nova prover & verifier params let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone()); let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); + let pp_hash = nova_params.1.pp_hash().unwrap(); // initialize the folding scheme engine, in our case we use Nova let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); @@ -131,6 +132,7 @@ fn main() { let calldata: Vec = prepare_calldata( function_selector, + pp_hash, nova.i, nova.z_0, nova.z_i, diff --git a/examples/full_flow.rs b/examples/full_flow.rs index bf1f28f3..7b586ea6 100644 --- a/examples/full_flow.rs +++ b/examples/full_flow.rs @@ -101,6 +101,7 @@ fn main() { // prepare the Nova prover & verifier params let nova_preprocess_params = PreprocessorParam::new(poseidon_config.clone(), f_circuit); let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); + let pp_hash = nova_params.1.pp_hash().unwrap(); // initialize the folding scheme engine, in our case we use Nova let mut nova = N::init(&nova_params, f_circuit, z_0).unwrap(); @@ -138,6 +139,7 @@ fn main() { let calldata: Vec = prepare_calldata( function_selector, + pp_hash, nova.i, nova.z_0, nova.z_i, diff --git a/examples/noir_full_flow.rs b/examples/noir_full_flow.rs index 64531098..30f2e89e 100644 --- a/examples/noir_full_flow.rs +++ b/examples/noir_full_flow.rs @@ -74,6 +74,7 @@ fn main() { // prepare the Nova prover & verifier params let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone()); let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); + let pp_hash = nova_params.1.pp_hash().unwrap(); // initialize the folding scheme engine, in our case we use Nova let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); @@ -119,6 +120,7 @@ fn main() { let calldata: Vec = prepare_calldata( function_selector, + pp_hash, nova.i, nova.z_0, nova.z_i, diff --git a/examples/noname_full_flow.rs b/examples/noname_full_flow.rs index 00dccbfb..0a6c0d4a 100644 --- a/examples/noname_full_flow.rs +++ b/examples/noname_full_flow.rs @@ -84,6 +84,7 @@ fn main() { // prepare the Nova prover & verifier params let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone()); let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); + let pp_hash = nova_params.1.pp_hash().unwrap(); // initialize the folding scheme engine, in our case we use Nova let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); @@ -131,6 +132,7 @@ fn main() { let calldata: Vec = prepare_calldata( function_selector, + pp_hash, nova.i, nova.z_0, nova.z_i, diff --git a/folding-schemes/src/folding/circuits/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs index 91a87877..a6632877 100644 --- a/folding-schemes/src/folding/circuits/cyclefold.rs +++ b/folding-schemes/src/folding/circuits/cyclefold.rs @@ -24,9 +24,10 @@ use super::{nonnative::uint::NonNativeUintVar, CF1, CF2}; use crate::arith::r1cs::{extract_w_x, R1CS}; use crate::commitment::CommitmentScheme; use crate::constants::NOVA_N_BITS_RO; -use crate::folding::nova::{nifs::NIFS, traits::NIFSTrait}; +use crate::folding::nova::nifs::{nova::NIFS, NIFSTrait}; use crate::transcript::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar}; use crate::Error; +use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; /// Re-export the Nova committed instance as `CycleFoldCommittedInstance` and /// witness as `CycleFoldWitness`, for clarity and consistency @@ -493,6 +494,72 @@ where } } +/// CycleFoldNIFS is a wrapper on top of Nova's NIFS, which just replaces the `prove` and `verify` +/// methods to use a different ChallengeGadget, but internally reuses the other Nova's NIFS +/// methods. +/// It is a custom implementation that does not follow the NIFSTrait because it needs to work over +/// different fields than the main NIFS impls (Nova, Mova, Ova). Could be abstracted, but it's a +/// tradeoff between overcomplexity at the NIFSTrait and the (not much) need of generalization at +/// the CycleFoldNIFS. +pub struct CycleFoldNIFS< + C1: CurveGroup, + C2: CurveGroup, + GC2: CurveVar> + ToConstraintFieldGadget>, + CS2: CommitmentScheme, + const H: bool = false, +> where + ::BaseField: PrimeField, + ::BaseField: PrimeField, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + _c1: PhantomData, + _c2: PhantomData, + _gc2: PhantomData, + _cs: PhantomData, +} +impl, const H: bool> + CycleFoldNIFS +where + ::BaseField: PrimeField, + ::BaseField: PrimeField, + ::ScalarField: Absorb, + ::ScalarField: Absorb, + C1: CurveGroup, + GC2: CurveVar> + ToConstraintFieldGadget>, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + fn prove( + cf_r_Fq: C2::ScalarField, // C2::Fr==C1::Fq + cf_W_i: &CycleFoldWitness, + cf_U_i: &CycleFoldCommittedInstance, + cf_w_i: &CycleFoldWitness, + cf_u_i: &CycleFoldCommittedInstance, + aux_p: &[C2::ScalarField], // = cf_T + aux_v: C2, // = cf_cmT + ) -> Result<(CycleFoldWitness, CycleFoldCommittedInstance), Error> { + let w = NIFS::, H>::fold_witness( + cf_r_Fq, + cf_W_i, + cf_w_i, + &aux_p.to_vec(), + )?; + let ci = Self::verify(cf_r_Fq, cf_U_i, cf_u_i, &aux_v)?; + Ok((w, ci)) + } + fn verify( + r: C2::ScalarField, + U_i: &CycleFoldCommittedInstance, + u_i: &CycleFoldCommittedInstance, + cmT: &C2, // VerifierAux + ) -> Result, Error> { + Ok( + NIFS::, H>::fold_committed_instances( + r, U_i, u_i, cmT, + ), + ) + } +} + /// Folds the given cyclefold circuit and its instances. This method is abstracted from any folding /// scheme struct because it is used both by Nova & HyperNova's CycleFold. #[allow(clippy::type_complexity)] @@ -551,14 +618,15 @@ where cf_w_i.commit::(&cf_cs_params, cf_x_i.clone())?; // compute T* and cmT* for CycleFoldCircuit - let (cf_T, cf_cmT) = NIFS::::compute_cyclefold_cmT( - &cf_cs_params, - &cf_r1cs, - &cf_w_i, - &cf_u_i, - &cf_W_i, - &cf_U_i, - )?; + let (cf_T, cf_cmT) = + NIFS::, H>::compute_cyclefold_cmT( + &cf_cs_params, + &cf_r1cs, + &cf_w_i, + &cf_u_i, + &cf_W_i, + &cf_U_i, + )?; let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_native( transcript, @@ -570,8 +638,11 @@ where let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) .expect("cf_r_bits out of bounds"); - let (cf_W_i1, cf_U_i1) = - NIFS::::prove(cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, &cf_cmT)?; + let (cf_W_i1, cf_U_i1) = CycleFoldNIFS::::prove( + cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT, + )?; + let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) + .expect("cf_r_bits out of bounds"); Ok((cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r_Fq)) } @@ -671,6 +742,10 @@ pub mod tests { fn test_nifs_full_gadget() { let mut rng = ark_std::test_rng(); + let poseidon_config = poseidon_canonical_config::(); + let mut transcript_v = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::rand(&mut rng); + // prepare the committed instances to test in-circuit let ci: Vec> = (0..2) .into_iter() @@ -685,11 +760,16 @@ pub mod tests { // make the 2nd instance a 'fresh' instance (ie. cmE=0, u=1) ci2.cmE = Projective::zero(); ci2.u = Fr::one(); - let r_bits: Vec = - Fr::rand(&mut rng).into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); - let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); - let cmT = Projective::rand(&mut rng); - let ci3 = NIFS::>::verify(r_Fr, &ci1, &ci2, &cmT); + + let cmT = Projective::rand(&mut rng); // random only for testing + let (ci3, r_bits) = NIFS::, PoseidonSponge>::verify( + &mut transcript_v, + pp_hash, + &ci1, + &ci2, + &cmT, + ) + .unwrap(); let cs = ConstraintSystem::::new_ref(); let r_bitsVar = Vec::>::new_witness(cs.clone(), || Ok(r_bits)).unwrap(); @@ -737,7 +817,7 @@ pub mod tests { .take(TestCycleFoldConfig::::IO_LEN) .collect(), }; - let cmT = Projective::rand(&mut rng); + let cmT = Projective::rand(&mut rng); // random only for testing // compute the challenge natively let pp_hash = Fq::from(42u32); // only for test diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index b657b61e..b5803a2d 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -529,8 +529,7 @@ pub mod tests { use ark_std::UniformRand; use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::nifs::NIFS; - use crate::folding::nova::traits::NIFSTrait; + use crate::folding::nova::nifs::{nova::NIFS, NIFSTrait}; use crate::folding::traits::CommittedInstanceOps; use crate::transcript::poseidon::poseidon_canonical_config; @@ -570,9 +569,19 @@ pub mod tests { }) .collect(); let (ci1, ci2) = (ci[0].clone(), ci[1].clone()); - let r_Fr = Fr::rand(&mut rng); + let pp_hash = Fr::rand(&mut rng); let cmT = Projective::rand(&mut rng); - let ci3 = NIFS::>::verify(r_Fr, &ci1, &ci2, &cmT); + 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(); diff --git a/folding-schemes/src/folding/nova/decider.rs b/folding-schemes/src/folding/nova/decider.rs index 901f152a..e11757c6 100644 --- a/folding-schemes/src/folding/nova/decider.rs +++ b/folding-schemes/src/folding/nova/decider.rs @@ -2,7 +2,7 @@ /// DeciderEth from decider_eth.rs file. /// More details can be found at the documentation page: /// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html -use ark_crypto_primitives::sponge::Absorb; +use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge}; use ark_ec::{AffineRepr, CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; @@ -13,7 +13,10 @@ use ark_std::{One, Zero}; use core::marker::PhantomData; use super::decider_circuits::{DeciderCircuit1, DeciderCircuit2}; -use super::{nifs::NIFS, traits::NIFSTrait, CommittedInstance, Nova}; +use super::{ + nifs::{nova::NIFS, NIFSTrait}, + CommittedInstance, Nova, +}; use crate::commitment::CommitmentScheme; use crate::folding::circuits::{ cyclefold::CycleFoldCommittedInstance, @@ -21,6 +24,7 @@ use crate::folding::circuits::{ CF2, }; use crate::frontend::FCircuit; +use crate::transcript::poseidon::poseidon_canonical_config; use crate::Error; use crate::{Decider as DeciderTrait, FoldingScheme}; @@ -41,7 +45,6 @@ where // cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are // checked in-circuit cmT: C1, - r: C1::ScalarField, // cyclefold committed instance cf_U_i: CycleFoldCommittedInstance, // the CS challenges are provided by the prover, but in-circuit they are checked to match the @@ -209,7 +212,6 @@ where .map_err(|e| Error::Other(e.to_string()))?; let cmT = circuit1.cmT.unwrap(); - let r_Fr = circuit1.r.unwrap(); let W_i1 = circuit1.W_i1.unwrap(); let cf_W_i = circuit2.cf_W_i.unwrap(); @@ -265,7 +267,6 @@ where cs1_proofs: [U_cmW_proof, U_cmE_proof], cs2_proofs: [cf_cmW_proof, cf_cmE_proof], cmT, - r: r_Fr, cf_U_i: circuit1.cf_U_i.unwrap(), cs1_challenges: [challenge_W, challenge_E], cs2_challenges: [c2_challenge_W, c2_challenge_E], @@ -286,7 +287,17 @@ where } // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT) - let U = NIFS::::verify(proof.r, running_instance, incoming_instance, &proof.cmT); + let poseidon_config = poseidon_canonical_config::(); + let mut transcript = PoseidonSponge::::new(&poseidon_config); + let (U, r_bits) = NIFS::>::verify( + &mut transcript, + vp.pp_hash, + running_instance, + incoming_instance, + &proof.cmT, + )?; + let r = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; let (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?; let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?; @@ -332,7 +343,7 @@ where // NIFS values: cmT_x, cmT_y, - vec![proof.r], + vec![r], ] .concat(); diff --git a/folding-schemes/src/folding/nova/decider_circuits.rs b/folding-schemes/src/folding/nova/decider_circuits.rs index cfd56c1b..d15ccf0a 100644 --- a/folding-schemes/src/folding/nova/decider_circuits.rs +++ b/folding-schemes/src/folding/nova/decider_circuits.rs @@ -28,8 +28,7 @@ use super::{ decider_eth_circuit::{ evaluate_gadget, KZGChallengesGadget, R1CSVar, RelaxedR1CSGadget, WitnessVar, }, - nifs::NIFS, - traits::NIFSTrait, + nifs::{nova::NIFS, NIFSTrait}, CommittedInstance, Nova, Witness, }; use crate::arith::r1cs::R1CS; @@ -114,28 +113,21 @@ where CS2: CommitmentScheme, { let mut transcript = PoseidonSponge::::new(&nova.poseidon_config); - // pp_hash is absorbed to transcript at the ChallengeGadget::get_challenge_native call + // pp_hash is absorbed to transcript at the NIFS::prove call // compute the U_{i+1}, W_{i+1} - let (T, cmT) = NIFS::::compute_cmT( + let (W_i1, U_i1, cmT, r_bits) = NIFS::, H>::prove( &nova.cs_pp, &nova.r1cs.clone(), - &nova.w_i.clone(), - &nova.u_i.clone(), - &nova.W_i.clone(), - &nova.U_i.clone(), - )?; - let r_bits = NIFS::::get_challenge( &mut transcript, nova.pp_hash, + &nova.W_i, &nova.U_i, + &nova.w_i, &nova.u_i, - &cmT, - ); + )?; let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; - let (W_i1, U_i1) = - NIFS::::prove(r_Fr, &nova.W_i, &nova.U_i, &nova.w_i, &nova.u_i, &T, &cmT)?; // compute the commitment scheme challenges used as inputs in the circuit let (cs_challenge_W, cs_challenge_E) = diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index 24d27edb..d17b3fe3 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -3,7 +3,7 @@ /// More details can be found at the documentation page: /// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html use ark_bn254::Bn254; -use ark_crypto_primitives::sponge::Absorb; +use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge}; use ark_ec::{AffineRepr, CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_groth16::Groth16; @@ -15,15 +15,19 @@ use ark_std::{One, Zero}; use core::marker::PhantomData; pub use super::decider_eth_circuit::DeciderEthCircuit; -use super::traits::NIFSTrait; -use super::{nifs::NIFS, CommittedInstance, Nova}; +use super::{ + nifs::{nova::NIFS, NIFSTrait}, + CommittedInstance, Nova, +}; use crate::commitment::{ kzg::{Proof as KZGProof, KZG}, pedersen::Params as PedersenParams, CommitmentScheme, }; use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2}; +use crate::folding::nova::circuits::ChallengeGadget; use crate::frontend::FCircuit; +use crate::transcript::poseidon::poseidon_canonical_config; use crate::Error; use crate::{Decider as DeciderTrait, FoldingScheme}; @@ -39,7 +43,6 @@ where // cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are // checked in-circuit cmT: C1, - r: C1::ScalarField, // the KZG challenges are provided by the prover, but in-circuit they are checked to match // the in-circuit computed computed ones. kzg_challenges: [C1::ScalarField; 2], @@ -161,7 +164,6 @@ where .map_err(|e| Error::Other(e.to_string()))?; let cmT = circuit.cmT.unwrap(); - let r_Fr = circuit.r.unwrap(); let W_i1 = circuit.W_i1.unwrap(); // get the challenges that have been already computed when preparing the circuit inputs in @@ -193,7 +195,6 @@ where snark_proof, kzg_proofs: [U_cmW_proof, U_cmE_proof], cmT, - r: r_Fr, kzg_challenges: [challenge_W, challenge_E], }) } @@ -212,7 +213,17 @@ where } // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT) - let U = NIFS::::verify(proof.r, running_instance, incoming_instance, &proof.cmT); + let poseidon_config = poseidon_canonical_config::(); + let mut transcript = PoseidonSponge::::new(&poseidon_config); + let (U, r_bits) = NIFS::>::verify( + &mut transcript, + vp.pp_hash, + running_instance, + incoming_instance, + &proof.cmT, + )?; + let r = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; let (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?; let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?; @@ -235,7 +246,7 @@ where ], cmT_x, cmT_y, - vec![proof.r], + vec![r], ] .concat(); @@ -264,8 +275,10 @@ where } /// Prepares solidity calldata for calling the NovaDecider contract +#[allow(clippy::too_many_arguments)] pub fn prepare_calldata( function_signature_check: [u8; 4], + pp_hash: ark_bn254::Fr, i: ark_bn254::Fr, z_0: Vec, z_i: Vec, @@ -273,6 +286,22 @@ pub fn prepare_calldata( incoming_instance: &CommittedInstance, proof: Proof, Groth16>, ) -> Result, Error> { + // compute the challenge r + let poseidon_config = poseidon_canonical_config::(); + let mut transcript = PoseidonSponge::::new(&poseidon_config); + let r_bits = ChallengeGadget::< + ark_bn254::G1Projective, + CommittedInstance, + >::get_challenge_native( + &mut transcript, + pp_hash, + running_instance, + incoming_instance, + Some(&proof.cmT), + ); + let r = + ark_bn254::Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).ok_or(Error::OutOfBounds)?; + Ok(vec![ function_signature_check.to_vec(), i.into_bigint().to_bytes_be(), // i @@ -286,7 +315,7 @@ pub fn prepare_calldata( point_to_eth_format(running_instance.cmE.into_affine())?, // U_i_cmE running_instance.u.into_bigint().to_bytes_be(), // U_i_u incoming_instance.u.into_bigint().to_bytes_be(), // u_i_u - proof.r.into_bigint().to_bytes_be(), // r + r.into_bigint().to_bytes_be(), // r running_instance .x .iter() diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index a1dd8091..a8ae6750 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -26,8 +26,7 @@ use core::{borrow::Borrow, marker::PhantomData}; use super::{ circuits::{ChallengeGadget, CommittedInstanceVar}, - nifs::NIFS, - traits::NIFSTrait, + nifs::{nova::NIFS, NIFSTrait}, CommittedInstance, Nova, Witness, }; use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; @@ -246,27 +245,18 @@ where let mut transcript = PoseidonSponge::::new(&nova.poseidon_config); // compute the U_{i+1}, W_{i+1} - let (aux_p, aux_v) = NIFS::::compute_aux( + let (W_i1, U_i1, cmT, r_bits) = NIFS::, H>::prove( &nova.cs_pp, &nova.r1cs.clone(), - &nova.w_i.clone(), - &nova.u_i.clone(), - &nova.W_i.clone(), - &nova.U_i.clone(), - )?; - let cmT = aux_v; - let r_bits = ChallengeGadget::>::get_challenge_native( &mut transcript, nova.pp_hash, + &nova.W_i, &nova.U_i, + &nova.w_i, &nova.u_i, - Some(&cmT), - ); + )?; let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; - let (W_i1, U_i1) = NIFS::::prove( - r_Fr, &nova.W_i, &nova.U_i, &nova.w_i, &nova.u_i, &aux_p, &aux_v, - )?; // compute the KZG challenges used as inputs in the circuit let (kzg_challenge_W, kzg_challenge_E) = diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index d67c33b9..5e701cd0 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -1,5 +1,9 @@ /// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf) and /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf). +/// +/// The structure of the Nova code is the following: +/// - NIFS implementation for Nova (nifs.rs), Mova (mova.rs), Ova (ova.rs) +/// - IVC and the Decider (offchain Decider & onchain Decider) implementations for Nova use ark_crypto_primitives::sponge::{ poseidon::{PoseidonConfig, PoseidonSponge}, Absorb, CryptographicSponge, @@ -36,14 +40,14 @@ use crate::{ use crate::{arith::Arith, commitment::CommitmentScheme}; pub mod circuits; -pub mod nifs; -pub mod ova; pub mod traits; pub mod zk; -use circuits::{AugmentedFCircuit, ChallengeGadget, CommittedInstanceVar}; -use nifs::NIFS; -use traits::NIFSTrait; +// NIFS related: +pub mod nifs; + +use circuits::{AugmentedFCircuit, CommittedInstanceVar}; +use nifs::{nova::NIFS, NIFSTrait}; // offchain decider pub mod decider; @@ -714,28 +718,21 @@ where .F .step_native(i_usize, self.z_i.clone(), external_inputs.clone())?; - // compute T and cmT for AugmentedFCircuit - let (aux_p, aux_v) = self.compute_cmT()?; - let cmT = aux_v; - - // r_bits is the r used to the RLC of the F' instances - let r_bits = ChallengeGadget::>::get_challenge_native( - &mut transcript, - self.pp_hash, - &self.U_i, - &self.u_i, - Some(&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, cmT, r_bits): (Witness, CommittedInstance, C1, Vec) = + NIFS::, H>::prove( + &self.cs_pp, + &self.r1cs, + &mut transcript, + self.pp_hash, + &self.W_i, + &self.U_i, + &self.w_i, + &self.u_i, + )?; let r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; - // fold Nova instances - let (W_i1, U_i1): (Witness, CommittedInstance) = NIFS::::prove( - r_Fr, &self.W_i, &self.U_i, &self.w_i, &self.u_i, &aux_p, &aux_v, - )?; - // folded instance output (public input, x) // u_{i+1}.x[0] = H(i+1, z_0, z_{i+1}, U_{i+1}) let u_i1_x = U_i1.hash( @@ -776,7 +773,15 @@ where }; #[cfg(test)] - NIFS::::verify_folded_instance(r_Fr, &self.U_i, &self.u_i, &U_i1, &cmT)?; + { + let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; + let expected = + NIFS::, H>::fold_committed_instances( + r_Fr, &self.U_i, &self.u_i, &cmT, + ); + assert_eq!(U_i1, expected); + } } else { // CycleFold part: // get the vector used as public inputs 'x' in the CycleFold circuit @@ -1037,33 +1042,6 @@ where } } -impl Nova -where - C1: CurveGroup, - GC1: CurveVar> + ToConstraintFieldGadget>, - C2: CurveGroup, - GC2: CurveVar>, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - ::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_aux( - &self.cs_pp, - &self.r1cs, - &self.w_i, - &self.u_i, - &self.W_i, - &self.U_i, - ) - } -} - impl Nova where C1: CurveGroup, diff --git a/folding-schemes/src/folding/nova/nifs/mod.rs b/folding-schemes/src/folding/nova/nifs/mod.rs new file mode 100644 index 00000000..98e40cf5 --- /dev/null +++ b/folding-schemes/src/folding/nova/nifs/mod.rs @@ -0,0 +1,152 @@ +/// 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; +use ark_ec::CurveGroup; +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::Error; + +pub mod mova; +pub mod nova; +pub mod ova; +pub mod pointvsline; + +/// Defines the NIFS (Non-Interactive Folding Scheme) trait, initially 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). +/// `H` specifies whether the NIFS will use a blinding factor. +pub trait NIFSTrait< + C: CurveGroup, + CS: CommitmentScheme, + T: Transcript, + const H: bool = false, +> +{ + type CommittedInstance: Debug + Clone + Absorb; + 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 + + fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness; + + fn new_instance( + rng: impl RngCore, + params: &CS::ProverParams, + w: &Self::Witness, + x: Vec, + aux: Vec, // t_or_e in Ova, empty for Nova + ) -> Result; + + fn fold_witness( + r: C::ScalarField, + W: &Self::Witness, // running witness + w: &Self::Witness, // incoming witness + aux: &Self::ProverAux, + ) -> Result; + + /// NIFS.P. Returns a tuple containing the folded Witness, the folded CommittedInstance, and + /// the used challenge `r` as a vector of bits, so that it can be reused in other methods. + #[allow(clippy::type_complexity)] + #[allow(clippy::too_many_arguments)] + fn prove( + cs_prover_params: &CS::ProverParams, + r1cs: &R1CS, + transcript: &mut T, + pp_hash: C::ScalarField, + W_i: &Self::Witness, // running witness + U_i: &Self::CommittedInstance, // running committed instance + w_i: &Self::Witness, // incoming witness + u_i: &Self::CommittedInstance, // incoming committed instance + ) -> Result< + ( + Self::Witness, + Self::CommittedInstance, + Self::Proof, + Vec, + ), + Error, + >; + + /// NIFS.V. Returns the folded CommittedInstance and the used challenge `r` as a vector of + /// bits, so that it can be reused in other methods. + fn verify( + transcript: &mut T, + pp_hash: C::ScalarField, + U_i: &Self::CommittedInstance, + u_i: &Self::CommittedInstance, + proof: &Self::Proof, + ) -> Result<(Self::CommittedInstance, Vec), Error>; +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::transcript::poseidon::poseidon_canonical_config; + use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; + use ark_pallas::{Fr, Projective}; + use ark_std::{test_rng, UniformRand}; + + use super::NIFSTrait; + use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z}; + use crate::commitment::pedersen::Pedersen; + + /// 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 + /// so that their relation can be checked. + 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 mut rng = ark_std::test_rng(); + let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); + + let poseidon_config = poseidon_canonical_config::(); + let mut transcript_p = PoseidonSponge::::new(&poseidon_config); + let mut transcript_v = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::rand(&mut rng); + + // prepare the running instance + 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(); + + let num_iters = 10; + for i in 0..num_iters { + // prepare the incoming instance + let incoming_instance_z = get_test_z(i + 4); + let (w, x) = r1cs.split_z(&incoming_instance_z); + let w_i = N::new_witness(w.clone(), r1cs.A.n_rows, test_rng()); + let u_i = N::new_instance(&mut rng, &pedersen_params, &w_i, x, vec![]).unwrap(); + + // NIFS.P + let (folded_witness, _, proof, _) = N::prove( + &pedersen_params, + &r1cs, + &mut transcript_p, + pp_hash, + &W_i, + &U_i, + &w_i, + &u_i, + ) + .unwrap(); + + // NIFS.V + let (folded_committed_instance, _) = + N::verify(&mut transcript_v, pp_hash, &U_i, &u_i, &proof).unwrap(); + + // set running_instance for next loop iteration + W_i = folded_witness; + U_i = folded_committed_instance; + } + + (W_i, U_i) + } +} diff --git a/folding-schemes/src/folding/nova/nifs/mova.rs b/folding-schemes/src/folding/nova/nifs/mova.rs new file mode 100644 index 00000000..6f3353bd --- /dev/null +++ b/folding-schemes/src/folding/nova/nifs/mova.rs @@ -0,0 +1,411 @@ +/// This module contains the implementation the NIFSTrait for the +/// [Mova](https://eprint.iacr.org/2024/1220.pdf) NIFS (Non-Interactive Folding Scheme). +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; +use ark_ff::PrimeField; +use ark_poly::MultilinearExtension; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::log2; +use ark_std::rand::RngCore; +use ark_std::{One, UniformRand, Zero}; +use std::marker::PhantomData; + +use super::{ + nova::NIFS as NovaNIFS, + pointvsline::{PointVsLine, PointVsLineProof, PointvsLineEvaluationClaim}, + NIFSTrait, +}; +use crate::arith::{r1cs::R1CS, Arith}; +use crate::commitment::CommitmentScheme; +use crate::folding::circuits::CF1; +use crate::folding::traits::Dummy; +use crate::transcript::AbsorbNonNative; +use crate::transcript::Transcript; +use crate::utils::{ + mle::dense_vec_to_dense_mle, + vec::{is_zero_vec, vec_add, vec_scalar_mul}, +}; +use crate::Error; + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct CommittedInstance { + // Random evaluation point for the E + pub rE: Vec, + // mleE is the evaluation of the MLE of E at r_E + pub mleE: C::ScalarField, + pub u: C::ScalarField, + pub cmW: C, + pub x: Vec, +} + +impl Absorb for CommittedInstance +where + C::ScalarField: Absorb, +{ + fn to_sponge_bytes(&self, _dest: &mut Vec) { + // This is never called + unimplemented!() + } + + fn to_sponge_field_elements(&self, dest: &mut Vec) { + self.u.to_sponge_field_elements(dest); + self.x.to_sponge_field_elements(dest); + self.rE.to_sponge_field_elements(dest); + self.mleE.to_sponge_field_elements(dest); + // We cannot call `to_native_sponge_field_elements(dest)` directly, as + // `to_native_sponge_field_elements` needs `F` to be `C::ScalarField`, + // but here `F` is a generic `PrimeField`. + self.cmW + .to_native_sponge_field_elements_as_vec() + .to_sponge_field_elements(dest); + } +} + +impl Dummy for CommittedInstance { + fn dummy(io_len: usize) -> Self { + Self { + rE: vec![C::ScalarField::zero(); io_len], + mleE: C::ScalarField::zero(), + u: C::ScalarField::zero(), + cmW: C::zero(), + x: vec![C::ScalarField::zero(); io_len], + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct Witness { + pub E: Vec, + pub W: Vec, + pub rW: C::ScalarField, +} + +impl Dummy<&R1CS> for Witness { + fn dummy(r1cs: &R1CS) -> Self { + Self { + E: vec![C::ScalarField::zero(); r1cs.A.n_rows], + W: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l], + rW: C::ScalarField::zero(), + } + } +} + +impl Witness { + pub fn new(w: Vec, e_len: usize, mut rng: impl RngCore) -> Self { + let rW = if H { + C::ScalarField::rand(&mut rng) + } else { + C::ScalarField::zero() + }; + + Self { + E: vec![C::ScalarField::zero(); e_len], + W: w, + rW, + } + } + + pub fn commit, const H: bool>( + &self, + params: &CS::ProverParams, + x: Vec, + rE: Vec, + ) -> Result, Error> { + let mut mleE = C::ScalarField::zero(); + if !is_zero_vec::(&self.E) { + let E = dense_vec_to_dense_mle(log2(self.E.len()) as usize, &self.E); + mleE = E.evaluate(&rE).ok_or(Error::NotExpectedLength( + rE.len(), + log2(self.E.len()) as usize, + ))?; + } + let cmW = CS::commit(params, &self.W, &self.rW)?; + Ok(CommittedInstance { + rE, + mleE, + u: C::ScalarField::one(), + cmW, + x, + }) + } +} + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct Proof { + pub h_proof: PointVsLineProof, + pub mleE1_prime: C::ScalarField, + pub mleE2_prime: C::ScalarField, + pub mleT: C::ScalarField, +} + +/// Implements the Non-Interactive Folding Scheme described in section 4 of +/// [Mova](https://eprint.iacr.org/2024/1220.pdf). +/// `H` specifies whether the NIFS will use a blinding factor +pub struct NIFS< + C: CurveGroup, + CS: CommitmentScheme, + T: Transcript, + const H: bool = false, +> { + _c: PhantomData, + _cp: PhantomData, + _ct: PhantomData, +} + +impl, T: Transcript, const H: bool> + NIFSTrait for NIFS +where + ::ScalarField: Absorb, + ::BaseField: PrimeField, +{ + type CommittedInstance = CommittedInstance; + type Witness = Witness; + type ProverAux = Vec; // T in Mova's notation + type Proof = Proof; + + fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness { + Witness::new::(w, e_len, rng) + } + + fn new_instance( + mut rng: impl RngCore, + params: &CS::ProverParams, + W: &Self::Witness, + x: Vec, + aux: Vec, // = r_E + ) -> Result { + let mut rE = aux.clone(); + if is_zero_vec(&rE) { + // means that we're in a fresh instance, so generate random value + rE = (0..log2(W.E.len())) + .map(|_| C::ScalarField::rand(&mut rng)) + .collect(); + } + + W.commit::(params, x, rE) + } + + // Protocol 7 - point 3 (16) + fn fold_witness( + a: C::ScalarField, + W_i: &Witness, + w_i: &Witness, + aux: &Vec, // T in Mova's notation + ) -> Result, Error> { + let a2 = a * a; + let E: Vec = vec_add( + &vec_add(&W_i.E, &vec_scalar_mul(aux, &a))?, + &vec_scalar_mul(&w_i.E, &a2), + )?; + let W: Vec = W_i + .W + .iter() + .zip(&w_i.W) + .map(|(i1, i2)| *i1 + (a * i2)) + .collect(); + + let rW = W_i.rW + a * w_i.rW; + Ok(Witness:: { E, W, rW }) + } + + /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. Protocol 8 + /// Returns a proof for the pt-vs-line operations along with the folded committed instance + /// instances and witness + #[allow(clippy::type_complexity)] + fn prove( + _cs_prover_params: &CS::ProverParams, // not used in Mova since we don't commit to T + r1cs: &R1CS, + transcript: &mut T, + pp_hash: C::ScalarField, + W_i: &Witness, + U_i: &CommittedInstance, + w_i: &Witness, + u_i: &CommittedInstance, + ) -> Result< + ( + Self::Witness, + Self::CommittedInstance, + Self::Proof, + Vec, + ), + Error, + > { + transcript.absorb(&pp_hash); + // Protocol 5 is pre-processing + transcript.absorb(U_i); + transcript.absorb(u_i); + + // Protocol 6 + let ( + h_proof, + PointvsLineEvaluationClaim { + mleE1_prime, + mleE2_prime, + rE_prime, + }, + ) = PointVsLine::::prove(transcript, U_i, u_i, W_i, w_i)?; + + // Protocol 7 + + transcript.absorb(&mleE1_prime); + transcript.absorb(&mleE2_prime); + + // compute the cross terms + let z1: Vec = [vec![U_i.u], U_i.x.to_vec(), W_i.W.to_vec()].concat(); + let z2: Vec = [vec![u_i.u], u_i.x.to_vec(), w_i.W.to_vec()].concat(); + let T = NovaNIFS::::compute_T(r1cs, U_i.u, u_i.u, &z1, &z2)?; + + let n_vars: usize = log2(W_i.E.len()) as usize; + if log2(T.len()) as usize != n_vars { + return Err(Error::NotExpectedLength(T.len(), n_vars)); + } + + let mleT = dense_vec_to_dense_mle(n_vars, &T); + let mleT_evaluated = mleT.evaluate(&rE_prime).ok_or(Error::EvaluationFail)?; + + transcript.absorb(&mleT_evaluated); + + let alpha: C::ScalarField = transcript.get_challenge(); + + let ci = Self::fold_committed_instance( + alpha, + U_i, + u_i, + &rE_prime, + &mleE1_prime, + &mleE2_prime, + &mleT_evaluated, + )?; + let w = Self::fold_witness(alpha, W_i, w_i, &T)?; + + let proof = Self::Proof { + h_proof, + mleE1_prime, + mleE2_prime, + mleT: mleT_evaluated, + }; + Ok(( + w, + ci, + proof, + vec![], // r_bits, returned to be passed as inputs to the circuit, not used at the + // current impl status + )) + } + + /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. It verifies the results from the proof + /// Both the folding and the pt-vs-line proof + /// returns the folded committed instance + fn verify( + transcript: &mut T, + pp_hash: C::ScalarField, + U_i: &CommittedInstance, + u_i: &CommittedInstance, + proof: &Proof, + ) -> Result<(Self::CommittedInstance, Vec), Error> { + transcript.absorb(&pp_hash); + transcript.absorb(U_i); + transcript.absorb(u_i); + let rE_prime = PointVsLine::::verify( + transcript, + U_i, + u_i, + &proof.h_proof, + &proof.mleE1_prime, + &proof.mleE2_prime, + )?; + + transcript.absorb(&proof.mleE1_prime); + transcript.absorb(&proof.mleE2_prime); + transcript.absorb(&proof.mleT); + + let alpha: C::ScalarField = transcript.get_challenge(); + + Ok(( + Self::fold_committed_instance( + alpha, + U_i, + u_i, + &rE_prime, + &proof.mleE1_prime, + &proof.mleE2_prime, + &proof.mleT, + )?, + vec![], + )) + } +} + +impl, T: Transcript, const H: bool> + NIFS +{ + // Protocol 7 - point 3 (15) + fn fold_committed_instance( + a: C::ScalarField, + U_i: &CommittedInstance, + u_i: &CommittedInstance, + rE_prime: &[C::ScalarField], + mleE1_prime: &C::ScalarField, + mleE2_prime: &C::ScalarField, + mleT: &C::ScalarField, + ) -> Result, Error> { + let a2 = a * a; + let mleE = *mleE1_prime + a * mleT + a2 * mleE2_prime; + let u = U_i.u + a * u_i.u; + let cmW = U_i.cmW + u_i.cmW.mul(a); + let x = U_i + .x + .iter() + .zip(&u_i.x) + .map(|(i1, i2)| *i1 + (a * i2)) + .collect::>(); + + Ok(CommittedInstance:: { + rE: rE_prime.to_vec(), + mleE, + u, + cmW, + x, + }) + } +} + +impl Arith, CommittedInstance> for R1CS> { + type Evaluation = Vec>; + + fn eval_relation( + &self, + w: &Witness, + u: &CommittedInstance, + ) -> Result { + self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat()) + } + + fn check_evaluation( + w: &Witness, + _u: &CommittedInstance, + e: Self::Evaluation, + ) -> Result<(), Error> { + (w.E == e).then_some(()).ok_or(Error::NotSatisfied) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_pallas::{Fr, Projective}; + + use crate::arith::{r1cs::tests::get_test_r1cs, Arith}; + use crate::commitment::pedersen::Pedersen; + use crate::folding::nova::nifs::tests::test_nifs_opt; + + #[test] + fn test_nifs_mova() { + let (W, U) = test_nifs_opt::, PoseidonSponge>>(); + + // check the last folded instance relation + let r1cs = get_test_r1cs(); + r1cs.check_relation(&W, &U).unwrap(); + } +} diff --git a/folding-schemes/src/folding/nova/nifs.rs b/folding-schemes/src/folding/nova/nifs/nova.rs similarity index 50% rename from folding-schemes/src/folding/nova/nifs.rs rename to folding-schemes/src/folding/nova/nifs/nova.rs index 0a2d807e..0323b505 100644 --- a/folding-schemes/src/folding/nova/nifs.rs +++ b/folding-schemes/src/folding/nova/nifs/nova.rs @@ -1,46 +1,56 @@ +/// 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_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; +use ark_ff::{BigInteger, PrimeField}; use ark_std::rand::RngCore; use ark_std::Zero; use std::marker::PhantomData; -use super::circuits::ChallengeGadget; -use super::traits::NIFSTrait; -use super::{CommittedInstance, Witness}; +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::folding::nova::{CommittedInstance, Witness}; use crate::transcript::Transcript; use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub}; use crate::Error; /// Implements the Non-Interactive Folding Scheme described in section 4 of -/// [Nova](https://eprint.iacr.org/2021/370.pdf) +/// [Nova](https://eprint.iacr.org/2021/370.pdf). /// `H` specifies whether the NIFS will use a blinding factor -pub struct NIFS, const H: bool = false> { +pub struct NIFS< + C: CurveGroup, + CS: CommitmentScheme, + T: Transcript, + const H: bool = false, +> { _c: PhantomData, _cp: PhantomData, + _t: PhantomData, } -impl, const H: bool> NIFSTrait - for NIFS +impl, T: Transcript, const H: bool> + NIFSTrait for NIFS where ::ScalarField: Absorb, ::BaseField: PrimeField, + ::ScalarField: PrimeField, { type CommittedInstance = CommittedInstance; type Witness = Witness; type ProverAux = Vec; - type VerifierAux = C; + type Proof = C; fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness { Witness::new::(w, e_len, rng) } fn new_instance( - W: &Self::Witness, + _rng: impl RngCore, params: &CS::ProverParams, + W: &Self::Witness, x: Vec, _aux: Vec, ) -> Result { @@ -51,7 +61,7 @@ where r: C::ScalarField, W_i: &Self::Witness, w_i: &Self::Witness, - aux: &Self::ProverAux, + aux: &Self::ProverAux, // T in Nova's notation ) -> Result { let r2 = r * r; let E: Vec = vec_add( @@ -72,65 +82,72 @@ where Ok(Self::Witness { E, rE, W, rW }) } - fn compute_aux( + fn prove( cs_prover_params: &CS::ProverParams, r1cs: &R1CS, + transcript: &mut T, + pp_hash: C::ScalarField, W_i: &Self::Witness, U_i: &Self::CommittedInstance, w_i: &Self::Witness, u_i: &Self::CommittedInstance, - ) -> Result<(Self::ProverAux, Self::VerifierAux), Error> { + ) -> Result< + ( + Self::Witness, + Self::CommittedInstance, + Self::Proof, + Vec, + ), + Error, + > { + // compute the cross terms let z1: Vec = [vec![U_i.u], U_i.x.to_vec(), W_i.W.to_vec()].concat(); let z2: Vec = [vec![u_i.u], u_i.x.to_vec(), w_i.W.to_vec()].concat(); - - // compute cross terms let T = Self::compute_T(r1cs, U_i.u, u_i.u, &z1, &z2)?; + // use r_T=0 since we don't need hiding property for cm(T) let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?; - Ok((T, cmT)) - } - fn get_challenge>( - transcript: &mut T, - pp_hash: C::ScalarField, // public params hash - U_i: &Self::CommittedInstance, - u_i: &Self::CommittedInstance, - aux: &Self::VerifierAux, // cmT - ) -> Vec { - ChallengeGadget::::get_challenge_native( + let r_bits = ChallengeGadget::::get_challenge_native( transcript, pp_hash, U_i, u_i, - Some(aux), - ) - } + Some(&cmT), + ); + let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; + + let w = Self::fold_witness(r_Fr, W_i, w_i, &T)?; - // Notice: `prove` method is implemented at the trait level. + let ci = Self::fold_committed_instances(r_Fr, U_i, u_i, &cmT); + + Ok((w, ci, cmT, r_bits)) + } fn verify( - // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element - r: C::ScalarField, + transcript: &mut T, + pp_hash: C::ScalarField, U_i: &Self::CommittedInstance, u_i: &Self::CommittedInstance, - cmT: &C, // VerifierAux - ) -> Self::CommittedInstance { - let r2 = r * r; - let cmE = U_i.cmE + cmT.mul(r) + u_i.cmE.mul(r2); - let u = U_i.u + r * u_i.u; - let cmW = U_i.cmW + u_i.cmW.mul(r); - let x = U_i - .x - .iter() - .zip(&u_i.x) - .map(|(a, b)| *a + (r * b)) - .collect::>(); + cmT: &C, // Proof + ) -> Result<(Self::CommittedInstance, Vec), Error> { + let r_bits = ChallengeGadget::::get_challenge_native( + transcript, + pp_hash, + U_i, + u_i, + Some(cmT), + ); + let r = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; - Self::CommittedInstance { cmE, u, cmW, x } + Ok((Self::fold_committed_instances(r, U_i, u_i, cmT), r_bits)) } } -impl, const H: bool> NIFS +impl, T: Transcript, const H: bool> + NIFS where ::ScalarField: Absorb, ::BaseField: PrimeField, @@ -161,26 +178,6 @@ where vec_sub(&vec_sub(&vec_add(&Az1_Bz2, &Az2_Bz1)?, &u1Cz2)?, &u2Cz1) } - /// In Nova, NIFS.P is the consecutive combination of compute_cmT with fold_instances, - /// ie. compute_cmT is part of the NIFS.P logic. - pub fn compute_cmT( - cs_prover_params: &CS::ProverParams, - r1cs: &R1CS, - w1: &Witness, - ci1: &CommittedInstance, - w2: &Witness, - ci2: &CommittedInstance, - ) -> Result<(Vec, C), Error> { - let z1: Vec = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat(); - let z2: Vec = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat(); - - // compute cross terms - let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?; - // use r_T=0 since we don't need hiding property for cm(T) - let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?; - Ok((T, cmT)) - } - pub fn compute_cyclefold_cmT( cs_prover_params: &CS::ProverParams, r1cs: &R1CS, // R1CS over C2.Fr=C1.Fq (here C=C2) @@ -202,25 +199,26 @@ where Ok((T, cmT)) } - /// Verify committed folded instance (ci) relations. Notice that this method does not open the - /// commitments, but just checks that the given committed instances (ci1, ci2) when folded - /// result in the folded committed instance (ci3) values. - pub fn verify_folded_instance( + /// folds two committed instances with the given r and cmT. This method is used by + /// Nova::verify, but also by Nova::prove and the CycleFoldNIFS::verify. + pub fn fold_committed_instances( r: C::ScalarField, - ci1: &CommittedInstance, - ci2: &CommittedInstance, - ci3: &CommittedInstance, + U_i: &CommittedInstance, + u_i: &CommittedInstance, cmT: &C, - ) -> Result<(), Error> { - let expected = Self::verify(r, ci1, ci2, cmT); - if ci3.cmE != expected.cmE - || ci3.u != expected.u - || ci3.cmW != expected.cmW - || ci3.x != expected.x - { - return Err(Error::NotSatisfied); - } - Ok(()) + ) -> CommittedInstance { + let r2 = r * r; + let cmE = U_i.cmE + cmT.mul(r) + u_i.cmE.mul(r2); + let u = U_i.u + r * u_i.u; + let cmW = U_i.cmW + u_i.cmW.mul(r); + let x = U_i + .x + .iter() + .zip(&u_i.x) + .map(|(a, b)| *a + (r * b)) + .collect::>(); + + CommittedInstance { cmE, u, cmW, x } } pub fn prove_commitments( @@ -241,101 +239,19 @@ where #[cfg(test)] pub mod tests { use super::*; - use crate::transcript::poseidon::poseidon_canonical_config; - use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; - use ark_ff::{BigInteger, PrimeField}; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; use ark_pallas::{Fr, Projective}; - use ark_std::{test_rng, UniformRand}; - use crate::arith::{ - r1cs::tests::{get_test_r1cs, get_test_z}, - Arith, - }; + use crate::arith::{r1cs::tests::get_test_r1cs, Arith}; use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::traits::NIFSTrait; + use crate::folding::nova::nifs::tests::test_nifs_opt; #[test] fn test_nifs_nova() { - let (W, U) = test_nifs_opt::>>(); + let (W, U) = test_nifs_opt::, PoseidonSponge>>(); // check the last folded instance relation let r1cs = get_test_r1cs(); r1cs.check_relation(&W, &U).unwrap(); } - - /// runs a loop using the NIFS trait, and returns the last Witness and CommittedInstance so - /// that their relation can be checked. - pub(crate) fn test_nifs_opt>>( - ) -> (N::Witness, N::CommittedInstance) { - let r1cs = get_test_r1cs(); - let z = get_test_z(3); - let (w, x) = r1cs.split_z(&z); - - let mut rng = ark_std::test_rng(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); - let pp_hash = Fr::rand(&mut rng); - - // prepare the running instance - let mut running_witness = N::new_witness(w.clone(), r1cs.A.n_rows, test_rng()); - let mut running_committed_instance = - N::new_instance(&running_witness, &pedersen_params, x, vec![]).unwrap(); - - let num_iters = 10; - for i in 0..num_iters { - // prepare the incoming instance - let incoming_instance_z = get_test_z(i + 4); - let (w, x) = r1cs.split_z(&incoming_instance_z); - let incoming_witness = N::new_witness(w.clone(), r1cs.A.n_rows, test_rng()); - let incoming_committed_instance = - N::new_instance(&incoming_witness, &pedersen_params, x, vec![]).unwrap(); - - let (aux_p, aux_v) = N::compute_aux( - &pedersen_params, - &r1cs, - &running_witness, - &running_committed_instance, - &incoming_witness, - &incoming_committed_instance, - ) - .unwrap(); - - let r_bits = N::get_challenge( - &mut transcript, - pp_hash, - &running_committed_instance, - &incoming_committed_instance, - &aux_v, - ); - let r = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); - - // NIFS.P - let (folded_witness, _) = N::prove( - r, - &running_witness, - &running_committed_instance, - &incoming_witness, - &incoming_committed_instance, - &aux_p, - &aux_v, - ) - .unwrap(); - - // NIFS.V - let folded_committed_instance = N::verify( - r, - &running_committed_instance, - &incoming_committed_instance, - &aux_v, - ); - - // set running_instance for next loop iteration - running_witness = folded_witness; - running_committed_instance = folded_committed_instance; - } - - (running_witness, running_committed_instance) - } } diff --git a/folding-schemes/src/folding/nova/ova.rs b/folding-schemes/src/folding/nova/nifs/ova.rs similarity index 79% rename from folding-schemes/src/folding/nova/ova.rs rename to folding-schemes/src/folding/nova/nifs/ova.rs index 95bd91ac..f45df1c0 100644 --- a/folding-schemes/src/folding/nova/ova.rs +++ b/folding-schemes/src/folding/nova/nifs/ova.rs @@ -1,19 +1,18 @@ /// This module contains the implementation the NIFSTrait for the -/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) NIFS (Non-Interactive Folding Scheme) as -/// outlined in the protocol description doc: -/// authored by Benedikt Bünz. +/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) NIFS (Non-Interactive Folding Scheme). use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; +use ark_ff::{BigInteger, PrimeField}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::fmt::Debug; use ark_std::rand::RngCore; use ark_std::{One, UniformRand, Zero}; use std::marker::PhantomData; -use super::{circuits::ChallengeGadget, traits::NIFSTrait}; +use super::NIFSTrait; use crate::arith::r1cs::R1CS; use crate::commitment::CommitmentScheme; +use crate::folding::nova::circuits::ChallengeGadget; 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}; @@ -51,7 +50,6 @@ where } } -// #[allow(dead_code)] // Clippy flag needed for now. /// 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)] @@ -103,13 +101,19 @@ impl Dummy<&R1CS>> for Witness { } /// Implements the NIFS (Non-Interactive Folding Scheme) trait for Ova. -pub struct NIFS, const H: bool = false> { +pub struct NIFS< + C: CurveGroup, + CS: CommitmentScheme, + T: Transcript, + const H: bool = false, +> { _c: PhantomData, _cp: PhantomData, + _t: PhantomData, } -impl, const H: bool> NIFSTrait - for NIFS +impl, T: Transcript, const H: bool> + NIFSTrait for NIFS where ::ScalarField: Absorb, ::BaseField: PrimeField, @@ -117,15 +121,16 @@ where type CommittedInstance = CommittedInstance; type Witness = Witness; type ProverAux = (); - type VerifierAux = (); + type Proof = (); fn new_witness(w: Vec, _e_len: usize, rng: impl RngCore) -> Self::Witness { Witness::new::(w, rng) } fn new_instance( - W: &Self::Witness, + _rng: impl RngCore, params: &CS::ProverParams, + W: &Self::Witness, x: Vec, aux: Vec, // t_or_e ) -> Result { @@ -149,40 +154,55 @@ where Ok(Self::Witness { w, rW }) } - fn compute_aux( + fn prove( _cs_prover_params: &CS::ProverParams, _r1cs: &R1CS, - _W_i: &Self::Witness, - _U_i: &Self::CommittedInstance, - _w_i: &Self::Witness, - _u_i: &Self::CommittedInstance, - ) -> Result<(Self::ProverAux, Self::VerifierAux), Error> { - Ok(((), ())) - } - - fn get_challenge>( transcript: &mut T, - pp_hash: C::ScalarField, // public params hash + pp_hash: C::ScalarField, + W_i: &Self::Witness, U_i: &Self::CommittedInstance, + w_i: &Self::Witness, u_i: &Self::CommittedInstance, - _aux: &Self::VerifierAux, - ) -> Vec { - // reuse Nova's get_challenge method - ChallengeGadget::::get_challenge_native( - transcript, pp_hash, U_i, u_i, None, // empty in Ova's case - ) - } + ) -> Result< + ( + Self::Witness, + Self::CommittedInstance, + Self::Proof, + Vec, + ), + Error, + > { + let mut transcript_v = transcript.clone(); - // Notice: `prove` method is implemented at the trait level. + let r_bits = ChallengeGadget::::get_challenge_native( + transcript, pp_hash, U_i, u_i, None, // cmT not used in Ova + ); + let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; + + 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, &())?; + #[cfg(test)] + assert_eq!(_r_bits_v, r_bits); + + Ok((w, ci, (), r_bits)) + } fn verify( - // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element - r: C::ScalarField, + transcript: &mut T, + pp_hash: C::ScalarField, U_i: &Self::CommittedInstance, u_i: &Self::CommittedInstance, - _aux: &Self::VerifierAux, - ) -> Self::CommittedInstance { - // recall that r <==> alpha, and u <==> mu between Nova and Ova respectively + _proof: &Self::Proof, // unused in Ova + ) -> Result<(Self::CommittedInstance, Vec), Error> { + let r_bits = ChallengeGadget::::get_challenge_native( + transcript, pp_hash, U_i, u_i, None, // cmT not used in Ova + ); + let r = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .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 cmWE = U_i.cmWE + u_i.cmWE.mul(r); let x = U_i @@ -192,7 +212,7 @@ where .map(|(a, b)| *a + (r * b)) .collect::>(); - Self::CommittedInstance { cmWE, u, x } + Ok((Self::CommittedInstance { cmWE, u, x }, r_bits)) } } @@ -224,6 +244,7 @@ pub mod tests { use crate::arith::{r1cs::tests::get_test_r1cs, Arith}; use crate::commitment::pedersen::Pedersen; use crate::folding::nova::nifs::tests::test_nifs_opt; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; // Simple auxiliary structure mainly used to help pass a witness for which we can check // easily an R1CS relation. @@ -257,7 +278,7 @@ pub mod tests { #[test] fn test_nifs_ova() { - let (W, U) = test_nifs_opt::>>(); + let (W, U) = test_nifs_opt::, PoseidonSponge>>(); // check the last folded instance relation let r1cs = get_test_r1cs(); diff --git a/folding-schemes/src/folding/nova/nifs/pointvsline.rs b/folding-schemes/src/folding/nova/nifs/pointvsline.rs new file mode 100644 index 00000000..a41c7f64 --- /dev/null +++ b/folding-schemes/src/folding/nova/nifs/pointvsline.rs @@ -0,0 +1,282 @@ +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; +use ark_ff::{One, PrimeField}; +use ark_poly::univariate::DensePolynomial; +use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, Polynomial}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{log2, Zero}; + +use super::mova::{CommittedInstance, Witness}; +use crate::transcript::Transcript; +use crate::utils::mle::dense_vec_to_dense_mle; +use crate::Error; + +/// Implements the Points vs Line as described in +/// [Mova](https://eprint.iacr.org/2024/1220.pdf) and Section 4.5.2 from Thaler’s book + +/// Claim from step 3 protocol 6 +pub struct PointvsLineEvaluationClaim { + pub mleE1_prime: C::ScalarField, + pub mleE2_prime: C::ScalarField, + pub rE_prime: Vec, +} +/// Proof from step 1 protocol 6 +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct PointVsLineProof { + pub h1: DensePolynomial, + pub h2: DensePolynomial, +} + +#[derive(Clone, Debug, Default)] +pub struct PointVsLine> { + _phantom_C: std::marker::PhantomData, + _phantom_T: std::marker::PhantomData, +} + +/// Protocol 6 from Mova +impl> PointVsLine +where + ::ScalarField: Absorb, +{ + pub fn prove( + transcript: &mut T, + ci1: &CommittedInstance, + ci2: &CommittedInstance, + w1: &Witness, + w2: &Witness, + ) -> Result<(PointVsLineProof, PointvsLineEvaluationClaim), Error> { + let n_vars: usize = log2(w1.E.len()) as usize; + + let mleE1 = dense_vec_to_dense_mle(n_vars, &w1.E); + let mleE2 = dense_vec_to_dense_mle(n_vars, &w2.E); + + // We have l(0) = r1, l(1) = r2 so we know that l(x) = r1 + x(r2-r1) thats why we need r2-r1 + let r1_sub_r2: Vec<::ScalarField> = ci1 + .rE + .iter() + .zip(&ci2.rE) + .map(|(&r1, r2)| r1 - r2) + .collect(); + + let h1 = compute_h(&mleE1, &ci1.rE, &r1_sub_r2)?; + let h2 = compute_h(&mleE2, &ci1.rE, &r1_sub_r2)?; + + transcript.absorb(&h1.coeffs()); + transcript.absorb(&h2.coeffs()); + + let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta"); + transcript.absorb(&beta_scalar); + let beta = transcript.get_challenge(); + + let mleE1_prime = h1.evaluate(&beta); + let mleE2_prime = h2.evaluate(&beta); + + let rE_prime = compute_l(&ci1.rE, &r1_sub_r2, beta)?; + + Ok(( + PointVsLineProof { h1, h2 }, + PointvsLineEvaluationClaim { + mleE1_prime, + mleE2_prime, + rE_prime, + }, + )) + } + + pub fn verify( + transcript: &mut T, + ci1: &CommittedInstance, + ci2: &CommittedInstance, + proof: &PointVsLineProof, + mleE1_prime: &::ScalarField, + mleE2_prime: &::ScalarField, + ) -> Result< + Vec<::ScalarField>, // rE=rE1'=rE2'. + Error, + > { + if proof.h1.evaluate(&C::ScalarField::zero()) != ci1.mleE { + return Err(Error::NotEqual); + } + + if proof.h2.evaluate(&C::ScalarField::one()) != ci2.mleE { + return Err(Error::NotEqual); + } + + transcript.absorb(&proof.h1.coeffs()); + transcript.absorb(&proof.h2.coeffs()); + + let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta"); + transcript.absorb(&beta_scalar); + let beta = transcript.get_challenge(); + + if *mleE1_prime != proof.h1.evaluate(&beta) { + return Err(Error::NotEqual); + } + + if *mleE2_prime != proof.h2.evaluate(&beta) { + return Err(Error::NotEqual); + } + + let r1_sub_r2: Vec<::ScalarField> = ci1 + .rE + .iter() + .zip(&ci2.rE) + .map(|(&r1, r2)| r1 - r2) + .collect(); + let rE_prime = compute_l(&ci1.rE, &r1_sub_r2, beta)?; + + Ok(rE_prime) + } +} + +fn compute_h( + mle: &DenseMultilinearExtension, + r1: &[F], + r1_sub_r2: &[F], +) -> Result, Error> { + let n_vars: usize = mle.num_vars; + if r1.len() != r1_sub_r2.len() || r1.len() != n_vars { + return Err(Error::NotEqual); + } + + // Initialize the polynomial vector from the evaluations in the multilinear extension. + // Each evaluation is turned into a constant polynomial. + let mut poly: Vec> = mle + .evaluations + .iter() + .map(|&x| DensePolynomial::from_coefficients_slice(&[x])) + .collect(); + + for (i, (&r1_i, &r1_sub_r2_i)) in r1.iter().zip(r1_sub_r2.iter()).enumerate().take(n_vars) { + // Create a linear polynomial r(X) = r1_i + (r1_sub_r2_i) * X (basically l) + let r = DensePolynomial::from_coefficients_slice(&[r1_i, r1_sub_r2_i]); + let half_len = 1 << (n_vars - i - 1); + + for b in 0..half_len { + let left = &poly[b << 1]; + let right = &poly[(b << 1) + 1]; + poly[b] = left + &(&r * &(right - left)); + } + } + + // After the loop, we should be left with a single polynomial, so return it. + Ok(poly.swap_remove(0)) +} + +fn compute_l(r1: &[F], r1_sub_r2: &[F], x: F) -> Result, Error> { + if r1.len() != r1_sub_r2.len() { + return Err(Error::NotEqual); + } + + // we have l(x) = r1 + x(r2-r1) so return the result + Ok(r1 + .iter() + .zip(r1_sub_r2) + .map(|(&r1, &r1_sub_r0)| r1 + x * r1_sub_r0) + .collect()) +} + +#[cfg(test)] +mod tests { + use super::{compute_h, compute_l}; + use ark_pallas::Fq; + use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial}; + + #[test] + fn test_compute_h() { + let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]); + let r0 = [Fq::from(5)]; + let r1 = [Fq::from(6)]; + let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); + + let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap(); + assert_eq!( + result, + DenseUVPolynomial::from_coefficients_slice(&[Fq::from(6), Fq::from(1)]) + ); + + let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]); + let r0 = [Fq::from(4)]; + let r1 = [Fq::from(7)]; + let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); + + let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap(); + assert_eq!( + result, + DenseUVPolynomial::from_coefficients_slice(&[Fq::from(5), Fq::from(3)]) + ); + + let mle = DenseMultilinearExtension::from_evaluations_slice( + 2, + &[Fq::from(1), Fq::from(2), Fq::from(3), Fq::from(4)], + ); + let r0 = [Fq::from(5), Fq::from(4)]; + let r1 = [Fq::from(2), Fq::from(7)]; + let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); + + let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap(); + assert_eq!( + result, + DenseUVPolynomial::from_coefficients_slice(&[Fq::from(14), Fq::from(3)]) + ); + let mle = DenseMultilinearExtension::from_evaluations_slice( + 3, + &[ + Fq::from(1), + Fq::from(2), + Fq::from(3), + Fq::from(4), + Fq::from(5), + Fq::from(6), + Fq::from(7), + Fq::from(8), + ], + ); + let r0 = [Fq::from(1), Fq::from(2), Fq::from(3)]; + let r1 = [Fq::from(5), Fq::from(6), Fq::from(7)]; + let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); + + let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap(); + assert_eq!( + result, + DenseUVPolynomial::from_coefficients_slice(&[Fq::from(18), Fq::from(28)]) + ); + } + + #[test] + fn test_compute_h_errors() { + let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]); + let r0 = [Fq::from(5)]; + let r1_sub_r0 = []; + let result = compute_h(&mle, &r0, &r1_sub_r0); + assert!(result.is_err()); + + let mle = DenseMultilinearExtension::from_evaluations_slice( + 2, + &[Fq::from(1), Fq::from(2), Fq::from(1), Fq::from(2)], + ); + let r0 = [Fq::from(4)]; + let r1 = [Fq::from(7)]; + let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); + + let result = compute_h(&mle, &r0, &r1_sub_r0); + assert!(result.is_err()) + } + + #[test] + fn test_compute_l() { + // Test with simple non-zero values + let r1 = vec![Fq::from(1), Fq::from(2), Fq::from(3)]; + let r1_sub_r2 = vec![Fq::from(4), Fq::from(5), Fq::from(6)]; + let x = Fq::from(2); + + let expected = vec![ + Fq::from(1) + Fq::from(2) * Fq::from(4), + Fq::from(2) + Fq::from(2) * Fq::from(5), + Fq::from(3) + Fq::from(2) * Fq::from(6), + ]; + + let result = compute_l(&r1, &r1_sub_r2, x).unwrap(); + assert_eq!(result, expected); + } +} diff --git a/folding-schemes/src/folding/nova/traits.rs b/folding-schemes/src/folding/nova/traits.rs index ce325f1a..8bf336ac 100644 --- a/folding-schemes/src/folding/nova/traits.rs +++ b/folding-schemes/src/folding/nova/traits.rs @@ -1,6 +1,4 @@ -use ark_crypto_primitives::sponge::Absorb; use ark_ec::CurveGroup; -use ark_std::fmt::Debug; use ark_std::{rand::RngCore, UniformRand}; use super::{CommittedInstance, Witness}; @@ -8,79 +6,8 @@ use crate::arith::ArithSampler; use crate::arith::{r1cs::R1CS, Arith}; use crate::commitment::CommitmentScheme; use crate::folding::circuits::CF1; -use crate::transcript::Transcript; use crate::Error; -/// Defines the NIFS (Non-Interactive Folding Scheme) trait, initially 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). -/// `H` specifies whether the NIFS will use a blinding factor. -pub trait NIFSTrait, const H: bool = false> { - type CommittedInstance: Debug + Clone + Absorb; - type Witness: Debug + Clone; - type ProverAux: Debug + Clone; // Prover's aux params - type VerifierAux: Debug + Clone; // Verifier's aux params - - fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness; - fn new_instance( - w: &Self::Witness, - params: &CS::ProverParams, - x: Vec, - aux: Vec, // t_or_e in Ova, empty for Nova - ) -> Result; - - fn fold_witness( - r: C::ScalarField, - W: &Self::Witness, // running witness - w: &Self::Witness, // incoming witness - aux: &Self::ProverAux, - ) -> Result; - - /// computes the auxiliary parameters, eg. in Nova: (T, cmT), in Ova: T - fn compute_aux( - cs_prover_params: &CS::ProverParams, - r1cs: &R1CS, - W_i: &Self::Witness, - U_i: &Self::CommittedInstance, - w_i: &Self::Witness, - u_i: &Self::CommittedInstance, - ) -> Result<(Self::ProverAux, Self::VerifierAux), Error>; - - fn get_challenge>( - transcript: &mut T, - pp_hash: C::ScalarField, // public params hash - U_i: &Self::CommittedInstance, - u_i: &Self::CommittedInstance, - aux: &Self::VerifierAux, // ie. in Nova wouild be cmT, in Ova it's empty - ) -> Vec; - - /// NIFS.P. Notice that this method is implemented at the trait level, and depends on the other - /// two methods `fold_witness` and `verify`. - fn prove( - r: C::ScalarField, - W_i: &Self::Witness, // running witness - U_i: &Self::CommittedInstance, // running committed instance - w_i: &Self::Witness, // incoming witness - u_i: &Self::CommittedInstance, // incoming committed instance - aux_p: &Self::ProverAux, - aux_v: &Self::VerifierAux, - ) -> Result<(Self::Witness, Self::CommittedInstance), Error> { - let w = Self::fold_witness(r, W_i, w_i, aux_p)?; - let ci = Self::verify(r, U_i, u_i, aux_v); - Ok((w, ci)) - } - - /// NIFS.V - fn verify( - // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element - r: C::ScalarField, - U_i: &Self::CommittedInstance, - u_i: &Self::CommittedInstance, - aux: &Self::VerifierAux, - ) -> Self::CommittedInstance; -} - /// Implements `Arith` for R1CS, where the witness is of type [`Witness`], and /// the committed instance is of type [`CommittedInstance`]. /// diff --git a/folding-schemes/src/folding/nova/zk.rs b/folding-schemes/src/folding/nova/zk.rs index 33f30cde..fe6cf1e4 100644 --- a/folding-schemes/src/folding/nova/zk.rs +++ b/folding-schemes/src/folding/nova/zk.rs @@ -30,8 +30,7 @@ /// paper). /// And the Use-case-2 would require a modified version of the Decider circuits. /// -use ark_crypto_primitives::sponge::CryptographicSponge; -use ark_ff::{BigInteger, PrimeField}; +use ark_ff::PrimeField; use ark_std::{One, Zero}; use crate::{ @@ -41,7 +40,7 @@ use crate::{ }; use ark_crypto_primitives::sponge::{ poseidon::{PoseidonConfig, PoseidonSponge}, - Absorb, + Absorb, CryptographicSponge, }; use ark_ec::{CurveGroup, Group}; use ark_r1cs_std::{ @@ -52,21 +51,16 @@ use ark_r1cs_std::{ use crate::{commitment::CommitmentScheme, folding::circuits::CF2, frontend::FCircuit, Error}; use super::{ - circuits::ChallengeGadget, nifs::NIFS, traits::NIFSTrait, CommittedInstance, Nova, Witness, + nifs::{nova::NIFS, NIFSTrait}, + CommittedInstance, Nova, Witness, }; -// We use the same definition of a folding proof as in https://eprint.iacr.org/2023/969.pdf -// It consists in the commitment to the T term -pub struct FoldingProof { - cmT: C, -} - pub struct RandomizedIVCProof { pub U_i: CommittedInstance, pub u_i: CommittedInstance, pub U_r: CommittedInstance, - pub pi: FoldingProof, - pub pi_prime: FoldingProof, + pub pi: C1, // proof = cmT + pub pi_prime: C1, // proof' = cmT' pub W_i_prime: Witness, pub cf_U_i: CommittedInstance, pub cf_W_i: Witness, @@ -77,24 +71,6 @@ where ::ScalarField: Absorb, ::BaseField: PrimeField, { - /// Computes challenge required before folding instances - fn get_folding_challenge( - sponge: &mut PoseidonSponge, - pp_hash: C1::ScalarField, - U_i: CommittedInstance, - u_i: CommittedInstance, - cmT: C1, - ) -> Result { - let r_bits = ChallengeGadget::>::get_challenge_native( - sponge, - pp_hash, - &U_i, - &u_i, - Some(&cmT), - ); - C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)).ok_or(Error::OutOfBounds) - } - /// Compute a zero-knowledge proof of a Nova IVC proof /// It implements the prover of appendix D.4.in https://eprint.iacr.org/2023/573.pdf /// For further details on why folding is hiding, see lemma 9 @@ -118,68 +94,45 @@ where GC2: ToConstraintFieldGadget<::BaseField>, C1: CurveGroup, { - let mut challenges_sponge = PoseidonSponge::::new(&nova.poseidon_config); + let mut transcript = PoseidonSponge::::new(&nova.poseidon_config); // I. Compute proof for 'regular' instances // 1. Fold the instance-witness pairs (U_i, W_i) with (u_i, w_i) - // a. Compute T - let (T, cmT) = NIFS::::compute_cmT( + let (W_f, U_f, cmT, _) = NIFS::, true>::prove( &nova.cs_pp, &nova.r1cs, + &mut transcript, + nova.pp_hash, &nova.w_i, &nova.u_i, &nova.W_i, &nova.U_i, )?; - // b. Compute folding challenge - let r = RandomizedIVCProof::::get_folding_challenge( - &mut challenges_sponge, - nova.pp_hash, - nova.U_i.clone(), - nova.u_i.clone(), - cmT, - )?; - - // c. Compute fold - let (W_f, U_f) = - NIFS::::prove(r, &nova.w_i, &nova.u_i, &nova.W_i, &nova.U_i, &T, &cmT)?; - - // d. Store folding proof - let pi = FoldingProof { cmT }; - // 2. Sample a satisfying relaxed R1CS instance-witness pair (W_r, U_r) let (W_r, U_r) = nova .r1cs .sample_witness_instance::(&nova.cs_pp, &mut rng)?; // 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r) - // a. Compute T - let (T_i_prime, cmT_i_prime) = - NIFS::::compute_cmT(&nova.cs_pp, &nova.r1cs, &W_f, &U_f, &W_r, &U_r)?; - - // b. Compute folding challenge - let r_2 = RandomizedIVCProof::::get_folding_challenge( - &mut challenges_sponge, - nova.pp_hash, - U_f.clone(), - U_r.clone(), - cmT_i_prime, - )?; - - // c. Compute fold - let (W_i_prime, _) = - NIFS::::prove(r_2, &W_f, &U_f, &W_r, &U_r, &T_i_prime, &cmT_i_prime)?; - - // d. Store folding proof - let pi_prime = FoldingProof { cmT: cmT_i_prime }; + let (W_i_prime, _, cmT_i_prime, _) = + NIFS::, true>::prove( + &nova.cs_pp, + &nova.r1cs, + &mut transcript, + nova.pp_hash, + &W_f, + &U_f, + &W_r, + &U_r, + )?; Ok(RandomizedIVCProof { U_i: nova.U_i.clone(), u_i: nova.u_i.clone(), U_r, - pi, - pi_prime, + pi: cmT, + pi_prime: cmT_i_prime, W_i_prime, cf_U_i: nova.cf_U_i.clone(), cf_W_i: nova.cf_W_i.clone(), @@ -228,7 +181,7 @@ where } // b. Check computed hashes are correct - let mut sponge = PoseidonSponge::::new(poseidon_config); + let sponge = PoseidonSponge::::new(poseidon_config); let expected_u_i_x = proof.U_i.hash(&sponge, pp_hash, i, &z_0, &z_i); if expected_u_i_x != proof.u_i.x[0] { return Err(Error::zkIVCVerificationFail); @@ -244,32 +197,25 @@ where return Err(Error::zkIVCVerificationFail); } + let mut transcript = PoseidonSponge::::new(poseidon_config); // 3. Obtain the U_f folded instance - // a. Compute folding challenge - let r = RandomizedIVCProof::::get_folding_challenge( - &mut sponge, + let (U_f, _) = NIFS::, true>::verify( + &mut transcript, pp_hash, - proof.U_i.clone(), - proof.u_i.clone(), - proof.pi.cmT, + &proof.u_i, + &proof.U_i, + &proof.pi, )?; - // b. Get the U_f instance - let U_f = NIFS::::verify(r, &proof.u_i, &proof.U_i, &proof.pi.cmT); - // 4. Obtain the U^{\prime}_i folded instance - // a. Compute folding challenge - let r_2 = RandomizedIVCProof::::get_folding_challenge( - &mut sponge, + let (U_i_prime, _) = NIFS::, true>::verify( + &mut transcript, pp_hash, - U_f.clone(), - proof.U_r.clone(), - proof.pi_prime.cmT, + &U_f, + &proof.U_r, + &proof.pi_prime, )?; - // b. Compute fold - let U_i_prime = NIFS::::verify(r_2, &U_f, &proof.U_r, &proof.pi_prime.cmT); - // 5. Check that W^{\prime}_i is a satisfying witness r1cs.check_relation(&proof.W_i_prime, &U_i_prime)?; diff --git a/solidity-verifiers/src/verifiers/nova_cyclefold.rs b/solidity-verifiers/src/verifiers/nova_cyclefold.rs index e82bd1f3..86e70987 100644 --- a/solidity-verifiers/src/verifiers/nova_cyclefold.rs +++ b/solidity-verifiers/src/verifiers/nova_cyclefold.rs @@ -366,6 +366,7 @@ mod tests { n_steps: usize, ) { let (decider_pp, decider_vp) = decider_params; + let pp_hash = fs_params.1.pp_hash().unwrap(); let f_circuit = FC::new(()).unwrap(); @@ -400,6 +401,7 @@ mod tests { let calldata: Vec = prepare_calldata( function_selector, + pp_hash, nova.i, nova.z_0, nova.z_i,