/// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases, /// other more efficient approaches can be used. use ark_crypto_primitives::sponge::{ constraints::CryptographicSpongeVar, poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, Absorb, CryptographicSponge, }; use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, boolean::Boolean, eq::EqGadget, fields::fp::FpVar, prelude::CurveVar, ToConstraintFieldGadget, }; use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_std::{borrow::Borrow, log2, marker::PhantomData}; use super::{ circuits::{CCCSVar, LCCCSVar, NIMFSGadget, ProofVar as NIMFSProofVar}, nimfs::{NIMFSProof, NIMFS}, HyperNova, Witness, CCCS, LCCCS, }; use crate::folding::circuits::{decider::on_chain::GenericOnchainDeciderCircuit, CF1, CF2}; use crate::folding::traits::{WitnessOps, WitnessVarOps}; use crate::frontend::FCircuit; use crate::utils::gadgets::{eval_mle, MatrixGadget}; use crate::Error; use crate::{ arith::{ ccs::{circuits::CCSMatricesVar, CCS}, ArithGadget, }, folding::circuits::decider::{EvalGadget, KZGChallengesGadget}, }; use crate::{ commitment::{pedersen::Params as PedersenParams, CommitmentScheme}, folding::circuits::decider::DeciderEnabledNIFS, }; impl ArithGadget>, LCCCSVar> for CCSMatricesVar> { type Evaluation = Vec>>; fn eval_relation( &self, w: &WitnessVar>, u: &LCCCSVar, ) -> Result { let z = [&[u.u.clone()][..], &u.x, &w.w].concat(); self.M .iter() .map(|M_j| { let s = log2(M_j.n_rows) as usize; let Mz = M_j.mul_vector(&z)?; Ok(eval_mle(s, Mz, u.r_x.clone())) }) .collect() } fn enforce_evaluation( _w: &WitnessVar>, u: &LCCCSVar, v: Self::Evaluation, ) -> Result<(), SynthesisError> { v.enforce_equal(&u.v) } } /// In-circuit representation of the Witness associated to the CommittedInstance. #[derive(Debug, Clone)] pub struct WitnessVar { pub w: Vec>, pub r_w: FpVar, } impl AllocVar, F> for WitnessVar { fn new_variable>>( cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { f().and_then(|val| { let cs = cs.into(); let w: Vec> = Vec::new_variable(cs.clone(), || Ok(val.borrow().w.clone()), mode)?; let r_w = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().r_w), mode)?; Ok(Self { w, r_w }) }) } } impl WitnessVarOps for WitnessVar { fn get_openings(&self) -> Vec<(&[FpVar], FpVar)> { vec![(&self.w, self.r_w.clone())] } } pub type DeciderEthCircuit = GenericOnchainDeciderCircuit< C1, C2, GC2, LCCCS, CCCS, Witness>, CCS>, CCSMatricesVar>, DeciderHyperNovaGadget, >; impl< C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, C2: CurveGroup, GC2: CurveVar> + ToConstraintFieldGadget>, FC: FCircuit, CS1: CommitmentScheme, // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider CS2: CommitmentScheme>, const MU: usize, const NU: usize, const H: bool, > TryFrom> for DeciderEthCircuit where CF1: Absorb, { type Error = Error; fn try_from(hn: HyperNova) -> Result { // compute the U_{i+1}, W_{i+1}, by folding the last running & incoming instances let mut transcript = PoseidonSponge::::new(&hn.poseidon_config); transcript.absorb(&hn.pp_hash); let (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( &mut transcript, &hn.ccs, &[hn.U_i.clone()], &[hn.u_i.clone()], &[hn.W_i.clone()], &[hn.w_i.clone()], )?; // compute the KZG challenges used as inputs in the circuit let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1); // get KZG evals let kzg_evaluations = W_i1 .get_openings() .iter() .zip(&kzg_challenges) .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c)) .collect::, _>>()?; Ok(Self { _gc2: PhantomData, _avar: PhantomData, arith: hn.ccs, cf_arith: hn.cf_r1cs, cf_pedersen_params: hn.cf_cs_pp, poseidon_config: hn.poseidon_config, pp_hash: hn.pp_hash, i: hn.i, z_0: hn.z_0, z_i: hn.z_i, U_i: hn.U_i, W_i: hn.W_i, u_i: hn.u_i, w_i: hn.w_i, U_i1, W_i1, proof: nimfs_proof, randomness: rho, cf_U_i: hn.cf_U_i, cf_W_i: hn.cf_W_i, kzg_challenges, kzg_evaluations, }) } } pub struct DeciderHyperNovaGadget; impl DeciderEnabledNIFS, CCCS, Witness, CCS>> for DeciderHyperNovaGadget where CF1: Absorb, { type ProofDummyCfg = (usize, usize, usize, usize); type Proof = NIMFSProof; type Randomness = CF1; type RandomnessDummyCfg = (); fn fold_field_elements_gadget( arith: &CCS>, transcript: &mut PoseidonSpongeVar>, pp_hash: FpVar>, U: LCCCSVar, _U_vec: Vec>>, u: CCCSVar, proof: Self::Proof, randomness: Self::Randomness, ) -> Result, SynthesisError> { let cs = transcript.cs(); transcript.absorb(&pp_hash)?; let nimfs_proof = NIMFSProofVar::::new_witness(cs.clone(), || Ok(proof))?; let rho = FpVar::>::new_input(cs.clone(), || Ok(randomness))?; let (computed_U_i1, rho_bits) = NIMFSGadget::::verify( cs.clone(), arith, transcript, &[U], &[u], nimfs_proof, Boolean::TRUE, // enabled )?; Boolean::le_bits_to_fp_var(&rho_bits)?.enforce_equal(&rho)?; Ok(computed_U_i1) } fn fold_group_elements_native( U_commitments: &[C], u_commitments: &[C], _: Option, r: Self::Randomness, ) -> Result, Error> { let U_C = U_commitments[0]; let u_C = u_commitments[0]; let C = U_C + u_C.mul(r); Ok(vec![C]) } } #[cfg(test)] pub mod tests { use ark_bn254::{constraints::GVar, Fr, G1Projective as Projective}; use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; use ark_std::{test_rng, UniformRand}; use super::*; use crate::arith::r1cs::R1CS; use crate::commitment::pedersen::Pedersen; use crate::folding::nova::PreprocessorParam; use crate::frontend::utils::CubicFCircuit; use crate::transcript::poseidon::poseidon_canonical_config; use crate::FoldingScheme; #[test] fn test_lcccs_checker_gadget() { let mut rng = test_rng(); let n_rows = 2_u32.pow(5) as usize; let n_cols = 2_u32.pow(5) as usize; let r1cs = R1CS::::rand(&mut rng, n_rows, n_cols); let ccs = CCS::from(r1cs); let z: Vec = (0..n_cols).map(|_| Fr::rand(&mut rng)).collect(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); let (lcccs, w) = ccs .to_lcccs::<_, Projective, Pedersen, false>(&mut rng, &pedersen_params, &z) .unwrap(); let cs = ConstraintSystem::::new_ref(); // CCS's (sparse) matrices are constants in the circuit let ccs_mat = CCSMatricesVar::::new_constant(cs.clone(), ccs.clone()).unwrap(); let w_var = WitnessVar::new_witness(cs.clone(), || Ok(w)).unwrap(); let lcccs_var = LCCCSVar::new_input(cs.clone(), || Ok(lcccs)).unwrap(); ccs_mat.enforce_relation(&w_var, &lcccs_var).unwrap(); assert!(cs.is_satisfied().unwrap()); } #[test] fn test_decider_circuit() { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_canonical_config::(); let F_circuit = CubicFCircuit::::new(()).unwrap(); let z_0 = vec![Fr::from(3_u32)]; const MU: usize = 1; const NU: usize = 1; type HN = HyperNova< Projective, GVar, Projective2, GVar2, CubicFCircuit, Pedersen, Pedersen, MU, NU, false, >; let prep_param = PreprocessorParam::< Projective, Projective2, CubicFCircuit, Pedersen, Pedersen, false, >::new(poseidon_config, F_circuit); let hn_params = HN::preprocess(&mut rng, &prep_param).unwrap(); // generate a Nova instance and do a step of it let mut hypernova = HN::init(&hn_params, F_circuit, z_0.clone()).unwrap(); hypernova.prove_step(&mut rng, vec![], None).unwrap(); let ivc_proof = hypernova.ivc_proof(); HN::verify(hn_params.1, ivc_proof).unwrap(); // load the DeciderEthCircuit from the generated Nova instance let decider_circuit = DeciderEthCircuit::::try_from(hypernova).unwrap(); let cs = ConstraintSystem::::new_ref(); // generate the constraints and check that are satisfied by the inputs decider_circuit.generate_constraints(cs.clone()).unwrap(); assert!(cs.is_satisfied().unwrap()); dbg!(cs.num_constraints()); } }