From dfd03ea3865b26d20a6f306bb3cfb24a4b9e0c60 Mon Sep 17 00:00:00 2001 From: winderica Date: Fri, 20 Sep 2024 01:36:19 +0800 Subject: [PATCH] Traits for witnesses and committed instances (#157) * Add traits for witness and committed instance * Implement witness and committed instance traits for Nova and HyperNova * Implement witness and committed instance traits for ProtoGalaxy * Improve the clarity of docs for `Witness{Var}Ext::get_openings` * Avoid cloning `z_i` * Fix grammar issues * Rename `Ext` traits for committed instances and witnesses to `Ops` * Implement `to_sponge_bytes` --- folding-schemes/src/folding/hypernova/cccs.rs | 19 +- .../src/folding/hypernova/circuits.rs | 201 +++++++++++------- .../folding/hypernova/decider_eth_circuit.rs | 18 +- .../src/folding/hypernova/lcccs.rs | 43 ++-- folding-schemes/src/folding/hypernova/mod.rs | 36 ++-- folding-schemes/src/folding/mod.rs | 1 + folding-schemes/src/folding/nova/circuits.rs | 132 +++++++----- .../src/folding/nova/decider_eth_circuit.rs | 22 +- folding-schemes/src/folding/nova/mod.rs | 57 +++-- folding-schemes/src/folding/nova/zk.rs | 3 +- .../src/folding/protogalaxy/circuits.rs | 51 +++-- .../src/folding/protogalaxy/mod.rs | 140 ++++++------ .../src/folding/protogalaxy/traits.rs | 49 ++++- folding-schemes/src/folding/traits.rs | 121 +++++++++++ folding-schemes/src/lib.rs | 2 + 15 files changed, 566 insertions(+), 329 deletions(-) create mode 100644 folding-schemes/src/folding/traits.rs diff --git a/folding-schemes/src/folding/hypernova/cccs.rs b/folding-schemes/src/folding/hypernova/cccs.rs index 5b95df2..0a253a8 100644 --- a/folding-schemes/src/folding/hypernova/cccs.rs +++ b/folding-schemes/src/folding/hypernova/cccs.rs @@ -9,9 +9,11 @@ use std::sync::Arc; use ark_std::rand::Rng; +use super::circuits::CCCSVar; use super::Witness; use crate::arith::{ccs::CCS, Arith}; use crate::commitment::CommitmentScheme; +use crate::folding::traits::CommittedInstanceOps; use crate::transcript::AbsorbNonNative; use crate::utils::mle::dense_vec_to_dense_mle; use crate::utils::vec::mat_vec_mul; @@ -126,9 +128,8 @@ impl Absorb for CCCS where C::ScalarField: Absorb, { - fn to_sponge_bytes(&self, _dest: &mut Vec) { - // This is never called - unimplemented!() + fn to_sponge_bytes(&self, dest: &mut Vec) { + C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); } fn to_sponge_field_elements(&self, dest: &mut Vec) { @@ -142,6 +143,18 @@ where } } +impl CommittedInstanceOps for CCCS { + type Var = CCCSVar; + + fn get_commitments(&self) -> Vec { + vec![self.C] + } + + fn is_incoming(&self) -> bool { + true + } +} + #[cfg(test)] pub mod tests { use ark_pallas::Fr; diff --git a/folding-schemes/src/folding/hypernova/circuits.rs b/folding-schemes/src/folding/hypernova/circuits.rs index 1c2d786..1fb4f09 100644 --- a/folding-schemes/src/folding/hypernova/circuits.rs +++ b/folding-schemes/src/folding/hypernova/circuits.rs @@ -1,6 +1,6 @@ /// Implementation of [HyperNova](https://eprint.iacr.org/2023/573.pdf) circuits use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, + constraints::{AbsorbGadget, CryptographicSpongeVar}, poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, CryptographicSponge, }; @@ -14,6 +14,7 @@ use ark_r1cs_std::{ fields::{fp::FpVar, FieldVar}, groups::GroupOpsBounds, prelude::CurveVar, + uint8::UInt8, R1CSVar, ToConstraintFieldGadget, }; use ark_relations::r1cs::{ @@ -41,6 +42,7 @@ use crate::folding::{ CF1, CF2, }, nova::get_r1cs_from_cs, + traits::CommittedInstanceVarOps, }; use crate::frontend::FCircuit; use crate::utils::virtual_polynomial::VPAuxInfo; @@ -52,19 +54,16 @@ use crate::{ /// Committed CCS instance #[derive(Debug, Clone)] -pub struct CCCSVar -where - ::BaseField: PrimeField, -{ +pub struct CCCSVar { // Commitment to witness pub C: NonNativeAffineVar, // Public io pub x: Vec>>, } + impl AllocVar, CF1> for CCCSVar where C: CurveGroup, - ::BaseField: PrimeField, { fn new_variable>>( cs: impl Into>>, @@ -83,12 +82,30 @@ where } } +impl CommittedInstanceVarOps for CCCSVar { + type PointVar = NonNativeAffineVar; + + fn get_commitments(&self) -> Vec { + vec![self.C.clone()] + } + + fn get_public_inputs(&self) -> &[FpVar>] { + &self.x + } + + fn enforce_incoming(&self) -> Result<(), SynthesisError> { + // `CCCSVar` is always the incoming instance + Ok(()) + } + + fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { + self.x.enforce_equal(&other.x) + } +} + /// Linearized Committed CCS instance #[derive(Debug, Clone)] -pub struct LCCCSVar -where - ::BaseField: PrimeField, -{ +pub struct LCCCSVar { // Commitment to witness pub C: NonNativeAffineVar, // Relaxation factor of z for folded LCCCS @@ -100,10 +117,10 @@ where // Vector of v_i pub v: Vec>>, } + impl AllocVar, CF1> for LCCCSVar where C: CurveGroup, - ::BaseField: PrimeField, { fn new_variable>>( cs: impl Into>>, @@ -127,41 +144,44 @@ where } } -impl LCCCSVar -where - C: CurveGroup, - ::ScalarField: Absorb, - ::BaseField: ark_ff::PrimeField, -{ - /// [`LCCCSVar`].hash implements the LCCCS instance hash compatible with the native - /// implementation from LCCCS.hash. - /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the LCCCS. - /// Additionally it returns the vector of the field elements from the self parameters, so they - /// can be reused in other gadgets avoiding recalculating (reconstraining) them. - #[allow(clippy::type_complexity)] - pub fn hash( - self, - sponge: &PoseidonSpongeVar>, - pp_hash: FpVar>, - i: FpVar>, - z_0: Vec>>, - z_i: Vec>>, - ) -> Result<(FpVar>, Vec>>), SynthesisError> { - let mut sponge = sponge.clone(); - let U_vec = [ - self.C.to_constraint_field()?, - vec![self.u], - self.x, - self.r_x, - self.v, +impl AbsorbGadget for LCCCSVar { + 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([ + &self.C.to_constraint_field()?, + &[self.u.clone()][..], + &self.x, + &self.r_x, + &self.v, ] - .concat(); - sponge.absorb(&pp_hash)?; - sponge.absorb(&i)?; - sponge.absorb(&z_0)?; - sponge.absorb(&z_i)?; - sponge.absorb(&U_vec)?; - Ok((sponge.squeeze_field_elements(1)?.pop().unwrap(), U_vec)) + .concat()) + } +} + +impl CommittedInstanceVarOps for LCCCSVar { + type PointVar = NonNativeAffineVar; + + fn get_commitments(&self) -> Vec { + vec![self.C.clone()] + } + + fn get_public_inputs(&self) -> &[FpVar>] { + &self.x + } + + fn enforce_incoming(&self) -> Result<(), SynthesisError> { + // `LCCCSVar` is always the running instance + Err(SynthesisError::Unsatisfiable) + } + + fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { + self.u.enforce_equal(&other.u)?; + self.x.enforce_equal(&other.x)?; + self.r_x.enforce_equal(&other.r_x)?; + self.v.enforce_equal(&other.v) } } @@ -742,25 +762,13 @@ where let sponge = PoseidonSpongeVar::::new(cs.clone(), &self.poseidon_config); - // get z_{i+1} from the F circuit - let i_usize = self.i_usize.unwrap_or(0); - let z_i1 = - self.F - .generate_step_constraints(cs.clone(), i_usize, z_i.clone(), external_inputs)?; - let is_basecase = i.is_zero()?; let is_not_basecase = is_basecase.not(); // Primary Part // P.1. Compute u_i.x // u_i.x[0] = H(i, z_0, z_i, U_i) - let (u_i_x, _) = U_i.clone().hash( - &sponge, - pp_hash.clone(), - i.clone(), - z_0.clone(), - z_i.clone(), - )?; + let (u_i_x, _) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?; // u_i.x[1] = H(cf_U_i) let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?; @@ -795,19 +803,26 @@ where U_i1.C = U_i1_C; // P.4.a compute and check the first output of F' + + // get z_{i+1} from the F circuit + let i_usize = self.i_usize.unwrap_or(0); + let z_i1 = self + .F + .generate_step_constraints(cs.clone(), i_usize, z_i, external_inputs)?; + let (u_i1_x, _) = U_i1.clone().hash( &sponge, - pp_hash.clone(), - i + FpVar::>::one(), - z_0.clone(), - z_i1.clone(), + &pp_hash, + &(i + FpVar::>::one()), + &z_0, + &z_i1, )?; let (u_i1_x_base, _) = LCCCSVar::new_constant(cs.clone(), U_dummy)?.hash( &sponge, - pp_hash.clone(), - FpVar::>::one(), - z_0.clone(), - z_i1.clone(), + &pp_hash, + &FpVar::>::one(), + &z_0, + &z_i1, )?; let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?; x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?; @@ -900,6 +915,7 @@ mod tests { utils::{compute_c, compute_sigmas_thetas}, HyperNovaCycleFoldCircuit, }, + traits::CommittedInstanceOps, }, frontend::utils::CubicFCircuit, transcript::poseidon::poseidon_canonical_config, @@ -1113,6 +1129,37 @@ mod tests { assert_eq!(folded_lcccsVar.u.value().unwrap(), folded_lcccs.u); } + /// test that checks the native LCCCS.to_sponge_{bytes,field_elements} vs + /// the R1CS constraints version + #[test] + pub fn test_lcccs_to_sponge_preimage() { + let mut rng = test_rng(); + + let ccs = get_test_ccs(); + let z1 = get_test_z::(3); + + let (pedersen_params, _) = + Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); + + let (lcccs, _) = ccs + .to_lcccs::<_, _, Pedersen, true>(&mut rng, &pedersen_params, &z1) + .unwrap(); + let bytes = lcccs.to_sponge_bytes_as_vec(); + let field_elements = lcccs.to_sponge_field_elements_as_vec(); + + let cs = ConstraintSystem::::new_ref(); + + let lcccsVar = LCCCSVar::::new_witness(cs.clone(), || Ok(lcccs)).unwrap(); + let bytes_var = lcccsVar.to_sponge_bytes().unwrap(); + let field_elements_var = lcccsVar.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 that checks the native LCCCS.hash vs the R1CS constraints version #[test] pub fn test_lcccs_hash() { @@ -1133,9 +1180,7 @@ mod tests { let (lcccs, _) = ccs .to_lcccs::<_, _, Pedersen, true>(&mut rng, &pedersen_params, &z1) .unwrap(); - let h = lcccs - .clone() - .hash(&sponge, pp_hash, i, z_0.clone(), z_i.clone()); + let h = lcccs.clone().hash(&sponge, pp_hash, i, &z_0, &z_i); let cs = ConstraintSystem::::new_ref(); @@ -1147,13 +1192,7 @@ mod tests { let lcccsVar = LCCCSVar::::new_witness(cs.clone(), || Ok(lcccs)).unwrap(); let (hVar, _) = lcccsVar .clone() - .hash( - &spongeVar, - pp_hashVar, - iVar.clone(), - z_0Var.clone(), - z_iVar.clone(), - ) + .hash(&spongeVar, &pp_hashVar, &iVar, &z_0Var, &z_iVar) .unwrap(); assert!(cs.is_satisfied().unwrap()); @@ -1225,7 +1264,7 @@ mod tests { let mut cf_W_i = cf_W_dummy.clone(); let mut cf_U_i = cf_U_dummy.clone(); u_i.x = vec![ - U_i.hash(&sponge, pp_hash, Fr::zero(), z_0.clone(), z_i.clone()), + U_i.hash(&sponge, pp_hash, Fr::zero(), &z_0, &z_i), cf_U_i.hash_cyclefold(&sponge, pp_hash), ]; @@ -1252,7 +1291,7 @@ mod tests { W_i1 = Witness::::dummy(&ccs); U_i1 = LCCCS::dummy(ccs.l, ccs.t, ccs.s); - let u_i1_x = U_i1.hash(&sponge, pp_hash, Fr::one(), z_0.clone(), z_i1.clone()); + let u_i1_x = U_i1.hash(&sponge, pp_hash, Fr::one(), &z_0, &z_i1); // hash the initial (dummy) CycleFold instance, which is used as the 2nd public // input in the AugmentedFCircuit @@ -1309,8 +1348,7 @@ mod tests { // sanity check: check the folded instance relation U_i1.check_relation(&ccs, &W_i1).unwrap(); - let u_i1_x = - U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), z_0.clone(), z_i1.clone()); + let u_i1_x = U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), &z_0, &z_i1); let rho_bits = rho.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); let rho_Fq = Fq::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); @@ -1434,8 +1472,7 @@ mod tests { assert_eq!(u_i.x, r1cs_x_i1); assert_eq!(u_i.x[0], augmented_f_circuit.x.unwrap()); assert_eq!(u_i.x[1], augmented_f_circuit.cf_x.unwrap()); - let expected_u_i1_x = - U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), z_0.clone(), z_i1.clone()); + let expected_u_i1_x = U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), &z_0, &z_i1); let expected_cf_U_i1_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); // u_i is already u_i1 at this point, check that has the expected value at x[0] assert_eq!(u_i.x[0], expected_u_i1_x); diff --git a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs index b4e2cf8..5f367fb 100644 --- a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs @@ -26,8 +26,6 @@ use super::{ nimfs::{NIMFSProof, NIMFS}, HyperNova, Witness, CCCS, LCCCS, }; -use crate::arith::ccs::CCS; -use crate::arith::r1cs::R1CS; use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; use crate::folding::circuits::{ cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness}, @@ -40,6 +38,8 @@ use crate::utils::{ vec::poly_from_vec, }; use crate::Error; +use crate::{arith::ccs::CCS, folding::traits::CommittedInstanceVarOps}; +use crate::{arith::r1cs::R1CS, folding::traits::WitnessVarOps}; /// In-circuit representation of the Witness associated to the CommittedInstance. #[derive(Debug, Clone)] @@ -66,6 +66,12 @@ impl AllocVar, F> for WitnessVar { } } +impl WitnessVarOps for WitnessVar { + fn get_openings(&self) -> Vec<(&[FpVar], FpVar)> { + vec![(&self.w, self.r_w.clone())] + } +} + /// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters. #[derive(Debug, Clone)] pub struct CCSMatricesVar { @@ -340,13 +346,7 @@ where )?; // 3.a u_i.x[0] == H(i, z_0, z_i, U_i) - let (u_i_x, _) = U_i.clone().hash( - &sponge, - pp_hash.clone(), - i.clone(), - z_0.clone(), - z_i.clone(), - )?; + let (u_i_x, _) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?; (u_i.x[0]).enforce_equal(&u_i_x)?; #[cfg(feature = "light-test")] diff --git a/folding-schemes/src/folding/hypernova/lcccs.rs b/folding-schemes/src/folding/hypernova/lcccs.rs index 6aa1652..e95fd8c 100644 --- a/folding-schemes/src/folding/hypernova/lcccs.rs +++ b/folding-schemes/src/folding/hypernova/lcccs.rs @@ -1,5 +1,5 @@ use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; +use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_poly::DenseMultilinearExtension; use ark_poly::MultilinearExtension; @@ -8,10 +8,12 @@ use ark_serialize::CanonicalSerialize; use ark_std::rand::Rng; use ark_std::Zero; +use super::circuits::LCCCSVar; use super::Witness; use crate::arith::ccs::CCS; use crate::commitment::CommitmentScheme; -use crate::transcript::{AbsorbNonNative, Transcript}; +use crate::folding::traits::CommittedInstanceOps; +use crate::transcript::AbsorbNonNative; use crate::utils::mle::dense_vec_to_dense_mle; use crate::utils::vec::mat_vec_mul; use crate::Error; @@ -121,9 +123,8 @@ impl Absorb for LCCCS where C::ScalarField: Absorb, { - fn to_sponge_bytes(&self, _dest: &mut Vec) { - // This is never called - unimplemented!() + fn to_sponge_bytes(&self, dest: &mut Vec) { + C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); } fn to_sponge_field_elements(&self, dest: &mut Vec) { @@ -140,29 +141,15 @@ where } } -impl LCCCS -where - ::ScalarField: Absorb, - ::BaseField: ark_ff::PrimeField, -{ - /// [`LCCCS`].hash implements the committed instance hash compatible with the gadget - /// implemented in nova/circuits.rs::CommittedInstanceVar.hash. - /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U_i` is the LCCCS. - pub fn hash>( - &self, - sponge: &T, - pp_hash: C::ScalarField, - i: C::ScalarField, - z_0: Vec, - z_i: Vec, - ) -> C::ScalarField { - let mut sponge = sponge.clone(); - sponge.absorb(&pp_hash); - sponge.absorb(&i); - sponge.absorb(&z_0); - sponge.absorb(&z_i); - sponge.absorb(&self); - sponge.squeeze_field_elements(1)[0] +impl CommittedInstanceOps for LCCCS { + type Var = LCCCSVar; + + fn get_commitments(&self) -> Vec { + vec![self.C] + } + + fn is_incoming(&self) -> bool { + false } } diff --git a/folding-schemes/src/folding/hypernova/mod.rs b/folding-schemes/src/folding/hypernova/mod.rs index 2917ea4..c63d943 100644 --- a/folding-schemes/src/folding/hypernova/mod.rs +++ b/folding-schemes/src/folding/hypernova/mod.rs @@ -20,6 +20,7 @@ pub mod utils; use cccs::CCCS; use circuits::AugmentedFCircuit; +use decider_eth_circuit::WitnessVar; use lcccs::LCCCS; use nimfs::NIMFS; @@ -32,6 +33,7 @@ use crate::folding::circuits::{ CF2, }; use crate::folding::nova::{get_r1cs_from_cs, PreprocessorParam}; +use crate::folding::traits::{CommittedInstanceOps, WitnessOps}; use crate::frontend::FCircuit; use crate::utils::{get_cm_coordinates, pp_hash}; use crate::Error; @@ -81,6 +83,14 @@ impl Witness { } } +impl WitnessOps for Witness { + type Var = WitnessVar; + + fn get_openings(&self) -> Vec<(&[F], F)> { + vec![(&self.w, self.r_w)] + } +} + /// Proving parameters for HyperNova-based IVC #[derive(Debug, Clone)] pub struct ProverParams @@ -307,8 +317,8 @@ where &sponge, self.pp_hash, C1::ScalarField::zero(), // i - self.z_0.clone(), - state.clone(), + &self.z_0, + &state, ), cf_U_i.hash_cyclefold(&sponge, self.pp_hash), ]; @@ -324,8 +334,8 @@ where &sponge, self.pp_hash, C1::ScalarField::one(), // i+1, where i=0 - self.z_0.clone(), - z_i1.clone(), + &self.z_0, + &z_i1, ); let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, self.pp_hash); @@ -494,13 +504,7 @@ where let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness, CycleFoldCommittedInstance) = cf_r1cs.dummy_running_instance(); u_dummy.x = vec![ - U_dummy.hash( - &sponge, - pp_hash, - C1::ScalarField::zero(), - z_0.clone(), - z_0.clone(), - ), + U_dummy.hash(&sponge, pp_hash, C1::ScalarField::zero(), &z_0, &z_0), cf_U_dummy.hash_cyclefold(&sponge, pp_hash), ]; @@ -643,8 +647,8 @@ where &sponge, self.pp_hash, C1::ScalarField::one(), - self.z_0.clone(), - z_i1.clone(), + &self.z_0, + &z_i1, ); // hash the initial (dummy) CycleFold instance, which is used as the 2nd public @@ -699,8 +703,8 @@ where &sponge, self.pp_hash, self.i + C1::ScalarField::one(), - self.z_0.clone(), - z_i1.clone(), + &self.z_0, + &z_i1, ); let rho_bits = rho.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); @@ -884,7 +888,7 @@ where // check that u_i's output points to the running instance // u_i.X[0] == H(i, z_0, z_i, U_i) - let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, z_0, z_i.clone()); + let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, &z_0, &z_i); if expected_u_i_x != u_i.x[0] { return Err(Error::IVCVerificationFail); } diff --git a/folding-schemes/src/folding/mod.rs b/folding-schemes/src/folding/mod.rs index 8f76a71..0f3a66f 100644 --- a/folding-schemes/src/folding/mod.rs +++ b/folding-schemes/src/folding/mod.rs @@ -2,3 +2,4 @@ pub mod circuits; pub mod hypernova; pub mod nova; pub mod protogalaxy; +pub mod traits; diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index a506c8c..6e90460 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -21,7 +21,6 @@ use ark_std::{fmt::Debug, One, Zero}; use core::{borrow::Borrow, marker::PhantomData}; use super::{CommittedInstance, NovaCycleFoldConfig}; -use crate::constants::NOVA_N_BITS_RO; use crate::folding::circuits::{ cyclefold::{ CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, @@ -32,15 +31,13 @@ use crate::folding::circuits::{ }; use crate::frontend::FCircuit; use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar}; +use crate::{constants::NOVA_N_BITS_RO, folding::traits::CommittedInstanceVarOps}; /// 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 -where - ::BaseField: ark_ff::PrimeField, -{ +pub struct CommittedInstanceVar { pub u: FpVar, pub x: Vec>, pub cmE: NonNativeAffineVar, @@ -50,7 +47,6 @@ where impl AllocVar, CF1> for CommittedInstanceVar where C: CurveGroup, - ::BaseField: PrimeField, { fn new_variable>>( cs: impl Into>>, @@ -80,7 +76,7 @@ where ::BaseField: ark_ff::PrimeField, { fn to_sponge_bytes(&self) -> Result>, SynthesisError> { - unimplemented!() + FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) } fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { @@ -94,35 +90,27 @@ where } } -impl CommittedInstanceVar -where - C: CurveGroup, - ::ScalarField: Absorb, - ::BaseField: ark_ff::PrimeField, -{ - /// hash implements the committed instance hash compatible with the native implementation from - /// CommittedInstance.hash. - /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the - /// `CommittedInstance`. - /// Additionally it returns the vector of the field elements from the self parameters, so they - /// can be reused in other gadgets avoiding recalculating (reconstraining) them. - #[allow(clippy::type_complexity)] - pub fn hash, S>>( - self, - sponge: &T, - pp_hash: FpVar>, - i: FpVar>, - z_0: Vec>>, - z_i: Vec>>, - ) -> Result<(FpVar>, Vec>>), SynthesisError> { - let mut sponge = sponge.clone(); - let U_vec = self.to_sponge_field_elements()?; - sponge.absorb(&pp_hash)?; - sponge.absorb(&i)?; - sponge.absorb(&z_0)?; - sponge.absorb(&z_i)?; - sponge.absorb(&U_vec)?; - Ok((sponge.squeeze_field_elements(1)?.pop().unwrap(), U_vec)) +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) } } @@ -359,24 +347,12 @@ where // `transcript` is for challenge generation. let mut transcript = sponge.clone(); - // get z_{i+1} from the F circuit - let i_usize = self.i_usize.unwrap_or(0); - let z_i1 = - self.F - .generate_step_constraints(cs.clone(), i_usize, z_i.clone(), external_inputs)?; - let is_basecase = i.is_zero()?; // Primary Part // P.1. Compute u_i.x // u_i.x[0] = H(i, z_0, z_i, U_i) - let (u_i_x, U_i_vec) = U_i.clone().hash( - &sponge, - pp_hash.clone(), - i.clone(), - z_0.clone(), - z_i.clone(), - )?; + let (u_i_x, U_i_vec) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?; // u_i.x[1] = H(cf_U_i) let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?; @@ -421,21 +397,28 @@ where 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 + let i_usize = self.i_usize.unwrap_or(0); + let z_i1 = self + .F + .generate_step_constraints(cs.clone(), i_usize, z_i, external_inputs)?; + // Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot}) // Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1}) let (u_i1_x, _) = U_i1.clone().hash( &sponge, - pp_hash.clone(), - i + FpVar::>::one(), - z_0.clone(), - z_i1.clone(), + &pp_hash, + &(i + FpVar::>::one()), + &z_0, + &z_i1, )?; let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash( &sponge, - pp_hash.clone(), - FpVar::>::one(), - z_0.clone(), - z_i1.clone(), + &pp_hash, + &FpVar::>::one(), + &z_0, + &z_i1, )?; let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?; x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?; @@ -538,6 +521,7 @@ pub mod tests { use crate::commitment::pedersen::Pedersen; use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; use crate::folding::nova::nifs::NIFS; + use crate::folding::traits::CommittedInstanceOps; use crate::transcript::poseidon::poseidon_canonical_config; #[test] @@ -591,6 +575,36 @@ pub mod tests { 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(); @@ -609,7 +623,7 @@ pub mod tests { }; // compute the CommittedInstance hash natively - let h = ci.hash(&sponge, pp_hash, i, z_0.clone(), z_i.clone()); + let h = ci.hash(&sponge, pp_hash, i, &z_0, &z_i); let cs = ConstraintSystem::::new_ref(); @@ -624,7 +638,7 @@ pub mod tests { // compute the CommittedInstance hash in-circuit let (hVar, _) = ciVar - .hash(&sponge, pp_hashVar, iVar, z_0Var, z_iVar) + .hash(&sponge, &pp_hashVar, &iVar, &z_0Var, &z_iVar) .unwrap(); assert!(cs.is_satisfied().unwrap()); diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index 25564a1..27d83f1 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -27,8 +27,6 @@ use super::{ nifs::NIFS, CommittedInstance, Nova, Witness, }; -use crate::arith::r1cs::R1CS; -use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; use crate::folding::circuits::{ cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness}, nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, @@ -41,6 +39,11 @@ use crate::utils::{ vec::poly_from_vec, }; use crate::Error; +use crate::{arith::r1cs::R1CS, folding::traits::WitnessVarOps}; +use crate::{ + commitment::{pedersen::Params as PedersenParams, CommitmentScheme}, + folding::traits::CommittedInstanceVarOps, +}; #[derive(Debug, Clone)] pub struct RelaxedR1CSGadget {} @@ -135,7 +138,6 @@ pub struct WitnessVar { impl AllocVar, CF1> for WitnessVar where C: CurveGroup, - ::BaseField: PrimeField, { fn new_variable>>( cs: impl Into>>, @@ -160,6 +162,12 @@ where } } +impl WitnessVarOps for WitnessVar { + fn get_openings(&self) -> Vec<(&[FpVar], FpVar)> { + vec![(&self.E, self.rE.clone()), (&self.W, self.rW.clone())] + } +} + /// Circuit that implements the in-circuit checks needed for the onchain (Ethereum's EVM) /// verification. #[derive(Clone, Debug)] @@ -398,13 +406,7 @@ where (u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?; // 3.a u_i.x[0] == H(i, z_0, z_i, U_i) - let (u_i_x, U_i_vec) = U_i.clone().hash( - &sponge, - pp_hash.clone(), - i.clone(), - z_0.clone(), - z_i.clone(), - )?; + let (u_i_x, U_i_vec) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?; (u_i.x[0]).enforce_equal(&u_i_x)?; #[cfg(feature = "light-test")] diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index 02e6d3b..97150a0 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -13,6 +13,7 @@ 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,9 +39,11 @@ pub mod nifs; pub mod serialize; pub mod traits; pub mod zk; -use circuits::{AugmentedFCircuit, ChallengeGadget}; +use circuits::{AugmentedFCircuit, ChallengeGadget, CommittedInstanceVar}; use nifs::NIFS; +use super::traits::{CommittedInstanceOps, WitnessOps}; + /// Configuration for Nova's CycleFold circuit pub struct NovaCycleFoldConfig { _c: PhantomData, @@ -83,9 +86,8 @@ impl Absorb for CommittedInstance where C::ScalarField: Absorb, { - fn to_sponge_bytes(&self, _dest: &mut Vec) { - // This is never called - unimplemented!() + fn to_sponge_bytes(&self, dest: &mut Vec) { + C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); } fn to_sponge_field_elements(&self, dest: &mut Vec) { @@ -103,30 +105,15 @@ where } } -impl CommittedInstance -where - ::ScalarField: Absorb, - ::BaseField: ark_ff::PrimeField, -{ - /// hash implements the committed instance hash compatible with the gadget implemented in - /// nova/circuits.rs::CommittedInstanceVar.hash. - /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U_i` is the - /// `CommittedInstance`. - pub fn hash>( - &self, - sponge: &T, - pp_hash: C::ScalarField, // public params hash - i: C::ScalarField, - z_0: Vec, - z_i: Vec, - ) -> C::ScalarField { - let mut sponge = sponge.clone(); - sponge.absorb(&pp_hash); - sponge.absorb(&i); - sponge.absorb(&z_0); - sponge.absorb(&z_i); - sponge.absorb(&self); - sponge.squeeze_field_elements(1)[0] +impl CommittedInstanceOps for CommittedInstance { + type Var = CommittedInstanceVar; + + fn get_commitments(&self) -> Vec { + vec![self.cmW, self.cmE] + } + + fn is_incoming(&self) -> bool { + self.cmE == C::zero() && self.u == One::one() } } @@ -188,6 +175,14 @@ impl Witness { } } +impl WitnessOps for Witness { + type Var = WitnessVar; + + fn get_openings(&self) -> Vec<(&[C::ScalarField], C::ScalarField)> { + vec![(&self.W, self.rW), (&self.E, self.rE)] + } +} + #[derive(Debug, Clone)] pub struct PreprocessorParam where @@ -696,8 +691,8 @@ where &sponge, self.pp_hash, self.i + C1::ScalarField::one(), - self.z_0.clone(), - z_i1.clone(), + &self.z_0, + &z_i1, ); // u_{i+1}.x[1] = H(cf_U_{i+1}) let cf_u_i1_x: C1::ScalarField; @@ -907,7 +902,7 @@ where // check that u_i's output points to the running instance // u_i.X[0] == H(i, z_0, z_i, U_i) - let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, z_0, z_i.clone()); + let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, &z_0, &z_i); if expected_u_i_x != u_i.x[0] { return Err(Error::IVCVerificationFail); } diff --git a/folding-schemes/src/folding/nova/zk.rs b/folding-schemes/src/folding/nova/zk.rs index 25a216a..8b5ff14 100644 --- a/folding-schemes/src/folding/nova/zk.rs +++ b/folding-schemes/src/folding/nova/zk.rs @@ -36,6 +36,7 @@ use ark_std::{One, Zero}; use crate::{ arith::r1cs::{RelaxedR1CS, R1CS}, + folding::traits::CommittedInstanceOps, RngCore, }; use ark_crypto_primitives::sponge::{ @@ -226,7 +227,7 @@ where // b. Check computed hashes are correct let mut sponge = PoseidonSponge::::new(poseidon_config); - let expected_u_i_x = proof.U_i.hash(&sponge, pp_hash, i, z_0, z_i); + 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); } diff --git a/folding-schemes/src/folding/protogalaxy/circuits.rs b/folding-schemes/src/folding/protogalaxy/circuits.rs index 74a7332..4ec2fad 100644 --- a/folding-schemes/src/folding/protogalaxy/circuits.rs +++ b/folding-schemes/src/folding/protogalaxy/circuits.rs @@ -24,13 +24,16 @@ use super::{ CommittedInstance, CommittedInstanceVar, ProtoGalaxyCycleFoldConfig, }; use crate::{ - folding::circuits::{ - cyclefold::{ - CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, - CycleFoldConfig, NIFSFullGadget, + folding::{ + circuits::{ + cyclefold::{ + CycleFoldChallengeGadget, CycleFoldCommittedInstance, + CycleFoldCommittedInstanceVar, CycleFoldConfig, NIFSFullGadget, + }, + nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, + CF1, CF2, }, - nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, - CF1, CF2, + traits::CommittedInstanceVarOps, }, frontend::FCircuit, transcript::{AbsorbNonNativeGadget, TranscriptVar}, @@ -346,24 +349,12 @@ where // `transcript` is for challenge generation. let mut transcript = sponge.clone(); - // get z_{i+1} from the F circuit - let i_usize = self.i_usize; - let z_i1 = - self.F - .generate_step_constraints(cs.clone(), i_usize, z_i.clone(), external_inputs)?; - let is_basecase = i.is_zero()?; // Primary Part // P.1. Compute u_i.x // u_i.x[0] = H(i, z_0, z_i, U_i) - let (u_i_x, _) = U_i.clone().hash( - &sponge, - pp_hash.clone(), - i.clone(), - z_0.clone(), - z_i.clone(), - )?; + let (u_i_x, _) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?; // u_i.x[1] = H(cf_U_i) let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?; @@ -380,21 +371,27 @@ where )?; // P.4.a compute and check the first output of F' + + // get z_{i+1} from the F circuit + let z_i1 = + self.F + .generate_step_constraints(cs.clone(), self.i_usize, z_i, external_inputs)?; + // Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot}) // Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1}) let (u_i1_x, _) = U_i1.clone().hash( &sponge, - pp_hash.clone(), - i + FpVar::>::one(), - z_0.clone(), - z_i1.clone(), + &pp_hash, + &(i + FpVar::>::one()), + &z_0, + &z_i1, )?; let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash( &sponge, - pp_hash.clone(), - FpVar::>::one(), - z_0.clone(), - z_i1.clone(), + &pp_hash, + &FpVar::>::one(), + &z_0, + &z_i1, )?; let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?; x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?; diff --git a/folding-schemes/src/folding/protogalaxy/mod.rs b/folding-schemes/src/folding/protogalaxy/mod.rs index 9ee44de..37b2836 100644 --- a/folding-schemes/src/folding/protogalaxy/mod.rs +++ b/folding-schemes/src/folding/protogalaxy/mod.rs @@ -1,14 +1,14 @@ /// Implements the scheme described in [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf) use ark_crypto_primitives::sponge::{ - constraints::{AbsorbGadget, CryptographicSpongeVar}, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, + poseidon::{PoseidonConfig, PoseidonSponge}, Absorb, CryptographicSponge, }; use ark_ec::{CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, - fields::fp::FpVar, + eq::EqGadget, + fields::{fp::FpVar, FieldVar}, groups::{CurveVar, GroupOpsBounds}, R1CSVar, ToConstraintFieldGadget, }; @@ -44,6 +44,8 @@ pub(crate) mod utils; use circuits::AugmentedFCircuit; use folding::Folding; +use super::traits::{CommittedInstanceOps, CommittedInstanceVarOps, WitnessOps, WitnessVarOps}; + /// Configuration for ProtoGalaxy's CycleFold circuit pub struct ProtoGalaxyCycleFoldConfig { _c: PhantomData, @@ -83,30 +85,15 @@ impl CommittedInstance { } } -impl CommittedInstance -where - C::ScalarField: Absorb, - C::BaseField: PrimeField, -{ - /// hash implements the committed instance hash compatible with the gadget implemented in - /// CommittedInstanceVar.hash. - /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U_i` is the - /// `CommittedInstance`. - pub fn hash( - &self, - sponge: &PoseidonSponge, - pp_hash: C::ScalarField, - i: C::ScalarField, - z_0: Vec, - z_i: Vec, - ) -> C::ScalarField { - let mut sponge = sponge.clone(); - sponge.absorb(&pp_hash); - sponge.absorb(&i); - sponge.absorb(&z_0); - sponge.absorb(&z_i); - sponge.absorb(&self); - sponge.squeeze_field_elements(1)[0] +impl CommittedInstanceOps for CommittedInstance { + type Var = CommittedInstanceVar; + + fn get_commitments(&self) -> Vec { + vec![self.phi] + } + + fn is_incoming(&self) -> bool { + self.e == Zero::zero() && self.betas.is_empty() } } @@ -164,34 +151,29 @@ impl R1CSVar for CommittedInstanceVar { } } -impl CommittedInstanceVar -where - C::ScalarField: Absorb, - C::BaseField: PrimeField, -{ - /// hash implements the committed instance hash compatible with the native implementation from - /// CommittedInstance.hash. - /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the - /// `CommittedInstance`. - /// Additionally it returns the vector of the field elements from the self parameters, so they - /// can be reused in other gadgets avoiding recalculating (reconstraining) them. - #[allow(clippy::type_complexity)] - pub fn hash( - self, - sponge: &PoseidonSpongeVar>, - pp_hash: FpVar>, - i: FpVar>, - z_0: Vec>>, - z_i: Vec>>, - ) -> Result<(FpVar>, Vec>>), SynthesisError> { - let mut sponge = sponge.clone(); - let U_vec = self.to_sponge_field_elements()?; - sponge.absorb(&pp_hash)?; - sponge.absorb(&i)?; - sponge.absorb(&z_0)?; - sponge.absorb(&z_i)?; - sponge.absorb(&U_vec)?; - Ok((sponge.squeeze_field_elements(1)?.pop().unwrap(), U_vec)) +impl CommittedInstanceVarOps for CommittedInstanceVar { + type PointVar = NonNativeAffineVar; + + fn get_commitments(&self) -> Vec { + vec![self.phi.clone()] + } + + fn get_public_inputs(&self) -> &[FpVar>] { + &self.x + } + + fn enforce_incoming(&self) -> Result<(), SynthesisError> { + if self.betas.is_empty() { + self.e.enforce_equal(&FpVar::zero()) + } else { + Err(SynthesisError::Unsatisfiable) + } + } + + fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> { + self.betas.enforce_equal(&other.betas)?; + self.e.enforce_equal(&other.e)?; + self.x.enforce_equal(&other.x) } } @@ -224,6 +206,44 @@ impl Witness { } } +impl WitnessOps for Witness { + type Var = WitnessVar; + + fn get_openings(&self) -> Vec<(&[F], F)> { + vec![(&self.w, self.r_w)] + } +} + +/// In-circuit representation of the Witness associated to the CommittedInstance. +#[derive(Debug, Clone)] +pub struct WitnessVar { + pub W: Vec>, + pub rW: 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::new_variable(cs.clone(), || Ok(val.borrow().w.to_vec()), mode)?; + let rW = FpVar::new_variable(cs.clone(), || Ok(val.borrow().r_w), mode)?; + + Ok(Self { W, rW }) + }) + } +} + +impl WitnessVarOps for WitnessVar { + fn get_openings(&self) -> Vec<(&[FpVar], FpVar)> { + vec![(&self.W, self.rW.clone())] + } +} + #[derive(Debug, thiserror::Error, PartialEq)] pub enum ProtoGalaxyError { #[error("The remainder from G(X)-F(α)*L_0(X)) / Z(X) should be zero")] @@ -636,8 +656,8 @@ where &sponge, self.pp_hash, self.i + C1::ScalarField::one(), - self.z_0.clone(), - z_i1.clone(), + &self.z_0, + &z_i1, ); // `cf_U_{i+1}` (i.e., `cf_U_1`) is fixed to `cf_U_dummy`, so we // just use `self.cf_U_i = cf_U_0 = cf_U_dummy`. @@ -744,8 +764,8 @@ where &sponge, self.pp_hash, self.i + C1::ScalarField::one(), - self.z_0.clone(), - z_i1.clone(), + &self.z_0, + &z_i1, ); cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, self.pp_hash); @@ -874,7 +894,7 @@ where // check that u_i's output points to the running instance // u_i.X[0] == H(i, z_0, z_i, U_i) - let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, z_0, z_i.clone()); + let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, &z_0, &z_i); if expected_u_i_x != u_i.x[0] { return Err(Error::IVCVerificationFail); } diff --git a/folding-schemes/src/folding/protogalaxy/traits.rs b/folding-schemes/src/folding/protogalaxy/traits.rs index d735f6e..d67f79e 100644 --- a/folding-schemes/src/folding/protogalaxy/traits.rs +++ b/folding-schemes/src/folding/protogalaxy/traits.rs @@ -18,8 +18,8 @@ impl Absorb for CommittedInstance where C::ScalarField: Absorb, { - fn to_sponge_bytes(&self, _dest: &mut Vec) { - unimplemented!() + fn to_sponge_bytes(&self, dest: &mut Vec) { + C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); } fn to_sponge_field_elements(&self, dest: &mut Vec) { @@ -35,7 +35,7 @@ where // Implements the trait for absorbing ProtoGalaxy's CommittedInstanceVar in-circuit. impl AbsorbGadget for CommittedInstanceVar { fn to_sponge_bytes(&self) -> Result>, SynthesisError> { - unimplemented!() + FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) } fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { @@ -114,3 +114,46 @@ impl RelaxedR1CS, CommittedInstance unimplemented!() } } + +#[cfg(test)] +pub mod tests { + use super::*; + use ark_bn254::{Fr, G1Projective as Projective}; + use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; + use ark_relations::r1cs::ConstraintSystem; + use ark_std::UniformRand; + use rand::Rng; + + /// 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 t = rng.gen::() as usize; + let io_len = rng.gen::() as usize; + + let ci = CommittedInstance:: { + phi: Projective::rand(&mut rng), + betas: (0..t).map(|_| Fr::rand(&mut rng)).collect(), + e: Fr::rand(&mut rng), + x: (0..io_len).map(|_| Fr::rand(&mut rng)).collect(), + }; + + 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); + } +} diff --git a/folding-schemes/src/folding/traits.rs b/folding-schemes/src/folding/traits.rs new file mode 100644 index 0000000..5815397 --- /dev/null +++ b/folding-schemes/src/folding/traits.rs @@ -0,0 +1,121 @@ +use ark_crypto_primitives::sponge::{ + constraints::{AbsorbGadget, CryptographicSpongeVar}, + poseidon::constraints::PoseidonSpongeVar, + Absorb, +}; +use ark_ec::CurveGroup; +use ark_ff::PrimeField; +use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, ToConstraintFieldGadget}; +use ark_relations::r1cs::SynthesisError; + +use crate::{transcript::Transcript, Error}; + +use super::circuits::CF1; + +pub trait CommittedInstanceOps { + /// The in-circuit representation of the committed instance. + type Var: AllocVar> + CommittedInstanceVarOps; + /// `hash` implements the committed instance hash compatible with the + /// in-circuit implementation from `CommittedInstanceVarOps::hash`. + /// + /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and + /// `U_i` is the committed instance `self`. + fn hash>>( + &self, + sponge: &T, + pp_hash: CF1, // public params hash + i: CF1, + z_0: &[CF1], + z_i: &[CF1], + ) -> CF1 + where + CF1: Absorb, + Self: Sized + Absorb, + { + let mut sponge = sponge.clone(); + sponge.absorb(&pp_hash); + sponge.absorb(&i); + sponge.absorb(&z_0); + sponge.absorb(&z_i); + sponge.absorb(&self); + sponge.squeeze_field_elements(1)[0] + } + + /// Returns the commitments contained in the committed instance. + fn get_commitments(&self) -> Vec; + + /// Returns `true` if the committed instance is an incoming instance, and + /// `false` if it is a running instance. + fn is_incoming(&self) -> bool; + + /// Checks if the committed instance is an incoming instance. + fn check_incoming(&self) -> Result<(), Error> { + self.is_incoming() + .then_some(()) + .ok_or(Error::NotIncomingCommittedInstance) + } +} + +pub trait CommittedInstanceVarOps { + type PointVar: ToConstraintFieldGadget>; + /// `hash` implements the in-circuit committed instance hash compatible with + /// the native implementation from `CommittedInstanceOps::hash`. + /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and + /// `U_i` is the committed instance `self`. + /// + /// Additionally it returns the in-circuit representation of the committed + /// instance `self` as a vector of field elements, so they can be reused in + /// other gadgets avoiding recalculating (reconstraining) them. + #[allow(clippy::type_complexity)] + fn hash( + &self, + sponge: &PoseidonSpongeVar>, + pp_hash: &FpVar>, + i: &FpVar>, + z_0: &[FpVar>], + z_i: &[FpVar>], + ) -> Result<(FpVar>, Vec>>), SynthesisError> + where + Self: AbsorbGadget>, + { + let mut sponge = sponge.clone(); + let U_vec = self.to_sponge_field_elements()?; + sponge.absorb(&pp_hash)?; + sponge.absorb(&i)?; + sponge.absorb(&z_0)?; + sponge.absorb(&z_i)?; + sponge.absorb(&U_vec)?; + Ok((sponge.squeeze_field_elements(1)?.pop().unwrap(), U_vec)) + } + + /// Returns the commitments contained in the committed instance. + fn get_commitments(&self) -> Vec; + + /// Returns the public inputs contained in the committed instance. + fn get_public_inputs(&self) -> &[FpVar>]; + + /// Generates constraints to enforce that the committed instance is an + /// incoming instance. + fn enforce_incoming(&self) -> Result<(), SynthesisError>; + + /// Generates constraints to enforce that the committed instance `self` is + /// partially equal to another committed instance `other`. + /// Here, only field elements are compared, while commitments (points) are + /// not. + fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError>; +} + +pub trait WitnessOps { + /// The in-circuit representation of the witness. + type Var: AllocVar + WitnessVarOps; + + /// Returns the openings (i.e., the values being committed to and the + /// randomness) contained in the witness. + fn get_openings(&self) -> Vec<(&[F], F)>; +} + +pub trait WitnessVarOps { + /// Returns the openings (i.e., the values being committed to and the + /// randomness) contained in the witness. + fn get_openings(&self) -> Vec<(&[FpVar], FpVar)>; +} diff --git a/folding-schemes/src/lib.rs b/folding-schemes/src/lib.rs index e4b22ef..6ed0805 100644 --- a/folding-schemes/src/lib.rs +++ b/folding-schemes/src/lib.rs @@ -43,6 +43,8 @@ pub enum Error { IVCVerificationFail, #[error("zkIVC verification failed")] zkIVCVerificationFail, + #[error("Committed instance is expected to be an incoming (fresh) instance")] + NotIncomingCommittedInstance, #[error("R1CS instance is expected to not be relaxed")] R1CSUnrelaxedFail, #[error("Could not find the inner ConstraintSystem")]