arnaucube
arnaucube/sonobePublic Self-hosted repository
Unify the computation of digests and challenges in different folding schemes (#94)

* Remove the trait bound `C::BaseField: PrimeField` for better DX

* Methods in `TranscriptVar` now exactly matches the ones in `Transcript`

* Add `ProtoGalaxyTranscriptVar` and `CommittedInstanceVar` for protogalaxy

* betas are unnecessary in "plain" (incoming) instances

* Absorb the result of `get_challenge_nbits` as well

* `ProtoGalaxyTranscript` now allows absorbing mulitple instances

* Always return `Result<(), SynthesisError>` in `ProtoGalaxyTranscriptVar`

* Impl `Transcript{Var}` for `PoseidonSponge{Var}` directly and remove `PoseidonTranscript{Var}`

* `Transcript::absorb_point` doesn't need to return `Error`

* Add `AbsorbNonNative` trait for hashing non-native values

Note that now `absorb_point` only supports hashing points whose BaseField is equal to the sponge's field

* More efficient `TranscriptVar::absorb_point` by securely removing `is_inf`

* Use `sponge` and `transcript` consistently

* Clarify the usage of `AbsorbNonNative{Gadget}`

* Generic `sponge` and `transcript` params

* Avoid unstable `associated_type_bounds`

* Reuse `sponge` in hypernova

* Clean up redundant imports

* Remove unstable code

* Clarify the usage of `absorb_point` and `absorb_nonnative`
winderica winderica@gmail.com
Mon, 08 Jul 2024 09:25:08 +0100
commit

16d51d757b2c80a9f1cc4ac29930235895064ff4

parent

c17fcf56c61777226ac5e54564d5a38b2ebe4e10

36 files changed, 1028 insertions(+), 1031 deletions(-)

jump to
M folding-schemes/src/arith/ccs.rs → folding-schemes/src/arith/ccs.rs

@@ -114,7 +114,6 @@ #[cfg(test)]

pub mod tests { use super::*; use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z}; - use ark_ff::PrimeField; use ark_pallas::Fr; pub fn get_test_ccs<F: PrimeField>() -> CCS<F> {
M folding-schemes/src/arith/r1cs.rs → folding-schemes/src/arith/r1cs.rs

@@ -140,7 +140,6 @@ pub mod tests {

use super::*; use crate::utils::vec::tests::{to_F_matrix, to_F_vec}; - use ark_ff::PrimeField; use ark_pallas::Fr; pub fn get_test_r1cs<F: PrimeField>() -> R1CS<F> {
M folding-schemes/src/commitment/ipa.rs → folding-schemes/src/commitment/ipa.rs

@@ -97,7 +97,7 @@ }

fn prove( params: &Self::ProverParams, - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<C::ScalarField>, P: &C, // commitment a: &[C::ScalarField], // vector blind: &C::ScalarField,

@@ -131,7 +131,7 @@ l = vec![];

r = vec![]; } - transcript.absorb_point(P)?; + transcript.absorb_nonnative(P); let x = transcript.get_challenge(); // challenge value at which we evaluate let s = transcript.get_challenge(); let U = C::generator().mul(s);

@@ -162,8 +162,8 @@ L[j] = C::msm_unchecked(&G[m..], &a[..m]) + U.mul(inner_prod(&a[..m], &b[m..])?);

R[j] = C::msm_unchecked(&G[..m], &a[m..]) + U.mul(inner_prod(&a[m..], &b[..m])?); } // get challenge for the j-th round - transcript.absorb_point(&L[j])?; - transcript.absorb_point(&R[j])?; + transcript.absorb_nonnative(&L[j]); + transcript.absorb_nonnative(&R[j]); u[j] = transcript.get_challenge(); let uj = u[j];

@@ -225,21 +225,21 @@ }

fn verify( params: &Self::VerifierParams, - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<C::ScalarField>, P: &C, // commitment proof: &Self::Proof, ) -> Result<(), Error> { let (p, _r) = (proof.0.clone(), proof.1); let k = p.L.len(); - transcript.absorb_point(P)?; + transcript.absorb_nonnative(P); let x = transcript.get_challenge(); // challenge value at which we evaluate let s = transcript.get_challenge(); let U = C::generator().mul(s); let mut u: Vec<C::ScalarField> = vec![C::ScalarField::zero(); k]; for i in (0..k).rev() { - transcript.absorb_point(&p.L[i])?; - transcript.absorb_point(&p.R[i])?; + transcript.absorb_nonnative(&p.L[i]); + transcript.absorb_nonnative(&p.R[i]); u[i] = transcript.get_challenge(); } let challenge = (x, U, u);

@@ -566,15 +566,15 @@ }

#[cfg(test)] mod tests { + use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; use ark_ec::Group; use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; - use ark_r1cs_std::{alloc::AllocVar, bits::boolean::Boolean, eq::EqGadget}; + use ark_r1cs_std::eq::EqGadget; use ark_relations::r1cs::ConstraintSystem; - use ark_std::UniformRand; use std::ops::Mul; use super::*; - use crate::transcript::poseidon::{poseidon_canonical_config, PoseidonTranscript}; + use crate::transcript::poseidon::poseidon_canonical_config; #[test] fn test_ipa() {

@@ -592,9 +592,9 @@ let (params, _) = IPA::<Projective, hiding>::setup(&mut rng, d).unwrap();

let poseidon_config = poseidon_canonical_config::<Fr>(); // init Prover's transcript - let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config); // init Verifier's transcript - let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config); // a is the vector that we're committing let a: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))

@@ -636,9 +636,9 @@ let (params, _) = IPA::<Projective, hiding>::setup(&mut rng, d).unwrap();

let poseidon_config = poseidon_canonical_config::<Fr>(); // init Prover's transcript - let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config); // init Verifier's transcript - let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config); let mut a: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng)) .take(d / 2)

@@ -666,15 +666,15 @@

// circuit let cs = ConstraintSystem::<Fq>::new_ref(); - let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config); - transcript_v.absorb_point(&cm).unwrap(); + let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config); + transcript_v.absorb_nonnative(&cm); let challenge = transcript_v.get_challenge(); // challenge value at which we evaluate let s = transcript_v.get_challenge(); let U = Projective::generator().mul(s); let mut u: Vec<Fr> = vec![Fr::zero(); k]; for i in (0..k).rev() { - transcript_v.absorb_point(&proof.0.L[i]).unwrap(); - transcript_v.absorb_point(&proof.0.R[i]).unwrap(); + transcript_v.absorb_nonnative(&proof.0.L[i]); + transcript_v.absorb_nonnative(&proof.0.R[i]); u[i] = transcript_v.get_challenge(); }
M folding-schemes/src/commitment/kzg.rs → folding-schemes/src/commitment/kzg.rs

@@ -156,13 +156,13 @@ /// with the main difference being the removal of the blinding factors and the no-dependency to

/// the Pairing trait. fn prove( params: &Self::ProverParams, - transcript: &mut impl Transcript<E::G1>, + transcript: &mut impl Transcript<E::ScalarField>, cm: &E::G1, v: &[E::ScalarField], _blind: &E::ScalarField, _rng: Option<&mut dyn RngCore>, ) -> Result<Self::Proof, Error> { - transcript.absorb_point(cm)?; + transcript.absorb_nonnative(cm); let challenge = transcript.get_challenge(); Self::prove_with_challenge(params, challenge, v, _blind, _rng) }

@@ -214,11 +214,11 @@ }

fn verify( params: &Self::VerifierParams, - transcript: &mut impl Transcript<E::G1>, + transcript: &mut impl Transcript<E::ScalarField>, cm: &E::G1, proof: &Self::Proof, ) -> Result<(), Error> { - transcript.absorb_point(cm)?; + transcript.absorb_nonnative(cm); let challenge = transcript.get_challenge(); Self::verify_with_challenge(params, challenge, cm, proof) }

@@ -286,17 +286,18 @@

#[cfg(test)] mod tests { use ark_bn254::{Bn254, Fr, G1Projective as G1}; + use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; use ark_std::{test_rng, UniformRand}; use super::*; - use crate::transcript::poseidon::{poseidon_canonical_config, PoseidonTranscript}; + use crate::transcript::poseidon::poseidon_canonical_config; #[test] fn test_kzg_commitment_scheme() { let mut rng = &mut test_rng(); let poseidon_config = poseidon_canonical_config::<Fr>(); - let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config); - let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config); + let transcript_p = &mut PoseidonSponge::<Fr>::new(&poseidon_config); + let transcript_v = &mut PoseidonSponge::<Fr>::new(&poseidon_config); let n = 10; let (pk, vk): (ProverKey<G1>, VerifierKey<Bn254>) =
M folding-schemes/src/commitment/mod.rs → folding-schemes/src/commitment/mod.rs

@@ -34,7 +34,7 @@ ) -> Result<C, Error>;

fn prove( params: &Self::ProverParams, - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<C::ScalarField>, cm: &C, v: &[C::ScalarField], blind: &C::ScalarField,

@@ -53,7 +53,7 @@ ) -> Result<Self::Proof, Error>;

fn verify( params: &Self::VerifierParams, - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<C::ScalarField>, cm: &C, proof: &Self::Proof, ) -> Result<(), Error>;

@@ -72,7 +72,10 @@ #[cfg(test)]

mod tests { use super::*; use ark_bn254::{Bn254, Fr, G1Projective as G1}; - use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; + use ark_crypto_primitives::sponge::{ + poseidon::{PoseidonConfig, PoseidonSponge}, + Absorb, CryptographicSponge, + }; use ark_poly_commit::kzg10::VerifierKey; use ark_std::Zero; use ark_std::{test_rng, UniformRand};

@@ -80,10 +83,7 @@

use super::ipa::IPA; use super::kzg::{ProverKey, KZG}; use super::pedersen::Pedersen; - use crate::transcript::{ - poseidon::{poseidon_canonical_config, PoseidonTranscript}, - Transcript, - }; + use crate::transcript::poseidon::poseidon_canonical_config; #[test] fn test_homomorphic_property_using_Commitment_trait() {

@@ -153,7 +153,7 @@ let cm_3 = cm_1 + cm_2.mul(r);

let v_3: Vec<C::ScalarField> = v_1.iter().zip(v_2).map(|(a, b)| *a + (r * b)).collect(); // compute the proof of the cm_3 - let transcript_p = &mut PoseidonTranscript::<C>::new(poseidon_config); + let transcript_p = &mut PoseidonSponge::<C::ScalarField>::new(poseidon_config); let proof = CS::prove( prover_params, transcript_p,

@@ -165,7 +165,7 @@ )

.unwrap(); // verify the opening proof - let transcript_v = &mut PoseidonTranscript::<C>::new(poseidon_config); + let transcript_v = &mut PoseidonSponge::<C::ScalarField>::new(poseidon_config); CS::verify(verifier_params, transcript_v, &cm_3, &proof).unwrap(); } }
M folding-schemes/src/commitment/pedersen.rs → folding-schemes/src/commitment/pedersen.rs

@@ -81,13 +81,13 @@ }

fn prove( params: &Self::ProverParams, - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<C::ScalarField>, cm: &C, v: &[C::ScalarField], r: &C::ScalarField, // blinding factor _rng: Option<&mut dyn RngCore>, ) -> Result<Self::Proof, Error> { - transcript.absorb_point(cm)?; + transcript.absorb_nonnative(cm); let r1 = transcript.get_challenge(); let d = transcript.get_challenges(v.len());

@@ -98,7 +98,7 @@ if H {

R += params.h.mul(r1); } - transcript.absorb_point(&R)?; + transcript.absorb_nonnative(&R); let e = transcript.get_challenge(); let challenge = (r1, d, R, e);

@@ -133,14 +133,14 @@ }

fn verify( params: &Self::VerifierParams, - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<C::ScalarField>, cm: &C, proof: &Proof<C>, ) -> Result<(), Error> { - transcript.absorb_point(cm)?; + transcript.absorb_nonnative(cm); transcript.get_challenge(); // r_1 transcript.get_challenges(proof.u.len()); // d - transcript.absorb_point(&proof.R)?; + transcript.absorb_nonnative(&proof.R); let e = transcript.get_challenge(); Self::verify_with_challenge(params, e, cm, proof) }

@@ -217,14 +217,14 @@ }

#[cfg(test)] mod tests { + use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; use ark_ff::{BigInteger, PrimeField}; use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget}; use ark_relations::r1cs::ConstraintSystem; - use ark_std::UniformRand; use super::*; - use crate::transcript::poseidon::{poseidon_canonical_config, PoseidonTranscript}; + use crate::transcript::poseidon::poseidon_canonical_config; #[test] fn test_pedersen() {

@@ -240,9 +240,9 @@ let (params, _) = Pedersen::<Projective>::setup(&mut rng, n).unwrap();

let poseidon_config = poseidon_canonical_config::<Fr>(); // init Prover's transcript - let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config); // init Verifier's transcript - let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config); let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng)) .take(n)
M folding-schemes/src/folding/circuits/cyclefold.rs → folding-schemes/src/folding/circuits/cyclefold.rs

@@ -1,23 +1,13 @@

/// Contains [CycleFold](https://eprint.iacr.org/2023/1192.pdf) related circuits and functions that /// are shared across the different folding schemes -use ark_crypto_primitives::{ - crh::{ - poseidon::constraints::{CRHGadget, CRHParametersVar}, - CRHSchemeGadget, - }, - sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, - Absorb, CryptographicSponge, - }, -}; -use ark_ec::{AffineRepr, CurveGroup, Group}; -use ark_ff::{BigInteger, Field, PrimeField, ToConstraintField}; +use ark_crypto_primitives::sponge::{Absorb, CryptographicSponge}; +use ark_ec::{CurveGroup, Group}; +use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, boolean::Boolean, eq::EqGadget, - fields::{fp::FpVar, FieldVar}, + fields::fp::FpVar, groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget,

@@ -26,7 +16,7 @@ use ark_relations::r1cs::{

ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, }; use ark_std::fmt::Debug; -use ark_std::{One, Zero}; +use ark_std::Zero; use core::{borrow::Borrow, marker::PhantomData}; use super::{nonnative::uint::NonNativeUintVar, CF2};

@@ -35,6 +25,7 @@ use crate::commitment::CommitmentScheme;

use crate::constants::N_BITS_RO; use crate::folding::nova::{nifs::NIFS, CommittedInstance, Witness}; use crate::frontend::FCircuit; +use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar}; use crate::Error; /// Public inputs length for the CycleFoldCircuit: |[r, p1.x,y, p2.x,y, p3.x,y]|

@@ -77,7 +68,7 @@ })

} } -impl<C, GC> ToConstraintFieldGadget<CF2<C>> for CycleFoldCommittedInstanceVar<C, GC> +impl<C, GC> AbsorbNonNativeGadget<C::BaseField> for CycleFoldCommittedInstanceVar<C, GC> where C: CurveGroup, GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,

@@ -87,25 +78,24 @@ {

/// Extracts the underlying field elements from `CycleFoldCommittedInstanceVar`, in the order /// of `u`, `x`, `cmE.x`, `cmE.y`, `cmW.x`, `cmW.y`, `cmE.is_inf || cmW.is_inf` (|| is for /// concat). - fn to_constraint_field(&self) -> Result<Vec<FpVar<CF2<C>>>, SynthesisError> { + fn to_native_sponge_field_elements(&self) -> Result<Vec<FpVar<CF2<C>>>, SynthesisError> { let mut cmE_elems = self.cmE.to_constraint_field()?; let mut cmW_elems = self.cmW.to_constraint_field()?; - let cmE_is_inf = cmE_elems.pop().unwrap(); - let cmW_is_inf = cmW_elems.pop().unwrap(); - // Concatenate `cmE_is_inf` and `cmW_is_inf` to save constraints for CRHGadget::evaluate - let is_inf = cmE_is_inf.double()? + cmW_is_inf; + // See `transcript/poseidon.rs: TranscriptVar::absorb_point` for details + // why the last element is unnecessary. + cmE_elems.pop(); + cmW_elems.pop(); Ok([ - self.u.to_constraint_field()?, + self.u.to_native_sponge_field_elements()?, self.x .iter() - .map(|i| i.to_constraint_field()) + .map(|i| i.to_native_sponge_field_elements()) .collect::<Result<Vec<_>, _>>()? .concat(), cmE_elems, cmW_elems, - vec![is_inf], ] .concat()) }

@@ -124,16 +114,16 @@ /// for CycleFold. 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( + pub fn hash<S: CryptographicSponge, T: TranscriptVar<CF2<C>, S>>( self, - crh_params: &CRHParametersVar<CF2<C>>, + sponge: &T, pp_hash: FpVar<CF2<C>>, // public params hash ) -> Result<(FpVar<CF2<C>>, Vec<FpVar<CF2<C>>>), SynthesisError> { - let U_vec = self.to_constraint_field()?; - Ok(( - CRHGadget::evaluate(crh_params, &[vec![pp_hash], U_vec.clone()].concat())?, - U_vec, - )) + let mut sponge = sponge.clone(); + let U_vec = self.to_native_sponge_field_elements()?; + sponge.absorb(&pp_hash)?; + sponge.absorb(&U_vec)?; + Ok((sponge.squeeze_field_elements(1)?.pop().unwrap(), U_vec)) } }

@@ -254,65 +244,33 @@ <C as CurveGroup>::BaseField: PrimeField,

<C as CurveGroup>::BaseField: Absorb, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { - pub fn get_challenge_native( - poseidon_config: &PoseidonConfig<C::BaseField>, + pub fn get_challenge_native<T: Transcript<C::BaseField>>( + transcript: &mut T, pp_hash: C::BaseField, // public params hash U_i: CommittedInstance<C>, u_i: CommittedInstance<C>, cmT: C, - ) -> Result<Vec<bool>, Error> { - let mut sponge = PoseidonSponge::<C::BaseField>::new(poseidon_config); - - let mut U_vec = U_i.to_field_elements().unwrap(); - let mut u_vec = u_i.to_field_elements().unwrap(); - let (cmT_x, cmT_y, cmT_is_inf) = match cmT.into_affine().xy() { - Some((&x, &y)) => (x, y, C::BaseField::zero()), - None => ( - C::BaseField::zero(), - C::BaseField::zero(), - C::BaseField::one(), - ), - }; - - let U_cm_is_inf = U_vec.pop().unwrap(); - let u_cm_is_inf = u_vec.pop().unwrap(); - - // Concatenate `U_i.cmE_is_inf`, `U_i.cmW_is_inf`, `u_i.cmE_is_inf`, `u_i.cmW_is_inf`, `cmT_is_inf` - // to save constraints for sponge.squeeze_bits in the corresponding circuit - let is_inf = U_cm_is_inf * CF2::<C>::from(8u8) + u_cm_is_inf.double() + cmT_is_inf; - - let input = [vec![pp_hash], U_vec, u_vec, vec![cmT_x, cmT_y, is_inf]].concat(); - sponge.absorb(&input); - let bits = sponge.squeeze_bits(N_BITS_RO); - Ok(bits) + ) -> Vec<bool> { + transcript.absorb(&pp_hash); + transcript.absorb_nonnative(&U_i); + transcript.absorb_nonnative(&u_i); + transcript.absorb_point(&cmT); + transcript.squeeze_bits(N_BITS_RO) } // compatible with the native get_challenge_native - pub fn get_challenge_gadget( - cs: ConstraintSystemRef<C::BaseField>, - poseidon_config: &PoseidonConfig<C::BaseField>, + pub fn get_challenge_gadget<S: CryptographicSponge, T: TranscriptVar<C::BaseField, S>>( + transcript: &mut T, pp_hash: FpVar<C::BaseField>, // public params hash - mut U_i_vec: Vec<FpVar<C::BaseField>>, + U_i_vec: Vec<FpVar<C::BaseField>>, u_i: CycleFoldCommittedInstanceVar<C, GC>, cmT: GC, ) -> Result<Vec<Boolean<C::BaseField>>, SynthesisError> { - let mut sponge = PoseidonSpongeVar::<C::BaseField>::new(cs, poseidon_config); - - let mut u_i_vec = u_i.to_constraint_field()?; - let mut cmT_vec = cmT.to_constraint_field()?; - - let U_cm_is_inf = U_i_vec.pop().unwrap(); - let u_cm_is_inf = u_i_vec.pop().unwrap(); - let cmT_is_inf = cmT_vec.pop().unwrap(); - - // Concatenate `U_i.cmE_is_inf`, `U_i.cmW_is_inf`, `u_i.cmE_is_inf`, `u_i.cmW_is_inf`, `cmT_is_inf` - // to save constraints for sponge.squeeze_bits - let is_inf = U_cm_is_inf * CF2::<C>::from(8u8) + u_cm_is_inf.double()? + cmT_is_inf; - - let input = [vec![pp_hash], U_i_vec, u_i_vec, cmT_vec, vec![is_inf]].concat(); - sponge.absorb(&input)?; - let bits = sponge.squeeze_bits(N_BITS_RO)?; - Ok(bits) + transcript.absorb(&pp_hash)?; + transcript.absorb(&U_i_vec)?; + transcript.absorb_nonnative(&u_i)?; + transcript.absorb_point(&cmT)?; + transcript.squeeze_bits(N_BITS_RO) } }

@@ -383,7 +341,7 @@ /// scheme struct because it is used both by Nova & HyperNova's CycleFold.

#[allow(clippy::type_complexity)] #[allow(clippy::too_many_arguments)] pub fn fold_cyclefold_circuit<C1, GC1, C2, GC2, FC, CS1, CS2>( - poseidon_config: &PoseidonConfig<C1::ScalarField>, + transcript: &mut impl Transcript<C1::ScalarField>, cf_r1cs: R1CS<C2::ScalarField>, cf_cs_params: CS2::ProverParams, pp_hash: C1::ScalarField, // public params hash

@@ -445,12 +403,12 @@ &cf_U_i,

)?; let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_native( - poseidon_config, + transcript, pp_hash, cf_U_i.clone(), cf_u_i.clone(), cf_cmT, - )?; + ); let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) .expect("cf_r_bits out of bounds");

@@ -463,9 +421,11 @@

#[cfg(test)] pub mod tests { use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective}; - use ark_ff::BigInteger; + use ark_crypto_primitives::sponge::{ + constraints::CryptographicSpongeVar, + poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, + }; use ark_r1cs_std::R1CSVar; - use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; use super::*;

@@ -583,6 +543,7 @@ #[test]

fn test_cyclefold_challenge_gadget() { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_canonical_config::<Fq>(); + let mut transcript = PoseidonSponge::<Fq>::new(&poseidon_config); let u_i = CommittedInstance::<Projective> { cmE: Projective::zero(), // zero on purpose, so we test also the zero point case

@@ -605,13 +566,12 @@

// compute the challenge natively let pp_hash = Fq::from(42u32); // only for test let r_bits = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_native( - &poseidon_config, + &mut transcript, pp_hash, U_i.clone(), u_i.clone(), cmT, - ) - .unwrap(); + ); let cs = ConstraintSystem::<Fq>::new_ref(); let u_iVar =

@@ -625,13 +585,14 @@ Ok(U_i.clone())

}) .unwrap(); let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap(); + let mut transcript_var = + PoseidonSpongeVar::<Fq>::new(ConstraintSystem::<Fq>::new_ref(), &poseidon_config); let pp_hashVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(pp_hash)).unwrap(); let r_bitsVar = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_gadget( - cs.clone(), - &poseidon_config, + &mut transcript_var, pp_hashVar, - U_iVar.to_constraint_field().unwrap(), + U_iVar.to_native_sponge_field_elements().unwrap(), u_iVar, cmTVar, )

@@ -649,6 +610,7 @@ #[test]

fn test_cyclefold_hash_gadget() { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_canonical_config::<Fq>(); + let sponge = PoseidonSponge::<Fq>::new(&poseidon_config); let U_i = CommittedInstance::<Projective> { cmE: Projective::rand(&mut rng),

@@ -659,7 +621,7 @@ .take(CF_IO_LEN)

.collect(), }; let pp_hash = Fq::from(42u32); // only for test - let h = U_i.hash_cyclefold(&poseidon_config, pp_hash).unwrap(); + let h = U_i.hash_cyclefold(&sponge, pp_hash); let cs = ConstraintSystem::<Fq>::new_ref(); let U_iVar =

@@ -670,7 +632,7 @@ .unwrap();

let pp_hashVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(pp_hash)).unwrap(); let (hVar, _) = U_iVar .hash( - &CRHParametersVar::new_constant(cs.clone(), poseidon_config).unwrap(), + &PoseidonSpongeVar::new(cs.clone(), &poseidon_config), pp_hashVar, ) .unwrap();
M folding-schemes/src/folding/circuits/nonnative/affine.rs → folding-schemes/src/folding/circuits/nonnative/affine.rs

@@ -1,5 +1,4 @@

use ark_ec::{AffineRepr, CurveGroup}; -use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, fields::fp::FpVar,

@@ -9,16 +8,15 @@ use ark_relations::r1cs::{Namespace, SynthesisError};

use ark_std::Zero; use core::borrow::Borrow; +use crate::transcript::{AbsorbNonNative, AbsorbNonNativeGadget}; + use super::uint::{nonnative_field_to_field_elements, NonNativeUintVar}; /// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native /// field, over the constraint field. It is not intended to perform operations, but just to contain /// the affine coordinates in order to perform hash operations of the point. #[derive(Debug, Clone)] -pub struct NonNativeAffineVar<C: CurveGroup> -where - <C as CurveGroup>::BaseField: PrimeField, -{ +pub struct NonNativeAffineVar<C: CurveGroup> { pub x: NonNativeUintVar<C::ScalarField>, pub y: NonNativeUintVar<C::ScalarField>, }

@@ -26,7 +24,6 @@

impl<C> AllocVar<C, C::ScalarField> for NonNativeAffineVar<C> where C: CurveGroup, - <C as CurveGroup>::BaseField: PrimeField, { fn new_variable<T: Borrow<C>>( cs: impl Into<Namespace<C::ScalarField>>,

@@ -37,21 +34,18 @@ f().and_then(|val| {

let cs = cs.into(); let affine = val.borrow().into_affine(); - let zero_point = (&C::BaseField::zero(), &C::BaseField::zero()); - let xy = affine.xy().unwrap_or(zero_point); + let zero = (&C::BaseField::zero(), &C::BaseField::zero()); + let (x, y) = affine.xy().unwrap_or(zero); - let x = NonNativeUintVar::new_variable(cs.clone(), || Ok(*xy.0), mode)?; - let y = NonNativeUintVar::new_variable(cs.clone(), || Ok(*xy.1), mode)?; + let x = NonNativeUintVar::new_variable(cs.clone(), || Ok(*x), mode)?; + let y = NonNativeUintVar::new_variable(cs.clone(), || Ok(*y), mode)?; Ok(Self { x, y }) }) } } -impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineVar<C> -where - <C as CurveGroup>::BaseField: PrimeField, -{ +impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineVar<C> { // Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in // the circuit. fn to_constraint_field(&self) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> {

@@ -63,50 +57,50 @@ }

/// The out-circuit counterpart of `NonNativeAffineVar::to_constraint_field` #[allow(clippy::type_complexity)] -pub fn nonnative_affine_to_field_elements<C: CurveGroup>( +fn nonnative_affine_to_field_elements<C: CurveGroup>( p: C, -) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError> -where - <C as CurveGroup>::BaseField: PrimeField, -{ +) -> (Vec<C::ScalarField>, Vec<C::ScalarField>) { let affine = p.into_affine(); - if affine.is_zero() { - let x = nonnative_field_to_field_elements(&C::BaseField::zero()); - let y = nonnative_field_to_field_elements(&C::BaseField::zero()); - return Ok((x, y)); - } + let zero = (&C::BaseField::zero(), &C::BaseField::zero()); + let (x, y) = affine.xy().unwrap_or(zero); - let (x, y) = affine.xy().unwrap(); let x = nonnative_field_to_field_elements(x); let y = nonnative_field_to_field_elements(y); - Ok((x, y)) + (x, y) } -impl<C: CurveGroup> NonNativeAffineVar<C> -where - <C as CurveGroup>::BaseField: PrimeField, -{ - // A wrapper of `point_to_nonnative_limbs_custom_opt` with constraints-focused optimization - // type (which is the default optimization type for arkworks' Groth16). - // Used for extracting a list of field elements of type `C::ScalarField` from the public input +impl<C: CurveGroup> NonNativeAffineVar<C> { + // Extracts a list of field elements of type `C::ScalarField` from the public input // `p`, in exactly the same way as how `NonNativeAffineVar` is represented as limbs of type // `FpVar` in-circuit. #[allow(clippy::type_complexity)] pub fn inputize(p: C) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError> { let affine = p.into_affine(); - if affine.is_zero() { - let x = NonNativeUintVar::inputize(C::BaseField::zero()); - let y = NonNativeUintVar::inputize(C::BaseField::zero()); - return Ok((x, y)); - } + let zero = (&C::BaseField::zero(), &C::BaseField::zero()); + let (x, y) = affine.xy().unwrap_or(zero); - let (x, y) = affine.xy().unwrap(); let x = NonNativeUintVar::inputize(*x); let y = NonNativeUintVar::inputize(*y); Ok((x, y)) } } +impl<C: CurveGroup> AbsorbNonNative<C::ScalarField> for C { + fn to_native_sponge_field_elements(&self, dest: &mut Vec<C::ScalarField>) { + let (x, y) = nonnative_affine_to_field_elements(*self); + dest.extend(x); + dest.extend(y); + } +} + +impl<C: CurveGroup> AbsorbNonNativeGadget<C::ScalarField> for NonNativeAffineVar<C> { + fn to_native_sponge_field_elements( + &self, + ) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> { + self.to_constraint_field() + } +} + #[cfg(test)] mod tests { use super::*;

@@ -132,7 +126,7 @@ // check that point_to_nonnative_limbs returns the expected values

let mut rng = ark_std::test_rng(); let p = Projective::rand(&mut rng); let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap(); - let (x, y) = nonnative_affine_to_field_elements(p).unwrap(); + let (x, y) = nonnative_affine_to_field_elements(p); assert_eq!( pVar.to_constraint_field().unwrap().value().unwrap(), [x, y].concat()
M folding-schemes/src/folding/circuits/nonnative/uint.rs → folding-schemes/src/folding/circuits/nonnative/uint.rs

@@ -3,7 +3,7 @@ borrow::Borrow,

cmp::{max, min}, }; -use ark_ff::{BigInteger, One, PrimeField, Zero}; +use ark_ff::{BigInteger, Field, One, PrimeField, Zero}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, boolean::Boolean,

@@ -16,7 +16,10 @@ use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError};

use num_bigint::BigUint; use num_integer::Integer; -use crate::utils::gadgets::{MatrixGadget, SparseMatrixVar, VectorGadget}; +use crate::{ + transcript::{AbsorbNonNative, AbsorbNonNativeGadget}, + utils::gadgets::{MatrixGadget, SparseMatrixVar, VectorGadget}, +}; /// `LimbVar` represents a single limb of a non-native unsigned integer in the /// circuit.

@@ -229,7 +232,7 @@ Ok(Self(limbs))

} } -impl<F: PrimeField, G: PrimeField> AllocVar<G, F> for NonNativeUintVar<F> { +impl<F: PrimeField, G: Field> AllocVar<G, F> for NonNativeUintVar<F> { fn new_variable<T: Borrow<G>>( cs: impl Into<Namespace<F>>, f: impl FnOnce() -> Result<T, SynthesisError>,

@@ -237,7 +240,8 @@ mode: AllocationMode,

) -> Result<Self, SynthesisError> { let cs = cs.into().cs(); let v = f()?; - let v = v.borrow(); + assert_eq!(G::extension_degree(), 1); + let v = v.borrow().to_base_prime_field_elements().next().unwrap(); let mut limbs = vec![];

@@ -256,8 +260,12 @@ }

} impl<F: PrimeField> NonNativeUintVar<F> { - pub fn inputize<T: PrimeField>(x: T) -> Vec<F> { - x.into_bigint() + pub fn inputize<T: Field>(x: T) -> Vec<F> { + assert_eq!(T::extension_degree(), 1); + x.to_base_prime_field_elements() + .next() + .unwrap() + .into_bigint() .to_bits_le() .chunks(Self::bits_per_limb()) .map(|chunk| F::from_bigint(F::BigInt::from_bits_le(chunk)).unwrap())

@@ -802,14 +810,40 @@ )

} } +// If we impl `AbsorbNonNative` directly for `PrimeField`, rustc will complain +// that this impl conflicts with the impl for `CurveGroup`. +// Therefore, we instead impl `AbsorbNonNative` for a slice of `PrimeField` as a +// workaround. +impl<TargetField: PrimeField, BaseField: PrimeField> AbsorbNonNative<BaseField> + for [TargetField] +{ + fn to_native_sponge_field_elements(&self, dest: &mut Vec<BaseField>) { + self.iter() + .for_each(|x| dest.extend(&nonnative_field_to_field_elements(x))); + } +} + +impl<F: PrimeField> AbsorbNonNativeGadget<F> for NonNativeUintVar<F> { + fn to_native_sponge_field_elements(&self) -> Result<Vec<FpVar<F>>, SynthesisError> { + self.to_constraint_field() + } +} + /// The out-circuit counterpart of `NonNativeUintVar::to_constraint_field` -pub fn nonnative_field_to_field_elements<TargetField: PrimeField, BaseField: PrimeField>( +pub(super) fn nonnative_field_to_field_elements<TargetField: Field, BaseField: PrimeField>( f: &TargetField, ) -> Vec<BaseField> { - let bits = f.into_bigint().to_bits_le(); + assert_eq!(TargetField::extension_degree(), 1); + let bits = f + .to_base_prime_field_elements() + .next() + .unwrap() + .into_bigint() + .to_bits_le(); let bits_per_limb = BaseField::MODULUS_BIT_SIZE as usize - 1; - let num_limbs = (TargetField::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb); + let num_limbs = + (TargetField::BasePrimeField::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb); let mut limbs = bits .chunks(bits_per_limb)

@@ -897,7 +931,6 @@ mod tests {

use std::error::Error; use super::*; - use ark_ff::Field; use ark_pallas::{Fq, Fr}; use ark_relations::r1cs::ConstraintSystem; use ark_std::{test_rng, UniformRand};
M folding-schemes/src/folding/circuits/sum_check.rs → folding-schemes/src/folding/circuits/sum_check.rs

@@ -3,8 +3,7 @@ /// Some changes:

/// - Typings to better stick to ark_poly's API /// - Uses `folding-schemes`' own `TranscriptVar` trait and `PoseidonTranscriptVar` struct /// - API made closer to gadgets found in `folding-schemes` -use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; +use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge}; use ark_ff::PrimeField; use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; use ark_r1cs_std::{

@@ -19,7 +18,7 @@

use crate::utils::espresso::sum_check::SumCheck; use crate::utils::virtual_polynomial::VPAuxInfo; use crate::{ - transcript::{poseidon::PoseidonTranscript, TranscriptVar}, + transcript::TranscriptVar, utils::sum_check::{structs::IOPProof, IOPSumCheck}, };

@@ -82,35 +81,27 @@ }

} #[derive(Clone, Debug)] -pub struct IOPProofVar<C: CurveGroup> { +pub struct IOPProofVar<F: PrimeField> { // We have to be generic over a CurveGroup because instantiating a IOPProofVar will call IOPSumCheck which requires a CurveGroup - pub proofs: Vec<DensePolynomialVar<C::ScalarField>>, // = IOPProof.proofs - pub claim: FpVar<C::ScalarField>, + pub proofs: Vec<DensePolynomialVar<F>>, + pub claim: FpVar<F>, } -impl<C: CurveGroup> AllocVar<IOPProof<C::ScalarField>, C::ScalarField> for IOPProofVar<C> -where - <C as Group>::ScalarField: Absorb, -{ - fn new_variable<T: Borrow<IOPProof<C::ScalarField>>>( - cs: impl Into<Namespace<C::ScalarField>>, +impl<F: PrimeField + Absorb> AllocVar<IOPProof<F>, F> for IOPProofVar<F> { + fn new_variable<T: Borrow<IOPProof<F>>>( + cs: impl Into<Namespace<F>>, f: impl FnOnce() -> Result<T, SynthesisError>, mode: AllocationMode, ) -> Result<Self, SynthesisError> { f().and_then(|c| { let cs = cs.into(); - let cp: &IOPProof<C::ScalarField> = c.borrow(); - let claim = IOPSumCheck::<C, PoseidonTranscript<C>>::extract_sum(cp); - let claim = FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(claim), mode)?; - let mut proofs = - Vec::<DensePolynomialVar<C::ScalarField>>::with_capacity(cp.proofs.len()); + let cp: &IOPProof<F> = c.borrow(); + let claim = IOPSumCheck::<F, PoseidonSponge<F>>::extract_sum(cp); + let claim = FpVar::<F>::new_variable(cs.clone(), || Ok(claim), mode)?; + let mut proofs = Vec::<DensePolynomialVar<F>>::with_capacity(cp.proofs.len()); for proof in cp.proofs.iter() { let poly = DensePolynomial::from_coefficients_slice(&proof.coeffs); - let proof = DensePolynomialVar::<C::ScalarField>::new_variable( - cs.clone(), - || Ok(poly), - mode, - )?; + let proof = DensePolynomialVar::<F>::new_variable(cs.clone(), || Ok(poly), mode)?; proofs.push(proof); } Ok(Self { proofs, claim })

@@ -149,28 +140,28 @@ }

} #[derive(Debug, Clone)] -pub struct SumCheckVerifierGadget<C: CurveGroup> { - _f: PhantomData<C>, +pub struct SumCheckVerifierGadget<F: PrimeField> { + _f: PhantomData<F>, } -impl<C: CurveGroup> SumCheckVerifierGadget<C> { +impl<F: PrimeField> SumCheckVerifierGadget<F> { #[allow(clippy::type_complexity)] - pub fn verify( - iop_proof_var: &IOPProofVar<C>, - poly_aux_info_var: &VPAuxInfoVar<C::ScalarField>, - transcript_var: &mut impl TranscriptVar<C::ScalarField>, - enabled: Boolean<C::ScalarField>, - ) -> Result<(Vec<FpVar<C::ScalarField>>, Vec<FpVar<C::ScalarField>>), SynthesisError> { + pub fn verify<S: CryptographicSponge, T: TranscriptVar<F, S>>( + iop_proof_var: &IOPProofVar<F>, + poly_aux_info_var: &VPAuxInfoVar<F>, + transcript_var: &mut T, + enabled: Boolean<F>, + ) -> Result<(Vec<FpVar<F>>, Vec<FpVar<F>>), SynthesisError> { let mut e_vars = vec![iop_proof_var.claim.clone()]; - let mut r_vars: Vec<FpVar<C::ScalarField>> = Vec::new(); - transcript_var.absorb(poly_aux_info_var.num_variables.clone())?; - transcript_var.absorb(poly_aux_info_var.max_degree.clone())?; + let mut r_vars: Vec<FpVar<F>> = Vec::new(); + transcript_var.absorb(&poly_aux_info_var.num_variables)?; + transcript_var.absorb(&poly_aux_info_var.max_degree)?; for poly_var in iop_proof_var.proofs.iter() { let res = poly_var.eval_at_one() + poly_var.eval_at_zero(); let e_var = e_vars.last().ok_or(SynthesisError::Unsatisfiable)?; res.conditional_enforce_equal(e_var, &enabled)?; - transcript_var.absorb_vec(&poly_var.coeffs)?; + transcript_var.absorb(&poly_var.coeffs)?; let r_i_var = transcript_var.get_challenge()?; e_vars.push(poly_var.evaluate(&r_i_var)); r_vars.push(r_i_var);

@@ -182,49 +173,35 @@ }

#[cfg(test)] mod tests { - use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; - use ark_ec::CurveGroup; - use ark_ff::Field; - use ark_pallas::{Fr, Projective}; - use ark_poly::{ - univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial, - MultilinearExtension, Polynomial, + use ark_crypto_primitives::sponge::{ + constraints::CryptographicSpongeVar, + poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, }; - use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; + use ark_pallas::Fr; + use ark_poly::{DenseMultilinearExtension, MultilinearExtension, Polynomial}; + use ark_r1cs_std::R1CSVar; use ark_relations::r1cs::ConstraintSystem; use std::sync::Arc; use super::*; use crate::{ - folding::circuits::sum_check::{IOPProofVar, VPAuxInfoVar}, - transcript::{ - poseidon::{poseidon_canonical_config, PoseidonTranscript, PoseidonTranscriptVar}, - Transcript, TranscriptVar, - }, - utils::{ - sum_check::{structs::IOPProof, IOPSumCheck, SumCheck}, - virtual_polynomial::VirtualPolynomial, - }, + transcript::poseidon::poseidon_canonical_config, + utils::virtual_polynomial::VirtualPolynomial, }; pub type TestSumCheckProof<F> = (VirtualPolynomial<F>, PoseidonConfig<F>, IOPProof<F>); /// Primarily used for testing the sumcheck gadget /// Returns a random virtual polynomial, the poseidon config used and the associated sumcheck proof - pub fn get_test_sumcheck_proof<C: CurveGroup>( + pub fn get_test_sumcheck_proof<F: PrimeField + Absorb>( num_vars: usize, - ) -> TestSumCheckProof<C::ScalarField> - where - <C as ark_ec::Group>::ScalarField: Absorb, - { + ) -> TestSumCheckProof<F> { let mut rng = ark_std::test_rng(); - let poseidon_config: PoseidonConfig<C::ScalarField> = - poseidon_canonical_config::<C::ScalarField>(); - let mut poseidon_transcript_prove = PoseidonTranscript::<C>::new(&poseidon_config); + let poseidon_config: PoseidonConfig<F> = poseidon_canonical_config::<F>(); + let mut poseidon_transcript_prove = PoseidonSponge::<F>::new(&poseidon_config); let poly_mle = DenseMultilinearExtension::rand(num_vars, &mut rng); - let virtual_poly = - VirtualPolynomial::new_from_mle(&Arc::new(poly_mle), C::ScalarField::ONE); - let sum_check: IOPProof<C::ScalarField> = IOPSumCheck::<C, PoseidonTranscript<C>>::prove( + let virtual_poly = VirtualPolynomial::new_from_mle(&Arc::new(poly_mle), F::ONE); + let sum_check: IOPProof<F> = IOPSumCheck::<F, PoseidonSponge<F>>::prove( &virtual_poly, &mut poseidon_transcript_prove, )

@@ -237,15 +214,15 @@ fn test_sum_check_circuit() {

for num_vars in 1..15 { let cs = ConstraintSystem::<Fr>::new_ref(); let (virtual_poly, poseidon_config, sum_check) = - get_test_sumcheck_proof::<Projective>(num_vars); - let mut poseidon_var: PoseidonTranscriptVar<Fr> = - PoseidonTranscriptVar::new(cs.clone(), &poseidon_config); + get_test_sumcheck_proof::<Fr>(num_vars); + let mut poseidon_var: PoseidonSpongeVar<Fr> = + PoseidonSpongeVar::new(cs.clone(), &poseidon_config); let iop_proof_var = - IOPProofVar::<Projective>::new_witness(cs.clone(), || Ok(&sum_check)).unwrap(); + IOPProofVar::<Fr>::new_witness(cs.clone(), || Ok(&sum_check)).unwrap(); let poly_aux_info_var = VPAuxInfoVar::<Fr>::new_witness(cs.clone(), || Ok(virtual_poly.aux_info)).unwrap(); let enabled = Boolean::<Fr>::new_witness(cs.clone(), || Ok(true)).unwrap(); - let res = SumCheckVerifierGadget::<Projective>::verify( + let res = SumCheckVerifierGadget::<Fr>::verify( &iop_proof_var, &poly_aux_info_var, &mut poseidon_var,

@@ -256,8 +233,7 @@ assert!(res.is_ok());

let (circuit_evals, r_challenges) = res.unwrap(); // 1. assert claim from circuit is equal to the one from the sum-check - let claim: Fr = - IOPSumCheck::<Projective, PoseidonTranscript<Projective>>::extract_sum(&sum_check); + let claim: Fr = IOPSumCheck::<Fr, PoseidonSponge<Fr>>::extract_sum(&sum_check); assert_eq!(circuit_evals[0].value().unwrap(), claim); // 2. assert that all in-circuit evaluations are equal to the ones from the sum-check
M folding-schemes/src/folding/hypernova/cccs.rs → folding-schemes/src/folding/hypernova/cccs.rs

@@ -1,3 +1,4 @@

+use ark_crypto_primitives::sponge::Absorb; use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_std::One;

@@ -9,6 +10,7 @@

use super::Witness; use crate::arith::{ccs::CCS, Arith}; use crate::commitment::CommitmentScheme; +use crate::transcript::AbsorbNonNative; use crate::utils::mle::dense_vec_to_dense_mle; use crate::utils::vec::mat_vec_mul; use crate::utils::virtual_polynomial::{build_eq_x_r_vec, VirtualPolynomial};

@@ -115,6 +117,26 @@ // would take too much, this checks the CCS relation of the CCCS.

ccs.check_relation(&z)?; Ok(()) + } +} + +impl<C: CurveGroup> Absorb for CCCS<C> +where + C::ScalarField: Absorb, +{ + fn to_sponge_bytes(&self, _dest: &mut Vec<u8>) { + // This is never called + unimplemented!() + } + + fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) { + // 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.C + .to_native_sponge_field_elements_as_vec() + .to_sponge_field_elements(dest); + self.x.to_sponge_field_elements(dest); } }
M folding-schemes/src/folding/hypernova/circuits.rs → folding-schemes/src/folding/hypernova/circuits.rs

@@ -1,7 +1,8 @@

/// Implementation of [HyperNova](https://eprint.iacr.org/2023/573.pdf) circuits -use ark_crypto_primitives::crh::{ - poseidon::constraints::{CRHGadget, CRHParametersVar}, - CRHSchemeGadget, +use ark_crypto_primitives::sponge::{ + constraints::CryptographicSpongeVar, + poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, + CryptographicSponge, }; use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; use ark_ec::{CurveGroup, Group};

@@ -45,10 +46,7 @@ use crate::utils::virtual_polynomial::VPAuxInfo;

use crate::Error; use crate::{ arith::{ccs::CCS, r1cs::extract_r1cs}, - transcript::{ - poseidon::{PoseidonTranscript, PoseidonTranscriptVar}, - Transcript, TranscriptVar, - }, + transcript::TranscriptVar, }; /// Committed CCS instance

@@ -142,12 +140,13 @@ /// can be reused in other gadgets avoiding recalculating (reconstraining) them.

#[allow(clippy::type_complexity)] pub fn hash( self, - crh_params: &CRHParametersVar<CF1<C>>, + sponge: &PoseidonSpongeVar<CF1<C>>, pp_hash: FpVar<CF1<C>>, i: FpVar<CF1<C>>, z_0: Vec<FpVar<CF1<C>>>, z_i: Vec<FpVar<CF1<C>>>, ) -> Result<(FpVar<CF1<C>>, Vec<FpVar<CF1<C>>>), SynthesisError> { + let mut sponge = sponge.clone(); let U_vec = [ self.C.to_constraint_field()?, vec![self.u],

@@ -156,18 +155,19 @@ self.r_x,

self.v, ] .concat(); - let input = [vec![pp_hash, i], z_0, z_i, U_vec.clone()].concat(); - Ok(( - CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)?, - U_vec, - )) + 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)) } } /// ProofVar defines a multifolding proof #[derive(Debug)] pub struct ProofVar<C: CurveGroup> { - pub sc_proof: IOPProofVar<C>, + pub sc_proof: IOPProofVar<C::ScalarField>, #[allow(clippy::type_complexity)] pub sigmas_thetas: (Vec<Vec<FpVar<CF1<C>>>>, Vec<Vec<FpVar<CF1<C>>>>), }

@@ -185,7 +185,7 @@ ) -> Result<Self, SynthesisError> {

f().and_then(|val| { let cs = cs.into(); - let sc_proof = IOPProofVar::<C>::new_variable( + let sc_proof = IOPProofVar::<C::ScalarField>::new_variable( cs.clone(), || Ok(val.borrow().sc_proof.clone()), mode,

@@ -223,12 +223,11 @@ {

/// Runs (in-circuit) the NIMFS.V, which outputs the new folded LCCCS instance together with /// the rho_bits, which will be used in other parts of the AugmentedFCircuit #[allow(clippy::type_complexity)] - pub fn verify( + pub fn verify<S: CryptographicSponge, T: TranscriptVar<C::ScalarField, S>>( cs: ConstraintSystemRef<CF1<C>>, // only used the CCS params, not the matrices ccs: &CCS<C::ScalarField>, - mut transcript: impl TranscriptVar<C::ScalarField>, - + transcript: &mut T, running_instances: &[LCCCSVar<C>], new_instances: &[CCCSVar<C>], proof: ProofVar<C>,

@@ -244,24 +243,24 @@ U_i.r_x.clone(),

U_i.v.clone(), ] .concat(); - transcript.absorb_vec(&v)?; + transcript.absorb(&v)?; } for u_i in new_instances { let v = [u_i.C.to_constraint_field()?, u_i.x.clone()].concat(); - transcript.absorb_vec(&v)?; + transcript.absorb(&v)?; } // get the challenges let gamma_scalar_raw = C::ScalarField::from_le_bytes_mod_order(b"gamma"); let gamma_scalar: FpVar<CF1<C>> = FpVar::<CF1<C>>::new_constant(cs.clone(), gamma_scalar_raw)?; - transcript.absorb(gamma_scalar)?; + transcript.absorb(&gamma_scalar)?; let gamma: FpVar<CF1<C>> = transcript.get_challenge()?; let beta_scalar_raw = C::ScalarField::from_le_bytes_mod_order(b"beta"); let beta_scalar: FpVar<CF1<C>> = FpVar::<CF1<C>>::new_constant(cs.clone(), beta_scalar_raw)?; - transcript.absorb(beta_scalar)?; + transcript.absorb(&beta_scalar)?; let beta: Vec<FpVar<CF1<C>>> = transcript.get_challenges(ccs.s)?; let vp_aux_info_raw = VPAuxInfo::<C::ScalarField> {

@@ -283,10 +282,10 @@ }

} // verify the interactive part of the sumcheck - let (e_vars, r_vars) = SumCheckVerifierGadget::<C>::verify( + let (e_vars, r_vars) = SumCheckVerifierGadget::<C::ScalarField>::verify( &proof.sc_proof, &vp_aux_info, - &mut transcript, + transcript, enabled.clone(), )?;

@@ -312,7 +311,7 @@

// get the folding challenge let rho_scalar_raw = C::ScalarField::from_le_bytes_mod_order(b"rho"); let rho_scalar: FpVar<CF1<C>> = FpVar::<CF1<C>>::new_constant(cs.clone(), rho_scalar_raw)?; - transcript.absorb(rho_scalar)?; + transcript.absorb(&rho_scalar)?; let rho_bits: Vec<Boolean<CF1<C>>> = transcript.get_challenge_nbits(N_BITS_RO)?; let rho = Boolean::le_bits_to_fp_var(&rho_bits)?;

@@ -560,10 +559,10 @@ let U_i = LCCCS::<C1>::dummy(ccs.l, ccs.t, ccs.s);

let w_i = W_i.clone(); let u_i = CCCS::<C1>::dummy(ccs.l); - let mut transcript_p: PoseidonTranscript<C1> = - PoseidonTranscript::<C1>::new(&self.poseidon_config.clone()); + let mut transcript_p: PoseidonSponge<C1::ScalarField> = + PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config.clone()); // since this is only for the number of constraints, no need to absorb the pp_hash here - let (nimfs_proof, U_i1, _, _) = NIMFS::<C1, PoseidonTranscript<C1>>::prove( + let (nimfs_proof, U_i1, _, _) = NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::prove( &mut transcript_p, &ccs, &[U_i.clone()],

@@ -671,10 +670,7 @@ Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone()))

})?; let cf_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf_cmT.unwrap_or_else(C2::zero)))?; - let crh_params = CRHParametersVar::<C1::ScalarField>::new_constant( - cs.clone(), - self.poseidon_config.clone(), - )?; + let sponge = PoseidonSpongeVar::<C1::ScalarField>::new(cs.clone(), &self.poseidon_config); // get z_{i+1} from the F circuit let i_usize = self.i_usize.unwrap_or(0);

@@ -689,14 +685,14 @@ // 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( - &crh_params, + &sponge, pp_hash.clone(), i.clone(), z_0.clone(), z_i.clone(), )?; // u_i.x[1] = H(cf_U_i) - let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&crh_params, pp_hash.clone())?; + let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?; // P.2. Construct u_i let u_i = CCCSVar::<C1> {

@@ -712,13 +708,12 @@ // P.3. NIMFS.verify, obtains U_{i+1} by folding [U_i] & [u_i].

// Notice that NIMFSGadget::fold_committed_instance does not fold C. We set `U_i1.C` to // unconstrained witnesses `U_i1_C` respectively. Its correctness will be checked on the // other curve. - let mut transcript = - PoseidonTranscriptVar::<C1::ScalarField>::new(cs.clone(), &self.poseidon_config); - transcript.absorb(pp_hash.clone())?; + let mut transcript = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config); + transcript.absorb(&pp_hash)?; let (mut U_i1, rho_bits) = NIMFSGadget::<C1>::verify( cs.clone(), &self.ccs.clone(), - transcript, + &mut transcript, &[U_i.clone()], &[u_i.clone()], nimfs_proof,

@@ -728,14 +723,14 @@ U_i1.C = U_i1_C;

// P.4.a compute and check the first output of F' let (u_i1_x, _) = U_i1.clone().hash( - &crh_params, + &sponge, pp_hash.clone(), i + FpVar::<CF1<C1>>::one(), z_0.clone(), z_i1.clone(), )?; let (u_i1_x_base, _) = LCCCSVar::new_constant(cs.clone(), U_dummy)?.hash( - &crh_params, + &sponge, pp_hash.clone(), FpVar::<CF1<C1>>::one(), z_0.clone(),

@@ -776,8 +771,7 @@ // C.3. nifs.verify (fold_committed_instance), obtains cf_U_{i+1} by folding cf_u_i & cf_U_i.

// compute cf_r = H(cf_u_i, cf_U_i, cf_cmT) // cf_r_bits is denoted by rho* in the paper. let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget( - cs.clone(), - &self.poseidon_config, + &mut transcript, pp_hash.clone(), cf_U_i_vec, cf_u_i.clone(),

@@ -802,10 +796,10 @@ // Back to Primary Part

// P.4.b compute and check the second output of F' // Base case: u_{i+1}.x[1] == H(cf_U_{\bot}) // Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1}) - let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&crh_params, pp_hash.clone())?; + let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge, pp_hash.clone())?; let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::new_constant(cs.clone(), cf_u_dummy)? - .hash(&crh_params, pp_hash)?; + .hash(&sponge, pp_hash)?; let cf_x = FpVar::new_input(cs.clone(), || { Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?)) })?;

@@ -820,34 +814,23 @@ mod tests {

use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective}; use ark_ff::BigInteger; use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; - use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar}; use ark_std::{test_rng, UniformRand}; use std::time::Instant; use super::*; use crate::{ arith::{ - ccs::{ - tests::{get_test_ccs, get_test_z}, - CCS, - }, + ccs::tests::{get_test_ccs, get_test_z}, r1cs::extract_w_x, }, commitment::{pedersen::Pedersen, CommitmentScheme}, folding::{ circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit}, - hypernova::{ - nimfs::NIMFS, - utils::{compute_c, compute_sigmas_thetas}, - Witness, - }, - nova::{traits::NovaR1CS, CommittedInstance, Witness as NovaWitness}, + hypernova::utils::{compute_c, compute_sigmas_thetas}, + nova::{traits::NovaR1CS, Witness as NovaWitness}, }, frontend::tests::CubicFCircuit, - transcript::{ - poseidon::{poseidon_canonical_config, PoseidonTranscript, PoseidonTranscriptVar}, - Transcript, - }, + transcript::poseidon::poseidon_canonical_config, utils::get_cm_coordinates, };

@@ -996,12 +979,11 @@ }

// Prover's transcript let poseidon_config = poseidon_canonical_config::<Fr>(); - let mut transcript_p: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config); // Run the prover side of the multifolding let (proof, folded_lcccs, folded_witness, _) = - NIMFS::<Projective, PoseidonTranscript<Projective>>::prove( + NIMFS::<Projective, PoseidonSponge<Fr>>::prove( &mut transcript_p, &ccs, &lcccs_instances,

@@ -1012,11 +994,10 @@ )

.unwrap(); // Verifier's transcript - let mut transcript_v: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_v: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config); // Run the verifier side of the multifolding - let folded_lcccs_v = NIMFS::<Projective, PoseidonTranscript<Projective>>::verify( + let folded_lcccs_v = NIMFS::<Projective, PoseidonSponge<Fr>>::verify( &mut transcript_v, &ccs, &lcccs_instances,

@@ -1039,13 +1020,13 @@ Vec::<CCCSVar<Projective>>::new_witness(cs.clone(), || Ok(cccs_instances.clone()))

.unwrap(); let proofVar = ProofVar::<Projective>::new_witness(cs.clone(), || Ok(proof.clone())).unwrap(); - let transcriptVar = PoseidonTranscriptVar::<Fr>::new(cs.clone(), &poseidon_config); + let mut transcriptVar = PoseidonSpongeVar::<Fr>::new(cs.clone(), &poseidon_config); let enabled = Boolean::<Fr>::new_witness(cs.clone(), || Ok(true)).unwrap(); let (folded_lcccsVar, _) = NIMFSGadget::<Projective>::verify( cs.clone(), &ccs, - transcriptVar, + &mut transcriptVar, &lcccs_instancesVar, &cccs_instancesVar, proofVar,

@@ -1061,6 +1042,7 @@ #[test]

pub fn test_lcccs_hash() { let mut rng = test_rng(); let poseidon_config = poseidon_canonical_config::<Fr>(); + let sponge = PoseidonSponge::<Fr>::new(&poseidon_config); let ccs = get_test_ccs(); let z1 = get_test_z::<Fr>(3);

@@ -1077,12 +1059,11 @@ .to_lcccs::<_, _, Pedersen<Projective>>(&mut rng, &pedersen_params, &z1)

.unwrap(); let h = lcccs .clone() - .hash(&poseidon_config, pp_hash, i, z_0.clone(), z_i.clone()) - .unwrap(); + .hash(&sponge, pp_hash, i, z_0.clone(), z_i.clone()); let cs = ConstraintSystem::<Fr>::new_ref(); - let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap(); + let spongeVar = PoseidonSpongeVar::<Fr>::new(cs.clone(), &poseidon_config); let pp_hashVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(pp_hash)).unwrap(); let iVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(i)).unwrap(); let z_0Var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap();

@@ -1091,7 +1072,7 @@ let lcccsVar = LCCCSVar::<Projective>::new_witness(cs.clone(), || Ok(lcccs)).unwrap();

let (hVar, _) = lcccsVar .clone() .hash( - &crh_params, + &spongeVar, pp_hashVar, iVar.clone(), z_0Var.clone(),

@@ -1108,6 +1089,7 @@ #[test]

pub fn test_augmented_f_circuit() { let mut rng = test_rng(); let poseidon_config = poseidon_canonical_config::<Fr>(); + let sponge = PoseidonSponge::<Fr>::new(&poseidon_config); let start = Instant::now(); let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();

@@ -1161,15 +1143,8 @@ let mut u_i = u_dummy.clone();

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( - &poseidon_config, - pp_hash, - Fr::zero(), - z_0.clone(), - z_i.clone(), - ) - .unwrap(), - cf_U_i.hash_cyclefold(&poseidon_config, pp_hash).unwrap(), + U_i.hash(&sponge, pp_hash, Fr::zero(), z_0.clone(), z_i.clone()), + cf_U_i.hash_cyclefold(&sponge, pp_hash), ]; let n_steps: usize = 4;

@@ -1185,19 +1160,11 @@ if i == 0 {

W_i1 = Witness::<Fr>::dummy(&ccs); U_i1 = LCCCS::dummy(ccs.l, ccs.t, ccs.s); - let u_i1_x = U_i1 - .hash( - &poseidon_config, - pp_hash, - Fr::one(), - z_0.clone(), - z_i1.clone(), - ) - .unwrap(); + let u_i1_x = U_i1.hash(&sponge, pp_hash, Fr::one(), z_0.clone(), z_i1.clone()); // hash the initial (dummy) CycleFold instance, which is used as the 2nd public // input in the AugmentedFCircuit - let cf_u_i1_x = cf_U_i.hash_cyclefold(&poseidon_config, pp_hash).unwrap(); + let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); augmented_f_circuit = AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {

@@ -1225,12 +1192,12 @@ cf_x: Some(cf_u_i1_x),

cf_cmT: None, }; } else { - let mut transcript_p: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config.clone()); + let mut transcript_p: PoseidonSponge<Fr> = + PoseidonSponge::<Fr>::new(&poseidon_config.clone()); transcript_p.absorb(&pp_hash); let (rho_bits, nimfs_proof); (nimfs_proof, U_i1, W_i1, rho_bits) = - NIMFS::<Projective, PoseidonTranscript<Projective>>::prove( + NIMFS::<Projective, PoseidonSponge<Fr>>::prove( &mut transcript_p, &ccs, &[U_i.clone()],

@@ -1243,15 +1210,8 @@

// sanity check: check the folded instance relation U_i1.check_relation(&ccs, &W_i1).unwrap(); - let u_i1_x = U_i1 - .hash( - &poseidon_config, - pp_hash, - iFr + Fr::one(), - z_0.clone(), - z_i1.clone(), - ) - .unwrap(); + let u_i1_x = + U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), z_0.clone(), z_i1.clone()); let rho_Fq = Fq::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); // CycleFold part:

@@ -1282,7 +1242,7 @@ CubicFCircuit<Fr>,

Pedersen<Projective>, Pedersen<Projective2>, >( - &poseidon_config, + &mut transcript_p, cf_r1cs.clone(), cf_pedersen_params.clone(), pp_hash,

@@ -1295,7 +1255,7 @@ .unwrap();

// hash the CycleFold folded instance, which is used as the 2nd public input in the // AugmentedFCircuit - let cf_u_i1_x = cf_U_i1.hash_cyclefold(&poseidon_config, pp_hash).unwrap(); + let cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, pp_hash); augmented_f_circuit = AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {

@@ -1346,16 +1306,9 @@ assert_eq!(w_i.w, r1cs_w_i1);

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( - &poseidon_config, - pp_hash, - iFr + Fr::one(), - z_0.clone(), - z_i1.clone(), - ) - .unwrap(); - let expected_cf_U_i1_x = cf_U_i.hash_cyclefold(&poseidon_config, pp_hash).unwrap(); + let expected_u_i1_x = + U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), z_0.clone(), z_i1.clone()); + 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); assert_eq!(u_i.x[1], expected_cf_U_i1_x);
M folding-schemes/src/folding/hypernova/lcccs.rs → folding-schemes/src/folding/hypernova/lcccs.rs

@@ -1,7 +1,4 @@

-use ark_crypto_primitives::{ - crh::{poseidon::CRH, CRHScheme}, - sponge::{poseidon::PoseidonConfig, Absorb}, -}; +use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; use ark_ff::PrimeField; use ark_poly::DenseMultilinearExtension;

@@ -12,7 +9,7 @@

use super::Witness; use crate::arith::ccs::CCS; use crate::commitment::CommitmentScheme; -use crate::folding::circuits::nonnative::affine::nonnative_affine_to_field_elements; +use crate::transcript::{AbsorbNonNative, Transcript}; use crate::utils::mle::dense_vec_to_dense_mle; use crate::utils::vec::mat_vec_mul; use crate::Error;

@@ -118,6 +115,29 @@ Ok(())

} } +impl<C: CurveGroup> Absorb for LCCCS<C> +where + C::ScalarField: Absorb, +{ + fn to_sponge_bytes(&self, _dest: &mut Vec<u8>) { + // This is never called + unimplemented!() + } + + fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) { + // 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.C + .to_native_sponge_field_elements_as_vec() + .to_sponge_field_elements(dest); + self.u.to_sponge_field_elements(dest); + self.x.to_sponge_field_elements(dest); + self.r_x.to_sponge_field_elements(dest); + self.v.to_sponge_field_elements(dest); + } +} + impl<C: CurveGroup> LCCCS<C> where <C as Group>::ScalarField: Absorb,

@@ -126,32 +146,21 @@ {

/// [`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( + pub fn hash<T: Transcript<C::ScalarField>>( &self, - poseidon_config: &PoseidonConfig<C::ScalarField>, + sponge: &T, pp_hash: C::ScalarField, i: C::ScalarField, z_0: Vec<C::ScalarField>, z_i: Vec<C::ScalarField>, - ) -> Result<C::ScalarField, Error> { - let (C_x, C_y) = nonnative_affine_to_field_elements::<C>(self.C)?; - - CRH::<C::ScalarField>::evaluate( - poseidon_config, - vec![ - vec![pp_hash, i], - z_0, - z_i, - C_x, - C_y, - vec![self.u], - self.x.clone(), - self.r_x.clone(), - self.v.clone(), - ] - .concat(), - ) - .map_err(|e| Error::Other(e.to_string())) + ) -> 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] } }

@@ -161,7 +170,6 @@ use ark_pallas::{Fr, Projective};

use ark_std::test_rng; use ark_std::One; use ark_std::UniformRand; - use ark_std::Zero; use std::sync::Arc; use super::*;
M folding-schemes/src/folding/hypernova/mod.rs → folding-schemes/src/folding/hypernova/mod.rs

@@ -1,5 +1,8 @@

/// Implements the scheme described in [HyperNova](https://eprint.iacr.org/2023/573.pdf) -use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; +use ark_crypto_primitives::sponge::{ + poseidon::{PoseidonConfig, PoseidonSponge}, + Absorb, CryptographicSponge, +}; use ark_ec::{CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};

@@ -18,6 +21,10 @@ use cccs::CCCS;

use lcccs::LCCCS; use nimfs::NIMFS; +use crate::arith::{ + ccs::CCS, + r1cs::{extract_w_x, R1CS}, +}; use crate::commitment::CommitmentScheme; use crate::folding::circuits::{ cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit},

@@ -31,13 +38,6 @@ use crate::frontend::FCircuit;

use crate::utils::{get_cm_coordinates, pp_hash}; use crate::Error; use crate::FoldingScheme; -use crate::{ - arith::{ - ccs::CCS, - r1cs::{extract_w_x, R1CS}, - }, - transcript::{poseidon::PoseidonTranscript, Transcript}, -}; /// Witness for the LCCCS & CCCS, containing the w vector, and the r_w used as randomness in the Pedersen commitment. #[derive(Debug, Clone, Eq, PartialEq)]

@@ -233,6 +233,8 @@ F: FC,

z_0: Vec<C1::ScalarField>, ) -> Result<Self, Error> { let (pp, vp) = params; + // `sponge` is for digest computation. + let sponge = PoseidonSponge::<C1::ScalarField>::new(&pp.poseidon_config); // prepare the HyperNova's AugmentedFCircuit and CycleFold's circuits and obtain its CCS // and R1CS respectively

@@ -258,13 +260,13 @@ let (cf_W_dummy, cf_U_dummy): (NovaWitness<C2>, CommittedInstance<C2>) =

cf_r1cs.dummy_instance(); u_dummy.x = vec![ U_dummy.hash( - &pp.poseidon_config, + &sponge, pp_hash, C1::ScalarField::zero(), z_0.clone(), z_0.clone(), - )?, - cf_U_dummy.hash_cyclefold(&pp.poseidon_config, pp_hash)?, + ), + cf_U_dummy.hash_cyclefold(&sponge, pp_hash), ]; // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the

@@ -299,6 +301,9 @@ &mut self,

mut rng: impl RngCore, external_inputs: Vec<C1::ScalarField>, ) -> Result<(), Error> { + // `sponge` is for digest computation. + let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config); + let augmented_f_circuit: AugmentedFCircuit<C1, C2, GC2, FC>; if self.z_i.len() != self.F.state_len() {

@@ -339,18 +344,16 @@ W_i1 = Witness::<C1::ScalarField>::dummy(&self.ccs);

U_i1 = LCCCS::dummy(self.ccs.l, self.ccs.t, self.ccs.s); let u_i1_x = U_i1.hash( - &self.poseidon_config, + &sponge, self.pp_hash, C1::ScalarField::one(), self.z_0.clone(), z_i1.clone(), - )?; + ); // hash the initial (dummy) CycleFold instance, which is used as the 2nd public // input in the AugmentedFCircuit - cf_u_i1_x = self - .cf_U_i - .hash_cyclefold(&self.poseidon_config, self.pp_hash)?; + cf_u_i1_x = self.cf_U_i.hash_cyclefold(&sponge, self.pp_hash); augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> { _c2: PhantomData,

@@ -377,30 +380,31 @@ cf_x: Some(cf_u_i1_x),

cf_cmT: None, }; } else { - let mut transcript_p: PoseidonTranscript<C1> = - PoseidonTranscript::<C1>::new(&self.poseidon_config); + let mut transcript_p: PoseidonSponge<C1::ScalarField> = + PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config); transcript_p.absorb(&self.pp_hash); let (rho_bits, nimfs_proof); - (nimfs_proof, U_i1, W_i1, rho_bits) = NIMFS::<C1, PoseidonTranscript<C1>>::prove( - &mut transcript_p, - &self.ccs, - &[self.U_i.clone()], - &[self.u_i.clone()], - &[self.W_i.clone()], - &[self.w_i.clone()], - )?; + (nimfs_proof, U_i1, W_i1, rho_bits) = + NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::prove( + &mut transcript_p, + &self.ccs, + &[self.U_i.clone()], + &[self.u_i.clone()], + &[self.W_i.clone()], + &[self.w_i.clone()], + )?; // sanity check: check the folded instance relation #[cfg(test)] U_i1.check_relation(&self.ccs, &W_i1)?; let u_i1_x = U_i1.hash( - &self.poseidon_config, + &sponge, self.pp_hash, self.i + C1::ScalarField::one(), self.z_0.clone(), z_i1.clone(), - )?; + ); let rho_Fq = C2::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)) .ok_or(Error::OutOfBounds)?;

@@ -425,7 +429,7 @@ };

let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = fold_cyclefold_circuit::<C1, GC1, C2, GC2, FC, CS1, CS2>( - &self.poseidon_config, + &mut transcript_p, self.cf_r1cs.clone(), self.cf_cs_params.clone(), self.pp_hash,

@@ -435,7 +439,7 @@ cf_u_i_x,

cf_circuit, )?; - cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config, self.pp_hash)?; + cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, self.pp_hash); augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> { _c2: PhantomData,

@@ -541,6 +545,8 @@ return Err(Error::IVCVerificationFail);

} return Ok(()); } + // `sponge` is for digest computation. + let sponge = PoseidonSponge::<C1::ScalarField>::new(&vp.poseidon_config); let (U_i, W_i) = running_instance; let (u_i, w_i) = incoming_instance;

@@ -553,12 +559,12 @@ let pp_hash = vp.pp_hash()?;

// 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(&vp.poseidon_config, 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.clone()); if expected_u_i_x != u_i.x[0] { return Err(Error::IVCVerificationFail); } // u_i.X[1] == H(cf_U_i) - let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&vp.poseidon_config, pp_hash)?; + let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); if expected_cf_u_i_x != u_i.x[1] { return Err(Error::IVCVerificationFail); }
M folding-schemes/src/folding/hypernova/nimfs.rs → folding-schemes/src/folding/hypernova/nimfs.rs

@@ -13,7 +13,6 @@ Witness,

}; use crate::arith::ccs::CCS; use crate::constants::N_BITS_RO; -use crate::folding::circuits::nonnative::affine::nonnative_affine_to_field_elements; use crate::transcript::Transcript; use crate::utils::sum_check::structs::{IOPProof as SumCheckProof, IOPProverMessage}; use crate::utils::sum_check::{IOPSumCheck, SumCheck};

@@ -58,12 +57,12 @@

#[derive(Debug)] /// Implements the Non-Interactive Multi Folding Scheme described in section 5 of /// [HyperNova](https://eprint.iacr.org/2023/573.pdf) -pub struct NIMFS<C: CurveGroup, T: Transcript<C>> { +pub struct NIMFS<C: CurveGroup, T: Transcript<C::ScalarField>> { pub _c: PhantomData<C>, pub _t: PhantomData<T>, } -impl<C: CurveGroup, T: Transcript<C>> NIMFS<C, T> +impl<C: CurveGroup, T: Transcript<C::ScalarField>> NIMFS<C, T> where <C as Group>::ScalarField: Absorb, C::BaseField: PrimeField,

@@ -178,7 +177,7 @@ /// Returns the final folded LCCCS, the folded witness, and the multifolding proof, which

/// contains the sumcheck proof and the helper sumcheck claim sigmas and thetas. #[allow(clippy::type_complexity)] pub fn prove( - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<C::ScalarField>, ccs: &CCS<C::ScalarField>, running_instances: &[LCCCS<C>], new_instances: &[CCCS<C>],

@@ -186,24 +185,8 @@ w_lcccs: &[Witness<C::ScalarField>],

w_cccs: &[Witness<C::ScalarField>], ) -> Result<(NIMFSProof<C>, LCCCS<C>, Witness<C::ScalarField>, Vec<bool>), Error> { // absorb instances to transcript - for U_i in running_instances { - let (C_x, C_y) = nonnative_affine_to_field_elements::<C>(U_i.C)?; - let v = [ - C_x, - C_y, - vec![U_i.u], - U_i.x.clone(), - U_i.r_x.clone(), - U_i.v.clone(), - ] - .concat(); - transcript.absorb_vec(&v); - } - for u_i in new_instances { - let (C_x, C_y) = nonnative_affine_to_field_elements::<C>(u_i.C)?; - let v = [C_x, C_y, u_i.x.clone()].concat(); - transcript.absorb_vec(&v); - } + transcript.absorb(&running_instances); + transcript.absorb(&new_instances); if running_instances.is_empty() { return Err(Error::Empty);

@@ -247,7 +230,7 @@ // Compute g(x)

let g = compute_g(ccs, running_instances, &z_lcccs, &z_cccs, gamma, &beta)?; // Step 3: Run the sumcheck prover - let sumcheck_proof = IOPSumCheck::<C, T>::prove(&g, transcript) + let sumcheck_proof = IOPSumCheck::<C::ScalarField, T>::prove(&g, transcript) .map_err(|err| Error::SumCheckProveError(err.to_string()))?; // Step 2: dig into the sumcheck and extract r_x_prime

@@ -290,31 +273,15 @@ /// Performs the multifolding verifier. Given μ LCCCS instances and ν CCS instances, fold them

/// into a single LCCCS instance. /// Returns the folded LCCCS instance. pub fn verify( - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<C::ScalarField>, ccs: &CCS<C::ScalarField>, running_instances: &[LCCCS<C>], new_instances: &[CCCS<C>], proof: NIMFSProof<C>, ) -> Result<LCCCS<C>, Error> { // absorb instances to transcript - for U_i in running_instances { - let (C_x, C_y) = nonnative_affine_to_field_elements::<C>(U_i.C)?; - let v = [ - C_x, - C_y, - vec![U_i.u], - U_i.x.clone(), - U_i.r_x.clone(), - U_i.v.clone(), - ] - .concat(); - transcript.absorb_vec(&v); - } - for u_i in new_instances { - let (C_x, C_y) = nonnative_affine_to_field_elements::<C>(u_i.C)?; - let v = [C_x, C_y, u_i.x.clone()].concat(); - transcript.absorb_vec(&v); - } + transcript.absorb(&running_instances); + transcript.absorb(&new_instances); if running_instances.is_empty() { return Err(Error::Empty);

@@ -349,9 +316,13 @@ }

} // Verify the interactive part of the sumcheck - let sumcheck_subclaim = - IOPSumCheck::<C, T>::verify(sum_v_j_gamma, &proof.sc_proof, &vp_aux_info, transcript) - .map_err(|err| Error::SumCheckVerifyError(err.to_string()))?; + let sumcheck_subclaim = IOPSumCheck::<C::ScalarField, T>::verify( + sum_v_j_gamma, + &proof.sc_proof, + &vp_aux_info, + transcript, + ) + .map_err(|err| Error::SumCheckVerifyError(err.to_string()))?; // Step 2: Dig into the sumcheck claim and extract the randomness used let r_x_prime = sumcheck_subclaim.point.clone();

@@ -413,7 +384,8 @@ ccs::tests::{get_test_ccs, get_test_z},

Arith, }; use crate::transcript::poseidon::poseidon_canonical_config; - use crate::transcript::poseidon::PoseidonTranscript; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_crypto_primitives::sponge::CryptographicSponge; use ark_std::test_rng; use ark_std::UniformRand;

@@ -450,7 +422,7 @@

let mut rng = test_rng(); let rho = Fr::rand(&mut rng); - let folded = NIMFS::<Projective, PoseidonTranscript<Projective>>::fold( + let folded = NIMFS::<Projective, PoseidonSponge<Fr>>::fold( &[lcccs], &[cccs], &sigmas_thetas,

@@ -458,8 +430,7 @@ r_x_prime,

rho, ); - let w_folded = - NIMFS::<Projective, PoseidonTranscript<Projective>>::fold_witness(&[w1], &[w2], rho); + let w_folded = NIMFS::<Projective, PoseidonSponge<Fr>>::fold_witness(&[w1], &[w2], rho); // check lcccs relation folded.check_relation(&ccs, &w_folded).unwrap();

@@ -491,13 +462,12 @@ .unwrap();

// Prover's transcript let poseidon_config = poseidon_canonical_config::<Fr>(); - let mut transcript_p: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config); transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init")); // Run the prover side of the multifolding let (proof, folded_lcccs, folded_witness, _) = - NIMFS::<Projective, PoseidonTranscript<Projective>>::prove( + NIMFS::<Projective, PoseidonSponge<Fr>>::prove( &mut transcript_p, &ccs, &[running_instance.clone()],

@@ -508,12 +478,11 @@ )

.unwrap(); // Verifier's transcript - let mut transcript_v: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_v: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config); transcript_v.absorb(&Fr::from_le_bytes_mod_order(b"init init")); // Run the verifier side of the multifolding - let folded_lcccs_v = NIMFS::<Projective, PoseidonTranscript<Projective>>::verify( + let folded_lcccs_v = NIMFS::<Projective, PoseidonSponge<Fr>>::verify( &mut transcript_v, &ccs, &[running_instance.clone()],

@@ -545,12 +514,10 @@ .unwrap();

let poseidon_config = poseidon_canonical_config::<Fr>(); - let mut transcript_p: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config); transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init")); - let mut transcript_v: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_v: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config); transcript_v.absorb(&Fr::from_le_bytes_mod_order(b"init init")); let n: usize = 10;

@@ -564,7 +531,7 @@ .unwrap();

// run the prover side of the multifolding let (proof, folded_lcccs, folded_witness, _) = - NIMFS::<Projective, PoseidonTranscript<Projective>>::prove( + NIMFS::<Projective, PoseidonSponge<Fr>>::prove( &mut transcript_p, &ccs, &[running_instance.clone()],

@@ -575,7 +542,7 @@ )

.unwrap(); // run the verifier side of the multifolding - let folded_lcccs_v = NIMFS::<Projective, PoseidonTranscript<Projective>>::verify( + let folded_lcccs_v = NIMFS::<Projective, PoseidonSponge<Fr>>::verify( &mut transcript_v, &ccs, &[running_instance.clone()],

@@ -641,13 +608,12 @@ }

// Prover's transcript let poseidon_config = poseidon_canonical_config::<Fr>(); - let mut transcript_p: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config); transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init")); // Run the prover side of the multifolding let (proof, folded_lcccs, folded_witness, _) = - NIMFS::<Projective, PoseidonTranscript<Projective>>::prove( + NIMFS::<Projective, PoseidonSponge<Fr>>::prove( &mut transcript_p, &ccs, &lcccs_instances,

@@ -658,12 +624,11 @@ )

.unwrap(); // Verifier's transcript - let mut transcript_v: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_v: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config); transcript_v.absorb(&Fr::from_le_bytes_mod_order(b"init init")); // Run the verifier side of the multifolding - let folded_lcccs_v = NIMFS::<Projective, PoseidonTranscript<Projective>>::verify( + let folded_lcccs_v = NIMFS::<Projective, PoseidonSponge<Fr>>::verify( &mut transcript_v, &ccs, &lcccs_instances,

@@ -690,13 +655,11 @@ Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();

let poseidon_config = poseidon_canonical_config::<Fr>(); // Prover's transcript - let mut transcript_p: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config); transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init")); // Verifier's transcript - let mut transcript_v: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_v: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config); transcript_v.absorb(&Fr::from_le_bytes_mod_order(b"init init")); let n_steps = 3;

@@ -741,7 +704,7 @@ }

// Run the prover side of the multifolding let (proof, folded_lcccs, folded_witness, _) = - NIMFS::<Projective, PoseidonTranscript<Projective>>::prove( + NIMFS::<Projective, PoseidonSponge<Fr>>::prove( &mut transcript_p, &ccs, &lcccs_instances,

@@ -752,7 +715,7 @@ )

.unwrap(); // Run the verifier side of the multifolding - let folded_lcccs_v = NIMFS::<Projective, PoseidonTranscript<Projective>>::verify( + let folded_lcccs_v = NIMFS::<Projective, PoseidonSponge<Fr>>::verify( &mut transcript_v, &ccs, &lcccs_instances,
M folding-schemes/src/folding/hypernova/utils.rs → folding-schemes/src/folding/hypernova/utils.rs

@@ -176,7 +176,6 @@ use crate::folding::hypernova::lcccs::tests::compute_Ls;

use crate::utils::hypercube::BooleanHypercube; use crate::utils::mle::matrix_to_dense_mle; use crate::utils::multilinear_polynomial::tests::fix_last_variables; - use crate::utils::virtual_polynomial::eq_eval; /// Given M(x,y) matrix and a random field element `r`, test that ~M(r,y) is is an s'-variable polynomial which /// compresses every column j of the M(x,y) matrix by performing a random linear combination between the elements
M folding-schemes/src/folding/nova/circuits.rs → folding-schemes/src/folding/nova/circuits.rs

@@ -1,11 +1,7 @@

/// contains [Nova](https://eprint.iacr.org/2021/370.pdf) related circuits -use ark_crypto_primitives::crh::{ - poseidon::constraints::{CRHGadget, CRHParametersVar}, - CRHSchemeGadget, -}; use ark_crypto_primitives::sponge::{ - constraints::CryptographicSpongeVar, - poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, + constraints::{AbsorbGadget, CryptographicSpongeVar}, + poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig}, Absorb, CryptographicSponge, }; use ark_ec::{CurveGroup, Group};

@@ -17,6 +13,7 @@ eq::EqGadget,

fields::{fp::FpVar, FieldVar}, groups::GroupOpsBounds, prelude::CurveVar, + uint8::UInt8, R1CSVar, ToConstraintFieldGadget, }; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};

@@ -29,13 +26,11 @@ use crate::folding::circuits::{

cyclefold::{ CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, CF_IO_LEN, }, - nonnative::{ - affine::{nonnative_affine_to_field_elements, NonNativeAffineVar}, - uint::NonNativeUintVar, - }, + nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, CF1, CF2, }; use crate::frontend::FCircuit; +use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar}; /// 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

@@ -78,6 +73,26 @@ })

} } +impl<C> AbsorbGadget<C::ScalarField> for CommittedInstanceVar<C> +where + C: CurveGroup, + <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField, +{ + fn to_sponge_bytes(&self) -> Result<Vec<UInt8<C::ScalarField>>, SynthesisError> { + unimplemented!() + } + + fn to_sponge_field_elements(&self) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> { + Ok([ + vec![self.u.clone()], + self.x.clone(), + self.cmE.to_constraint_field()?, + self.cmW.to_constraint_field()?, + ] + .concat()) + } +} + impl<C> CommittedInstanceVar<C> where C: CurveGroup,

@@ -91,26 +106,22 @@ /// `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( + pub fn hash<S: CryptographicSponge, T: TranscriptVar<CF1<C>, S>>( self, - crh_params: &CRHParametersVar<CF1<C>>, + sponge: &T, pp_hash: FpVar<CF1<C>>, i: FpVar<CF1<C>>, z_0: Vec<FpVar<CF1<C>>>, z_i: Vec<FpVar<CF1<C>>>, ) -> Result<(FpVar<CF1<C>>, Vec<FpVar<CF1<C>>>), SynthesisError> { - let U_vec = [ - vec![self.u], - self.x, - self.cmE.to_constraint_field()?, - self.cmW.to_constraint_field()?, - ] - .concat(); - let input = [vec![pp_hash, i], z_0, z_i, U_vec.clone()].concat(); - Ok(( - CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)?, - U_vec, - )) + 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)) } }

@@ -174,67 +185,33 @@ C: CurveGroup,

<C as CurveGroup>::BaseField: PrimeField, <C as Group>::ScalarField: Absorb, { - pub fn get_challenge_native( - poseidon_config: &PoseidonConfig<C::ScalarField>, + pub fn get_challenge_native<T: Transcript<C::ScalarField>>( + transcript: &mut T, pp_hash: C::ScalarField, // public params hash U_i: CommittedInstance<C>, u_i: CommittedInstance<C>, cmT: C, - ) -> Result<Vec<bool>, SynthesisError> { - let (U_cmE_x, U_cmE_y) = nonnative_affine_to_field_elements::<C>(U_i.cmE)?; - let (U_cmW_x, U_cmW_y) = nonnative_affine_to_field_elements::<C>(U_i.cmW)?; - let (u_cmE_x, u_cmE_y) = nonnative_affine_to_field_elements::<C>(u_i.cmE)?; - let (u_cmW_x, u_cmW_y) = nonnative_affine_to_field_elements::<C>(u_i.cmW)?; - let (cmT_x, cmT_y) = nonnative_affine_to_field_elements::<C>(cmT)?; - - let mut sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config); - let input = vec![ - vec![pp_hash], - vec![U_i.u], - U_i.x.clone(), - U_cmE_x, - U_cmE_y, - U_cmW_x, - U_cmW_y, - vec![u_i.u], - u_i.x.clone(), - u_cmE_x, - u_cmE_y, - u_cmW_x, - u_cmW_y, - cmT_x, - cmT_y, - ] - .concat(); - sponge.absorb(&input); - let bits = sponge.squeeze_bits(N_BITS_RO); - Ok(bits) + ) -> Vec<bool> { + transcript.absorb(&pp_hash); + transcript.absorb(&U_i); + transcript.absorb(&u_i); + transcript.absorb_nonnative(&cmT); + transcript.squeeze_bits(N_BITS_RO) } // compatible with the native get_challenge_native - pub fn get_challenge_gadget( - cs: ConstraintSystemRef<C::ScalarField>, - poseidon_config: &PoseidonConfig<C::ScalarField>, + pub fn get_challenge_gadget<S: CryptographicSponge, T: TranscriptVar<CF1<C>, S>>( + transcript: &mut T, pp_hash: FpVar<CF1<C>>, // public params hash U_i_vec: Vec<FpVar<CF1<C>>>, // apready processed input, so we don't have to recompute these values u_i: CommittedInstanceVar<C>, cmT: NonNativeAffineVar<C>, ) -> Result<Vec<Boolean<C::ScalarField>>, SynthesisError> { - let mut sponge = PoseidonSpongeVar::<C::ScalarField>::new(cs, poseidon_config); - - let input: Vec<FpVar<C::ScalarField>> = [ - vec![pp_hash], - U_i_vec, - vec![u_i.u.clone()], - u_i.x.clone(), - u_i.cmE.to_constraint_field()?, - u_i.cmW.to_constraint_field()?, - cmT.to_constraint_field()?, - ] - .concat(); - sponge.absorb(&input)?; - let bits = sponge.squeeze_bits(N_BITS_RO)?; - Ok(bits) + transcript.absorb(&pp_hash)?; + transcript.absorb(&U_i_vec)?; + transcript.absorb(&u_i)?; + transcript.absorb_nonnative(&cmT)?; + transcript.squeeze_bits(N_BITS_RO) } }

@@ -367,10 +344,10 @@ })?;

let cf1_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf1_cmT.unwrap_or_else(C2::zero)))?; let cf2_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf2_cmT.unwrap_or_else(C2::zero)))?; - let crh_params = CRHParametersVar::<C1::ScalarField>::new_constant( - cs.clone(), - self.poseidon_config.clone(), - )?; + // `sponge` is for digest computation. + let sponge = PoseidonSpongeVar::<C1::ScalarField>::new(cs.clone(), &self.poseidon_config); + // `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);

@@ -384,14 +361,14 @@ // 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( - &crh_params, + &sponge, pp_hash.clone(), i.clone(), z_0.clone(), z_i.clone(), )?; // u_i.x[1] = H(cf_U_i) - let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&crh_params, pp_hash.clone())?; + let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?; // P.2. Construct u_i let u_i = CommittedInstanceVar {

@@ -411,8 +388,7 @@ // P.3. nifs.verify, obtains U_{i+1} by folding u_i & U_i .

// compute r = H(u_i, U_i, cmT) let r_bits = ChallengeGadget::<C1>::get_challenge_gadget( - cs.clone(), - &self.poseidon_config, + &mut transcript, pp_hash.clone(), U_i_vec, u_i.clone(),

@@ -438,14 +414,14 @@ // P.4.a compute and check the first output of F'

// 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( - &crh_params, + &sponge, pp_hash.clone(), i + FpVar::<CF1<C1>>::one(), z_0.clone(), z_i1.clone(), )?; let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash( - &crh_params, + &sponge, pp_hash.clone(), FpVar::<CF1<C1>>::one(), z_0.clone(),

@@ -499,8 +475,7 @@

// compute cf1_r = H(cf1_u_i, cf_U_i, cf1_cmT) // cf_r_bits is denoted by rho* in the paper. let cf1_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget( - cs.clone(), - &self.poseidon_config, + &mut transcript, pp_hash.clone(), cf_U_i_vec, cf1_u_i.clone(),

@@ -523,10 +498,9 @@ )?;

// same for cf2_r: let cf2_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget( - cs.clone(), - &self.poseidon_config, + &mut transcript, pp_hash.clone(), - cf1_U_i1.to_constraint_field()?, + cf1_U_i1.to_native_sponge_field_elements()?, cf2_u_i.clone(), cf2_cmT.clone(), )?;

@@ -547,10 +521,10 @@ // Back to Primary Part

// P.4.b compute and check the second output of F' // Base case: u_{i+1}.x[1] == H(cf_U_{\bot}) // Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1}) - let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&crh_params, pp_hash.clone())?; + let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge, pp_hash.clone())?; let (cf_u_i1_x_base, _) = CycleFoldCommittedInstanceVar::new_constant(cs.clone(), cf_u_dummy)? - .hash(&crh_params, pp_hash)?; + .hash(&sponge, pp_hash)?; let cf_x = FpVar::new_input(cs.clone(), || { Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?)) })?;

@@ -564,6 +538,7 @@ #[cfg(test)]

pub mod tests { use super::*; use ark_bn254::{Fr, G1Projective as Projective}; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; use ark_ff::BigInteger; use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand;

@@ -628,6 +603,7 @@ #[test]

fn test_committed_instance_hash() { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_canonical_config::<Fr>(); + let sponge = PoseidonSponge::<Fr>::new(&poseidon_config); let pp_hash = Fr::from(42u32); // only for test let i = Fr::from(3_u32);

@@ -641,9 +617,7 @@ x: vec![Fr::rand(&mut rng); 1],

}; // compute the CommittedInstance hash natively - let h = ci - .hash(&poseidon_config, pp_hash, i, z_0.clone(), z_i.clone()) - .unwrap(); + let h = ci.hash(&sponge, pp_hash, i, z_0.clone(), z_i.clone()); let cs = ConstraintSystem::<Fr>::new_ref();

@@ -654,11 +628,11 @@ let z_iVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();

let ciVar = CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap(); - let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap(); + let sponge = PoseidonSpongeVar::<Fr>::new(cs.clone(), &poseidon_config); // compute the CommittedInstance hash in-circuit let (hVar, _) = ciVar - .hash(&crh_params, pp_hashVar, iVar, z_0Var, z_iVar) + .hash(&sponge, pp_hashVar, iVar, z_0Var, z_iVar) .unwrap(); assert!(cs.is_satisfied().unwrap());

@@ -671,6 +645,7 @@ #[test]

fn test_challenge_gadget() { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_canonical_config::<Fr>(); + let mut transcript = PoseidonSponge::<Fr>::new(&poseidon_config); let u_i = CommittedInstance::<Projective> { cmE: Projective::rand(&mut rng),

@@ -690,13 +665,12 @@ let pp_hash = Fr::from(42u32); // only for testing

// compute the challenge natively let r_bits = ChallengeGadget::<Projective>::get_challenge_native( - &poseidon_config, + &mut transcript, pp_hash, U_i.clone(), u_i.clone(), cmT, - ) - .unwrap(); + ); let r = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); let cs = ConstraintSystem::<Fr>::new_ref();

@@ -708,6 +682,7 @@ let U_iVar =

CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(U_i.clone())) .unwrap(); let cmTVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(cmT)).unwrap(); + let mut transcriptVar = PoseidonSpongeVar::<Fr>::new(cs.clone(), &poseidon_config); // compute the challenge in-circuit let U_iVar_vec = [

@@ -718,8 +693,7 @@ U_iVar.cmW.to_constraint_field().unwrap(),

] .concat(); let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget( - cs.clone(), - &poseidon_config, + &mut transcriptVar, pp_hashVar, U_iVar_vec, u_iVar,
M folding-schemes/src/folding/nova/decider_eth.rs → folding-schemes/src/folding/nova/decider_eth.rs

@@ -317,13 +317,11 @@ }

#[cfg(test)] pub mod tests { - use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; - use ark_groth16::Groth16; + use ark_bn254::{constraints::GVar, Fr, G1Projective as Projective}; use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; use std::time::Instant; use super::*; - use crate::commitment::kzg::KZG; use crate::commitment::pedersen::Pedersen; use crate::folding::nova::PreprocessorParam; use crate::frontend::tests::CubicFCircuit;
M folding-schemes/src/folding/nova/decider_eth_circuit.rs → folding-schemes/src/folding/nova/decider_eth_circuit.rs

@@ -1,7 +1,10 @@

/// 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::crh::poseidon::constraints::CRHParametersVar; -use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; +use ark_crypto_primitives::sponge::{ + constraints::CryptographicSpongeVar, + poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, + Absorb, CryptographicSponge, +}; use ark_ec::{CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_poly::Polynomial;

@@ -23,18 +26,12 @@ use super::{circuits::ChallengeGadget, nifs::NIFS};

use crate::arith::r1cs::R1CS; use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; use crate::folding::circuits::{ - nonnative::{ - affine::{nonnative_affine_to_field_elements, NonNativeAffineVar}, - uint::NonNativeUintVar, - }, + nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, CF1, CF2, }; use crate::folding::nova::{circuits::CommittedInstanceVar, CommittedInstance, Nova, Witness}; use crate::frontend::FCircuit; -use crate::transcript::{ - poseidon::{PoseidonTranscript, PoseidonTranscriptVar}, - Transcript, TranscriptVar, -}; +use crate::transcript::{Transcript, TranscriptVar}; use crate::utils::{ gadgets::{MatrixGadget, SparseMatrixVar, VectorGadget}, vec::poly_from_vec,

@@ -264,6 +261,8 @@ {

pub fn from_nova<FC: FCircuit<C1::ScalarField>>( nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2>, ) -> Result<Self, Error> { + let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config); + // compute the U_{i+1}, W_{i+1} let (T, cmT) = NIFS::<C1, CS1>::compute_cmT( &nova.cs_pp,

@@ -274,12 +273,12 @@ &nova.W_i.clone(),

&nova.U_i.clone(), )?; let r_bits = ChallengeGadget::<C1>::get_challenge_native( - &nova.poseidon_config, + &mut transcript, nova.pp_hash, nova.U_i.clone(), nova.u_i.clone(), cmT, - )?; + ); let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; let (W_i1, U_i1) = NIFS::<C1, CS1>::fold_instances(

@@ -288,7 +287,7 @@ )?;

// compute the KZG challenges used as inputs in the circuit let (kzg_challenge_W, kzg_challenge_E) = - KZGChallengesGadget::<C1>::get_challenges_native(&nova.poseidon_config, U_i1.clone())?; + KZGChallengesGadget::<C1>::get_challenges_native(&mut transcript, U_i1.clone()); // get KZG evals let mut W = W_i1.W.clone();

@@ -410,10 +409,10 @@ let _eval_E = FpVar::<CF1<C1>>::new_input(cs.clone(), || {

Ok(self.eval_E.unwrap_or_else(CF1::<C1>::zero)) })?; - let crh_params = CRHParametersVar::<C1::ScalarField>::new_constant( - cs.clone(), - self.poseidon_config.clone(), - )?; + // `sponge` is for digest computation. + let sponge = PoseidonSpongeVar::<C1::ScalarField>::new(cs.clone(), &self.poseidon_config); + // `transcript` is for challenge generation. + let mut transcript = sponge.clone(); // 1. check RelaxedR1CS of U_{i+1} let z_U1: Vec<FpVar<CF1<C1>>> =

@@ -429,7 +428,7 @@ (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( - &crh_params, + &sponge, pp_hash.clone(), i.clone(), z_0.clone(),

@@ -465,7 +464,7 @@ Ok(self.cf_W_i.unwrap_or(w_dummy_native.clone()))

})?; // 3.b u_i.x[1] == H(cf_U_i) - let (cf_u_i_x, _) = cf_U_i.clone().hash(&crh_params, pp_hash.clone())?; + let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?; (u_i.x[1]).enforce_equal(&cf_u_i_x)?; // 4. check Pedersen commitments of cf_U_i.{cmE, cmW}

@@ -498,12 +497,23 @@ let cf_z_U = [vec![cf_U_i.u.clone()], cf_U_i.x.to_vec(), cf_W_i.W.to_vec()].concat();

RelaxedR1CSGadget::check_nonnative(cf_r1cs, cf_W_i.E, cf_U_i.u.clone(), cf_z_U)?; } - // 6. check KZG challenges - let (incircuit_c_W, incircuit_c_E) = KZGChallengesGadget::<C1>::get_challenges_gadget( - cs.clone(), - &self.poseidon_config, - U_i1.clone(), + // 8.a, 6.a compute NIFS.V and KZG challenges. + // We need to ensure the order of challenge generation is the same as + // the native counterpart, so we first compute the challenges here and + // do the actual checks later. + let cmT = + NonNativeAffineVar::new_input(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?; + let r_bits = ChallengeGadget::<C1>::get_challenge_gadget( + &mut transcript, + pp_hash, + U_i_vec, + u_i.clone(), + cmT.clone(), )?; + let (incircuit_c_W, incircuit_c_E) = + KZGChallengesGadget::<C1>::get_challenges_gadget(&mut transcript, U_i1.clone())?; + + // 6.b check KZG challenges incircuit_c_W.enforce_equal(&kzg_c_W)?; incircuit_c_E.enforce_equal(&kzg_c_E)?;

@@ -516,18 +526,8 @@ // let incircuit_eval_E = evaluate_gadget::<CF1<C1>>(W_i1.E, incircuit_c_E)?;

// incircuit_eval_W.enforce_equal(&eval_W)?; // incircuit_eval_E.enforce_equal(&eval_E)?; - // 8. compute the NIFS.V challenge and check that matches the one from the public input (so we + // 8.b check the NIFS.V challenge matches the one from the public input (so we // avoid the verifier computing it) - let cmT = - NonNativeAffineVar::new_input(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?; - let r_bits = ChallengeGadget::<C1>::get_challenge_gadget( - cs.clone(), - &self.poseidon_config, - pp_hash, - U_i_vec, - u_i.clone(), - cmT.clone(), - )?; let r_Fr = Boolean::le_bits_to_fp_var(&r_bits)?; // check that the in-circuit computed r is equal to the inputted r let r =

@@ -569,38 +569,28 @@ C::ScalarField: PrimeField,

<C as CurveGroup>::BaseField: PrimeField, C::ScalarField: Absorb, { - pub fn get_challenges_native( - poseidon_config: &PoseidonConfig<C::ScalarField>, + pub fn get_challenges_native<T: Transcript<C::ScalarField>>( + transcript: &mut T, U_i: CommittedInstance<C>, - ) -> Result<(C::ScalarField, C::ScalarField), Error> { - let (cmE_x_limbs, cmE_y_limbs) = nonnative_affine_to_field_elements(U_i.cmE)?; - let (cmW_x_limbs, cmW_y_limbs) = nonnative_affine_to_field_elements(U_i.cmW)?; - - let transcript = &mut PoseidonTranscript::<C>::new(poseidon_config); + ) -> (C::ScalarField, C::ScalarField) { // compute the KZG challenges, which are computed in-circuit and checked that it matches // the inputted one - transcript.absorb_vec(&cmW_x_limbs); - transcript.absorb_vec(&cmW_y_limbs); + transcript.absorb_nonnative(&U_i.cmW); let challenge_W = transcript.get_challenge(); - transcript.absorb_vec(&cmE_x_limbs); - transcript.absorb_vec(&cmE_y_limbs); + transcript.absorb_nonnative(&U_i.cmE); let challenge_E = transcript.get_challenge(); - Ok((challenge_W, challenge_E)) + (challenge_W, challenge_E) } // compatible with the native get_challenges_native - pub fn get_challenges_gadget( - cs: ConstraintSystemRef<C::ScalarField>, - poseidon_config: &PoseidonConfig<C::ScalarField>, + pub fn get_challenges_gadget<S: CryptographicSponge, T: TranscriptVar<CF1<C>, S>>( + transcript: &mut T, U_i: CommittedInstanceVar<C>, ) -> Result<(FpVar<C::ScalarField>, FpVar<C::ScalarField>), SynthesisError> { - let mut transcript = - PoseidonTranscriptVar::<CF1<C>>::new(cs.clone(), &poseidon_config.clone()); - - transcript.absorb_vec(&U_i.cmW.to_constraint_field()?[..])?; + transcript.absorb(&U_i.cmW.to_constraint_field()?)?; let challenge_W = transcript.get_challenge()?; - transcript.absorb_vec(&U_i.cmE.to_constraint_field()?[..])?; + transcript.absorb(&U_i.cmE.to_constraint_field()?)?; let challenge_E = transcript.get_challenge()?; Ok((challenge_W, challenge_E))

@@ -852,6 +842,7 @@ #[test]

fn test_kzg_challenge_gadget() { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_canonical_config::<Fr>(); + let mut transcript = PoseidonSponge::<Fr>::new(&poseidon_config); let U_i = CommittedInstance::<Projective> { cmE: Projective::rand(&mut rng),

@@ -862,21 +853,17 @@ };

// compute the challenge natively let (challenge_W, challenge_E) = - KZGChallengesGadget::<Projective>::get_challenges_native(&poseidon_config, U_i.clone()) - .unwrap(); + KZGChallengesGadget::<Projective>::get_challenges_native(&mut transcript, U_i.clone()); let cs = ConstraintSystem::<Fr>::new_ref(); let U_iVar = CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(U_i.clone())) .unwrap(); + let mut transcript_var = PoseidonSpongeVar::<Fr>::new(cs.clone(), &poseidon_config); let (challenge_W_Var, challenge_E_Var) = - KZGChallengesGadget::<Projective>::get_challenges_gadget( - cs.clone(), - &poseidon_config, - U_iVar, - ) - .unwrap(); + KZGChallengesGadget::<Projective>::get_challenges_gadget(&mut transcript_var, U_iVar) + .unwrap(); assert!(cs.is_satisfied().unwrap()); // check that the natively computed and in-circuit computed hashes match
M folding-schemes/src/folding/nova/mod.rs → folding-schemes/src/folding/nova/mod.rs

@@ -1,11 +1,11 @@

/// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf) and /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf). -use ark_crypto_primitives::{ - crh::{poseidon::CRH, CRHScheme}, - sponge::{poseidon::PoseidonConfig, Absorb}, +use ark_crypto_primitives::sponge::{ + poseidon::{PoseidonConfig, PoseidonSponge}, + Absorb, CryptographicSponge, }; use ark_ec::{AffineRepr, CurveGroup, Group}; -use ark_ff::{BigInteger, Field, PrimeField, ToConstraintField}; +use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};

@@ -14,19 +14,18 @@ use ark_std::rand::RngCore;

use ark_std::{One, Zero}; use core::marker::PhantomData; -use crate::arith::r1cs::{extract_r1cs, extract_w_x, R1CS}; use crate::commitment::CommitmentScheme; -use crate::folding::circuits::{ - cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit}, - nonnative::{ - affine::nonnative_affine_to_field_elements, uint::nonnative_field_to_field_elements, - }, - CF2, -}; +use crate::folding::circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit}; +use crate::folding::circuits::CF2; use crate::frontend::FCircuit; -use crate::utils::{get_cm_coordinates, pp_hash, vec::is_zero_vec}; +use crate::transcript::{AbsorbNonNative, Transcript}; +use crate::utils::vec::is_zero_vec; use crate::Error; use crate::FoldingScheme; +use crate::{ + arith::r1cs::{extract_r1cs, extract_w_x, R1CS}, + utils::{get_cm_coordinates, pp_hash}, +}; pub mod circuits; pub mod decider_eth;

@@ -57,6 +56,54 @@ }

} } +impl<C: CurveGroup> Absorb for CommittedInstance<C> +where + C::ScalarField: Absorb, +{ + fn to_sponge_bytes(&self, _dest: &mut Vec<u8>) { + // This is never called + unimplemented!() + } + + fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) { + self.u.to_sponge_field_elements(dest); + self.x.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.cmE + .to_native_sponge_field_elements_as_vec() + .to_sponge_field_elements(dest); + self.cmW + .to_native_sponge_field_elements_as_vec() + .to_sponge_field_elements(dest); + } +} + +impl<C: CurveGroup> AbsorbNonNative<C::BaseField> for CommittedInstance<C> +where + <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb, +{ + // Compatible with the in-circuit `CycleFoldCommittedInstanceVar::to_native_sponge_field_elements` + // in `cyclefold.rs`. + fn to_native_sponge_field_elements(&self, dest: &mut Vec<C::BaseField>) { + [self.u].to_native_sponge_field_elements(dest); + self.x.to_native_sponge_field_elements(dest); + let (cmE_x, cmE_y) = match self.cmE.into_affine().xy() { + Some((&x, &y)) => (x, y), + None => (C::BaseField::zero(), C::BaseField::zero()), + }; + let (cmW_x, cmW_y) = match self.cmW.into_affine().xy() { + Some((&x, &y)) => (x, y), + None => (C::BaseField::zero(), C::BaseField::zero()), + }; + cmE_x.to_sponge_field_elements(dest); + cmE_y.to_sponge_field_elements(dest); + cmW_x.to_sponge_field_elements(dest); + cmW_y.to_sponge_field_elements(dest); + } +} + impl<C: CurveGroup> CommittedInstance<C> where <C as Group>::ScalarField: Absorb,

@@ -66,67 +113,21 @@ /// 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( + pub fn hash<T: Transcript<C::ScalarField>>( &self, - poseidon_config: &PoseidonConfig<C::ScalarField>, + sponge: &T, pp_hash: C::ScalarField, // public params hash i: C::ScalarField, z_0: Vec<C::ScalarField>, z_i: Vec<C::ScalarField>, - ) -> Result<C::ScalarField, Error> { - let (cmE_x, cmE_y) = nonnative_affine_to_field_elements::<C>(self.cmE)?; - let (cmW_x, cmW_y) = nonnative_affine_to_field_elements::<C>(self.cmW)?; - - CRH::<C::ScalarField>::evaluate( - poseidon_config, - vec![ - vec![pp_hash, i], - z_0, - z_i, - vec![self.u], - self.x.clone(), - cmE_x, - cmE_y, - cmW_x, - cmW_y, - ] - .concat(), - ) - .map_err(|e| Error::Other(e.to_string())) - } -} - -impl<C: CurveGroup> ToConstraintField<C::BaseField> for CommittedInstance<C> -where - <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb, -{ - fn to_field_elements(&self) -> Option<Vec<C::BaseField>> { - let u = nonnative_field_to_field_elements(&self.u); - let x = self - .x - .iter() - .flat_map(nonnative_field_to_field_elements) - .collect::<Vec<_>>(); - let (cmE_x, cmE_y, cmE_is_inf) = match self.cmE.into_affine().xy() { - Some((&x, &y)) => (x, y, C::BaseField::zero()), - None => ( - C::BaseField::zero(), - C::BaseField::zero(), - C::BaseField::one(), - ), - }; - let (cmW_x, cmW_y, cmW_is_inf) = match self.cmW.into_affine().xy() { - Some((&x, &y)) => (x, y, C::BaseField::zero()), - None => ( - C::BaseField::zero(), - C::BaseField::zero(), - C::BaseField::one(), - ), - }; - // Concatenate `cmE_is_inf` and `cmW_is_inf` to save constraints for CRHGadget::evaluate in the corresponding circuit - let is_inf = cmE_is_inf.double() + cmW_is_inf; - - Some([u, x, vec![cmE_x, cmE_y, cmW_x, cmW_y, is_inf]].concat()) + ) -> 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] } }

@@ -137,16 +138,15 @@ {

/// hash_cyclefold implements the committed instance hash compatible with the gadget implemented in /// nova/cyclefold.rs::CycleFoldCommittedInstanceVar.hash. /// Returns `H(U_i)`, where `U_i` is the `CommittedInstance` for CycleFold. - pub fn hash_cyclefold( + pub fn hash_cyclefold<T: Transcript<C::BaseField>>( &self, - poseidon_config: &PoseidonConfig<C::BaseField>, + sponge: &T, pp_hash: C::BaseField, // public params hash - ) -> Result<C::BaseField, Error> { - CRH::<C::BaseField>::evaluate( - poseidon_config, - [vec![pp_hash], self.to_field_elements().unwrap()].concat(), - ) - .map_err(|e| Error::Other(e.to_string())) + ) -> C::BaseField { + let mut sponge = sponge.clone(); + sponge.absorb(&pp_hash); + sponge.absorb_nonnative(self); + sponge.squeeze_field_elements(1)[0] } }

@@ -453,6 +453,11 @@ &mut self,

_rng: impl RngCore, external_inputs: Vec<C1::ScalarField>, ) -> Result<(), Error> { + // `sponge` is for digest computation. + let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config); + // `transcript` is for challenge generation. + let mut transcript = sponge.clone(); + let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>; if self.z_i.len() != self.F.state_len() {

@@ -488,12 +493,12 @@ let (T, cmT) = self.compute_cmT()?;

// r_bits is the r used to the RLC of the F' instances let r_bits = ChallengeGadget::<C1>::get_challenge_native( - &self.poseidon_config, + &mut transcript, self.pp_hash, self.U_i.clone(), self.u_i.clone(), cmT, - )?; + ); let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; let r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&r_bits))

@@ -507,19 +512,17 @@

// 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( - &self.poseidon_config, + &sponge, self.pp_hash, self.i + C1::ScalarField::one(), self.z_0.clone(), z_i1.clone(), - )?; + ); // u_{i+1}.x[1] = H(cf_U_{i+1}) let cf_u_i1_x: C1::ScalarField; if self.i == C1::ScalarField::zero() { - cf_u_i1_x = self - .cf_U_i - .hash_cyclefold(&self.poseidon_config, self.pp_hash)?; + cf_u_i1_x = self.cf_U_i.hash_cyclefold(&sponge, self.pp_hash); // base case augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> { _gc2: PhantomData,

@@ -584,16 +587,22 @@ };

// fold self.cf_U_i + cfW_U -> folded running with cfW let (_cfW_w_i, cfW_u_i, cfW_W_i1, cfW_U_i1, cfW_cmT, _) = self.fold_cyclefold_circuit( + &mut transcript, self.cf_W_i.clone(), // CycleFold running instance witness self.cf_U_i.clone(), // CycleFold running instance cfW_u_i_x, cfW_circuit, )?; // fold [the output from folding self.cf_U_i + cfW_U] + cfE_U = folded_running_with_cfW + cfE - let (_cfE_w_i, cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = - self.fold_cyclefold_circuit(cfW_W_i1, cfW_U_i1.clone(), cfE_u_i_x, cfE_circuit)?; + let (_cfE_w_i, cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = self.fold_cyclefold_circuit( + &mut transcript, + cfW_W_i1, + cfW_U_i1.clone(), + cfE_u_i_x, + cfE_circuit, + )?; - cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config, self.pp_hash)?; + cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, self.pp_hash); augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> { _gc2: PhantomData,

@@ -697,6 +706,8 @@ running_instance: Self::RunningInstance,

incoming_instance: Self::IncomingInstance, cyclefold_instance: Self::CFInstance, ) -> Result<(), Error> { + let sponge = PoseidonSponge::<C1::ScalarField>::new(&vp.poseidon_config); + if num_steps == C1::ScalarField::zero() { if z_0 != z_i { return Err(Error::IVCVerificationFail);

@@ -716,12 +727,12 @@ let pp_hash = vp.pp_hash()?;

// 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(&vp.poseidon_config, 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.clone()); if expected_u_i_x != u_i.x[0] { return Err(Error::IVCVerificationFail); } // u_i.X[1] == H(cf_U_i) - let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&vp.poseidon_config, pp_hash)?; + let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); if expected_cf_u_i_x != u_i.x[1] { return Err(Error::IVCVerificationFail); }

@@ -790,8 +801,9 @@ for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,

{ // folds the given cyclefold circuit and its instances #[allow(clippy::type_complexity)] - fn fold_cyclefold_circuit( + fn fold_cyclefold_circuit<T: Transcript<C1::ScalarField>>( &self, + transcript: &mut T, cf_W_i: Witness<C2>, // witness of the running instance cf_U_i: CommittedInstance<C2>, // running instance cf_u_i_x: Vec<C2::ScalarField>,

@@ -808,7 +820,7 @@ ),

Error, > { fold_cyclefold_circuit::<C1, GC1, C2, GC2, FC, CS1, CS2>( - &self.poseidon_config, + transcript, self.cf_r1cs.clone(), self.cf_cs_pp.clone(), self.pp_hash,
M folding-schemes/src/folding/nova/nifs.rs → folding-schemes/src/folding/nova/nifs.rs

@@ -183,7 +183,7 @@ Ok(())

} pub fn prove_commitments( - tr: &mut impl Transcript<C>, + tr: &mut impl Transcript<C::ScalarField>, cs_prover_params: &CS::ProverParams, w: &Witness<C>, ci: &CommittedInstance<C>,

@@ -200,7 +200,10 @@

#[cfg(test)] pub mod tests { use super::*; - use ark_crypto_primitives::sponge::poseidon::PoseidonConfig; + use ark_crypto_primitives::sponge::{ + poseidon::{PoseidonConfig, PoseidonSponge}, + CryptographicSponge, + }; use ark_ff::{BigInteger, PrimeField}; use ark_pallas::{Fr, Projective}; use ark_std::{ops::Mul, UniformRand};

@@ -209,7 +212,7 @@ use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z};

use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; use crate::folding::nova::circuits::ChallengeGadget; use crate::folding::nova::traits::NovaR1CS; - use crate::transcript::poseidon::{poseidon_canonical_config, PoseidonTranscript}; + use crate::transcript::poseidon::poseidon_canonical_config; #[allow(clippy::type_complexity)] pub(crate) fn prepare_simple_fold_inputs<C>() -> (

@@ -258,16 +261,16 @@ NIFS::<C, Pedersen<C>>::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2)

.unwrap(); let poseidon_config = poseidon_canonical_config::<C::ScalarField>(); + let mut transcript = PoseidonSponge::<C::ScalarField>::new(&poseidon_config); let pp_hash = C::ScalarField::from(42u32); // only for test let r_bits = ChallengeGadget::<C>::get_challenge_native( - &poseidon_config, + &mut transcript, pp_hash, ci1.clone(), ci2.clone(), cmT, - ) - .unwrap(); + ); let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); let (w3, ci3) =

@@ -364,9 +367,9 @@ NIFS::<Projective, Pedersen<Projective>>::verify_folded_instance(r, &ci1, &ci2, &ci3, &cmT)

.unwrap(); // init Prover's transcript - let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config); // init Verifier's transcript - let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config); // prove the ci3.cmE, ci3.cmW, cmT commitments let cm_proofs = NIFS::<Projective, Pedersen<Projective>>::prove_commitments(
M folding-schemes/src/folding/protogalaxy/folding.rs → folding-schemes/src/folding/protogalaxy/folding.rs

@@ -6,12 +6,10 @@ use ark_poly::{

univariate::{DensePolynomial, SparsePolynomial}, DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial, }; -use ark_std::log2; -use ark_std::{cfg_into_iter, Zero}; +use ark_std::{cfg_into_iter, log2, Zero}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use std::marker::PhantomData; -use super::traits::ProtoGalaxyTranscript; use super::utils::{all_powers, betas_star, exponential_powers}; use super::ProtoGalaxyError; use super::{CommittedInstance, Witness};

@@ -36,7 +34,7 @@ {

#![allow(clippy::type_complexity)] /// implements the non-interactive Prover from the folding scheme described in section 4 pub fn prove( - transcript: &mut (impl Transcript<C> + ProtoGalaxyTranscript<C>), + transcript: &mut impl Transcript<C::ScalarField>, r1cs: &R1CS<C::ScalarField>, // running instance instance: &CommittedInstance<C>,

@@ -81,10 +79,8 @@ return Err(Error::ProtoGalaxy(ProtoGalaxyError::WrongNumInstances(k)));

} // absorb the committed instances - transcript.absorb_committed_instance(instance)?; - for ci in vec_instances.iter() { - transcript.absorb_committed_instance(ci)?; - } + transcript.absorb(instance); + transcript.absorb(&vec_instances); let delta = transcript.get_challenge(); let deltas = exponential_powers(delta, t);

@@ -95,7 +91,7 @@ // F(X)

let F_X: SparsePolynomial<C::ScalarField> = calc_f_from_btree(&f_w, &instance.betas, &deltas).expect("Error calculating F[x]"); let F_X_dense = DensePolynomial::from(F_X.clone()); - transcript.absorb_vec(&F_X_dense.coeffs); + transcript.absorb(&F_X_dense.coeffs); let alpha = transcript.get_challenge();

@@ -187,7 +183,7 @@ if !remainder.is_zero() {

return Err(Error::ProtoGalaxy(ProtoGalaxyError::RemainderNotZero)); } - transcript.absorb_vec(&K_X.coeffs); + transcript.absorb(&K_X.coeffs); let gamma = transcript.get_challenge();

@@ -223,7 +219,7 @@ }

/// implements the non-interactive Verifier from the folding scheme described in section 4 pub fn verify( - transcript: &mut (impl Transcript<C> + ProtoGalaxyTranscript<C>), + transcript: &mut impl Transcript<C::ScalarField>, r1cs: &R1CS<C::ScalarField>, // running instance instance: &CommittedInstance<C>,

@@ -237,15 +233,13 @@ let t = instance.betas.len();

let n = r1cs.A.n_cols; // absorb the committed instances - transcript.absorb_committed_instance(instance)?; - for ci in vec_instances.iter() { - transcript.absorb_committed_instance(ci)?; - } + transcript.absorb(instance); + transcript.absorb(&vec_instances); let delta = transcript.get_challenge(); let deltas = exponential_powers(delta, t); - transcript.absorb_vec(&F_coeffs); + transcript.absorb(&F_coeffs); let alpha = transcript.get_challenge(); let alphas = all_powers(alpha, n);

@@ -266,7 +260,7 @@ let Z_X: DensePolynomial<C::ScalarField> = H.vanishing_polynomial().into();

let K_X: DensePolynomial<C::ScalarField> = DensePolynomial::<C::ScalarField>::from_coefficients_vec(K_coeffs); - transcript.absorb_vec(&K_X.coeffs); + transcript.absorb(&K_X.coeffs); let gamma = transcript.get_challenge();

@@ -380,12 +374,14 @@

#[cfg(test)] mod tests { use super::*; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_crypto_primitives::sponge::CryptographicSponge; use ark_pallas::{Fr, Projective}; use ark_std::UniformRand; use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z}; use crate::commitment::{pedersen::Pedersen, CommitmentScheme}; - use crate::transcript::poseidon::{poseidon_canonical_config, PoseidonTranscript}; + use crate::transcript::poseidon::poseidon_canonical_config; pub(crate) fn check_instance<C: CurveGroup>( r1cs: &R1CS<C::ScalarField>,

@@ -495,7 +491,7 @@ )

.unwrap(); let instance_i = CommittedInstance::<Projective> { phi: phi_i, - betas: betas.clone(), + betas: vec![], e: Fr::zero(), }; witnesses.push(witness_i);

@@ -513,8 +509,8 @@ let r1cs = get_test_r1cs::<Fr>();

// init Prover & Verifier's transcript let poseidon_config = poseidon_canonical_config::<Fr>(); - let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config); - let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config); + let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config); let (folded_instance, folded_witness, F_coeffs, K_coeffs) = Folding::<Projective>::prove( &mut transcript_p,

@@ -553,8 +549,8 @@ let r1cs = get_test_r1cs::<Fr>();

// init Prover & Verifier's transcript let poseidon_config = poseidon_canonical_config::<Fr>(); - let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config); - let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config); + let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config); + let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config); let (mut running_witness, mut running_instance, _, _) = prepare_inputs(0);
M folding-schemes/src/folding/protogalaxy/mod.rs → folding-schemes/src/folding/protogalaxy/mod.rs

@@ -1,7 +1,10 @@

/// Implements the scheme described in [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf) use ark_ec::CurveGroup; use ark_ff::PrimeField; +use ark_r1cs_std::fields::fp::FpVar; use thiserror::Error; + +use super::circuits::nonnative::affine::NonNativeAffineVar; pub mod folding; pub mod traits;

@@ -12,6 +15,13 @@ pub struct CommittedInstance<C: CurveGroup> {

phi: C, betas: Vec<C::ScalarField>, e: C::ScalarField, +} + +#[derive(Clone, Debug)] +pub struct CommittedInstanceVar<C: CurveGroup> { + phi: NonNativeAffineVar<C>, + betas: Vec<FpVar<C::ScalarField>>, + e: FpVar<C::ScalarField>, } #[derive(Clone, Debug)]
M folding-schemes/src/folding/protogalaxy/traits.rs → folding-schemes/src/folding/protogalaxy/traits.rs

@@ -1,23 +1,42 @@

-use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; +use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, Absorb}; +use ark_ec::CurveGroup; +use ark_ff::PrimeField; +use ark_r1cs_std::{fields::fp::FpVar, uint8::UInt8, ToConstraintFieldGadget}; +use ark_relations::r1cs::SynthesisError; -use super::CommittedInstance; -use crate::transcript::{poseidon::PoseidonTranscript, Transcript}; -use crate::Error; +use super::{CommittedInstance, CommittedInstanceVar}; +use crate::transcript::AbsorbNonNative; + +// Implements the trait for absorbing ProtoGalaxy's CommittedInstance. +impl<C: CurveGroup> Absorb for CommittedInstance<C> +where + C::ScalarField: Absorb, +{ + fn to_sponge_bytes(&self, _dest: &mut Vec<u8>) { + unimplemented!() + } -/// ProtoGalaxyTranscript extends [`Transcript`] with the method to absorb ProtoGalaxy's -/// CommittedInstance. -pub trait ProtoGalaxyTranscript<C: CurveGroup>: Transcript<C> { - fn absorb_committed_instance(&mut self, ci: &CommittedInstance<C>) -> Result<(), Error> { - self.absorb_point(&ci.phi)?; - self.absorb_vec(&ci.betas); - self.absorb(&ci.e); - Ok(()) + fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) { + self.phi + .to_native_sponge_field_elements_as_vec() + .to_sponge_field_elements(dest); + self.betas.to_sponge_field_elements(dest); + self.e.to_sponge_field_elements(dest); } } -// Implements ProtoGalaxyTranscript for PoseidonTranscript -impl<C: CurveGroup> ProtoGalaxyTranscript<C> for PoseidonTranscript<C> where - <C as Group>::ScalarField: Absorb -{ +// Implements the trait for absorbing ProtoGalaxy's CommittedInstanceVar in-circuit. +impl<C: CurveGroup> AbsorbGadget<C::ScalarField> for CommittedInstanceVar<C> { + fn to_sponge_bytes(&self) -> Result<Vec<UInt8<C::ScalarField>>, SynthesisError> { + unimplemented!() + } + + fn to_sponge_field_elements(&self) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> { + Ok([ + self.phi.to_constraint_field()?, + self.betas.to_sponge_field_elements()?, + self.e.to_sponge_field_elements()?, + ] + .concat()) + } }
M folding-schemes/src/frontend/circom/mod.rs → folding-schemes/src/frontend/circom/mod.rs

@@ -207,8 +207,7 @@ #[cfg(test)]

pub mod tests { use super::*; use ark_bn254::Fr; - use ark_r1cs_std::alloc::AllocVar; - use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; + use ark_relations::r1cs::ConstraintSystem; // Tests the step_native function of CircomFCircuit. #[test]
M folding-schemes/src/frontend/circom/utils.rs → folding-schemes/src/frontend/circom/utils.rs

@@ -110,7 +110,6 @@ use ark_bn254::Fr;

use ark_circom::circom::{CircomBuilder, CircomConfig}; use ark_circom::CircomCircuit; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; - use std::path::PathBuf; //To generate .r1cs and .wasm files, run the below command in the terminal. //bash ./folding-schemes/src/frontend/circom/test_folder/compile.sh
M folding-schemes/src/frontend/mod.rs → folding-schemes/src/frontend/mod.rs

@@ -52,9 +52,7 @@ pub mod tests {

use super::*; use ark_bn254::Fr; use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget}; - use ark_relations::r1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError, - }; + use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; use core::marker::PhantomData; /// CubicFCircuit is a struct that implements the FCircuit trait, for the R1CS example circuit
M folding-schemes/src/transcript/mod.rs → folding-schemes/src/transcript/mod.rs

@@ -1,31 +1,103 @@

-use crate::Error; +use ark_crypto_primitives::sponge::{constraints::CryptographicSpongeVar, CryptographicSponge}; use ark_ec::CurveGroup; use ark_ff::PrimeField; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; -use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; -use ark_std::fmt::Debug; +use ark_r1cs_std::{ + boolean::Boolean, fields::fp::FpVar, groups::CurveVar, ToConstraintFieldGadget, +}; +use ark_relations::r1cs::SynthesisError; pub mod poseidon; -pub trait Transcript<C: CurveGroup> { - type TranscriptConfig: Debug; +/// An interface for objects that can be absorbed by a `Transcript`. +/// +/// Matches `Absorb` in `ark-crypto-primitives`. +pub trait AbsorbNonNative<F: PrimeField> { + /// Converts the object into field elements that can be absorbed by a `Transcript`. + /// Append the list to `dest` + fn to_native_sponge_field_elements(&self, dest: &mut Vec<F>); + + /// Converts the object into field elements that can be absorbed by a `Transcript`. + /// Return the list as `Vec` + fn to_native_sponge_field_elements_as_vec(&self) -> Vec<F> { + let mut result = Vec::new(); + self.to_native_sponge_field_elements(&mut result); + result + } +} + +/// An interface for objects that can be absorbed by a `TranscriptVar` whose constraint field +/// is `F`. +/// +/// Matches `AbsorbGadget` in `ark-crypto-primitives`. +pub trait AbsorbNonNativeGadget<F: PrimeField> { + /// Converts the object into field elements that can be absorbed by a `TranscriptVar`. + fn to_native_sponge_field_elements(&self) -> Result<Vec<FpVar<F>>, SynthesisError>; +} - fn new(config: &Self::TranscriptConfig) -> Self; - fn absorb(&mut self, v: &C::ScalarField); - fn absorb_vec(&mut self, v: &[C::ScalarField]); - fn absorb_point(&mut self, v: &C) -> Result<(), Error>; - fn get_challenge(&mut self) -> C::ScalarField; +pub trait Transcript<F: PrimeField>: CryptographicSponge { + /// `absorb_point` is for absorbing points whose `BaseField` is the field of + /// the sponge, i.e., the type `C` of these points should satisfy + /// `C::BaseField = F`. + /// + /// If the sponge field `F` is `C::ScalarField`, call `absorb_nonnative` + /// instead. + fn absorb_point<C: CurveGroup<BaseField = F>>(&mut self, v: &C); + /// `absorb_nonnative` is for structs that contain non-native (field or + /// group) elements, including: + /// + /// - A field element of type `T: PrimeField` that will be absorbed into a + /// sponge that operates in another field `F != T`. + /// - A group element of type `C: CurveGroup` that will be absorbed into a + /// sponge that operates in another field `F != C::BaseField`, e.g., + /// `F = C::ScalarField`. + /// - A `CommittedInstance` on the secondary curve (used for CycleFold) that + /// will be absorbed into a sponge that operates in the (scalar field of + /// the) primary curve. + /// + /// Note that although a `CommittedInstance` for `AugmentedFCircuit` on + /// the primary curve also contains non-native elements, we still regard + /// it as native, because the sponge is on the same curve. + fn absorb_nonnative<V: AbsorbNonNative<F>>(&mut self, v: &V); + + fn get_challenge(&mut self) -> F; /// get_challenge_nbits returns a field element of size nbits fn get_challenge_nbits(&mut self, nbits: usize) -> Vec<bool>; - fn get_challenges(&mut self, n: usize) -> Vec<C::ScalarField>; + fn get_challenges(&mut self, n: usize) -> Vec<F>; } -pub trait TranscriptVar<F: PrimeField> { - type TranscriptVarConfig: Debug; +pub trait TranscriptVar<F: PrimeField, S: CryptographicSponge>: + CryptographicSpongeVar<F, S> +{ + /// `absorb_point` is for absorbing points whose `BaseField` is the field of + /// the sponge, i.e., the type `C` of these points should satisfy + /// `C::BaseField = F`. + /// + /// If the sponge field `F` is `C::ScalarField`, call `absorb_nonnative` + /// instead. + fn absorb_point<C: CurveGroup<BaseField = F>, GC: CurveVar<C, F> + ToConstraintFieldGadget<F>>( + &mut self, + v: &GC, + ) -> Result<(), SynthesisError>; + /// `absorb_nonnative` is for structs that contain non-native (field or + /// group) elements, including: + /// + /// - A field element of type `T: PrimeField` that will be absorbed into a + /// sponge that operates in another field `F != T`. + /// - A group element of type `C: CurveGroup` that will be absorbed into a + /// sponge that operates in another field `F != C::BaseField`, e.g., + /// `F = C::ScalarField`. + /// - A `CommittedInstance` on the secondary curve (used for CycleFold) that + /// will be absorbed into a sponge that operates in the (scalar field of + /// the) primary curve. + /// + /// Note that although a `CommittedInstance` for `AugmentedFCircuit` on + /// the primary curve also contains non-native elements, we still regard + /// it as native, because the sponge is on the same curve. + fn absorb_nonnative<V: AbsorbNonNativeGadget<F>>( + &mut self, + v: &V, + ) -> Result<(), SynthesisError>; - fn new(cs: ConstraintSystemRef<F>, poseidon_config: &Self::TranscriptVarConfig) -> Self; - fn absorb(&mut self, v: FpVar<F>) -> Result<(), SynthesisError>; - fn absorb_vec(&mut self, v: &[FpVar<F>]) -> Result<(), SynthesisError>; fn get_challenge(&mut self) -> Result<FpVar<F>, SynthesisError>; /// returns the bit representation of the challenge, we use its output in-circuit for the /// `GC.scalar_mul_le` method.
M folding-schemes/src/transcript/poseidon.rs → folding-schemes/src/transcript/poseidon.rs

@@ -3,114 +3,86 @@ constraints::CryptographicSpongeVar,

poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, Absorb, CryptographicSponge, }; -use ark_ec::{AffineRepr, CurveGroup, Group}; -use ark_ff::{BigInteger, Field, PrimeField}; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; -use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; -use ark_std::Zero; - -use crate::transcript::Transcript; -use crate::Error; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::{ + boolean::Boolean, fields::fp::FpVar, groups::CurveVar, ToConstraintFieldGadget, +}; +use ark_relations::r1cs::SynthesisError; -use super::TranscriptVar; +use super::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar}; -/// PoseidonTranscript implements the Transcript trait using the Poseidon hash -pub struct PoseidonTranscript<C: CurveGroup> -where - <C as Group>::ScalarField: Absorb, -{ - sponge: PoseidonSponge<C::ScalarField>, -} - -impl<C: CurveGroup> Transcript<C> for PoseidonTranscript<C> -where - <C as Group>::ScalarField: Absorb, -{ - type TranscriptConfig = PoseidonConfig<C::ScalarField>; - - fn new(poseidon_config: &Self::TranscriptConfig) -> Self { - let sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config); - Self { sponge } +impl<F: PrimeField + Absorb> Transcript<F> for PoseidonSponge<F> { + // Compatible with the in-circuit `TranscriptVar::absorb_point` + fn absorb_point<C: CurveGroup<BaseField = F>>(&mut self, p: &C) { + let (x, y) = match p.into_affine().xy() { + Some((&x, &y)) => (x, y), + None => (C::BaseField::zero(), C::BaseField::zero()), + }; + self.absorb(&x); + self.absorb(&y); } - fn absorb(&mut self, v: &C::ScalarField) { - self.sponge.absorb(&v); - } - fn absorb_vec(&mut self, v: &[C::ScalarField]) { - self.sponge.absorb(&v); + fn absorb_nonnative<V: AbsorbNonNative<F>>(&mut self, v: &V) { + self.absorb(&v.to_native_sponge_field_elements_as_vec()); } - fn absorb_point(&mut self, p: &C) -> Result<(), Error> { - self.sponge.absorb(&prepare_point(p)?); - Ok(()) - } - fn get_challenge(&mut self) -> C::ScalarField { - let c = self.sponge.squeeze_field_elements(1); - self.sponge.absorb(&c[0]); + fn get_challenge(&mut self) -> F { + let c = self.squeeze_field_elements(1); + self.absorb(&c[0]); c[0] } fn get_challenge_nbits(&mut self, nbits: usize) -> Vec<bool> { - self.sponge.squeeze_bits(nbits) + let bits = self.squeeze_bits(nbits); + self.absorb(&F::from(F::BigInt::from_bits_le(&bits))); + bits } - fn get_challenges(&mut self, n: usize) -> Vec<C::ScalarField> { - let c = self.sponge.squeeze_field_elements(n); - self.sponge.absorb(&c); + fn get_challenges(&mut self, n: usize) -> Vec<F> { + let c = self.squeeze_field_elements(n); + self.absorb(&c); c } } -// Returns the point coordinates in Fr, so it can be absorbed by the transcript. It does not work -// over bytes in order to have a logic that can be reproduced in-circuit. -fn prepare_point<C: CurveGroup>(p: &C) -> Result<Vec<C::ScalarField>, Error> { - let affine = p.into_affine(); - let zero_point = (&C::BaseField::zero(), &C::BaseField::zero()); - let xy = affine.xy().unwrap_or(zero_point); - - let x_bi = - xy.0.to_base_prime_field_elements() - .next() - .expect("a") - .into_bigint(); - let y_bi = - xy.1.to_base_prime_field_elements() - .next() - .expect("a") - .into_bigint(); - Ok(vec![ - C::ScalarField::from_le_bytes_mod_order(x_bi.to_bytes_le().as_ref()), - C::ScalarField::from_le_bytes_mod_order(y_bi.to_bytes_le().as_ref()), - ]) -} - -/// PoseidonTranscriptVar implements the gadget compatible with PoseidonTranscript -pub struct PoseidonTranscriptVar<F: PrimeField> { - sponge: PoseidonSpongeVar<F>, -} -impl<F: PrimeField> TranscriptVar<F> for PoseidonTranscriptVar<F> { - type TranscriptVarConfig = PoseidonConfig<F>; - - fn new(cs: ConstraintSystemRef<F>, poseidon_config: &Self::TranscriptVarConfig) -> Self { - let sponge = PoseidonSpongeVar::<F>::new(cs, poseidon_config); - Self { sponge } +impl<F: PrimeField> TranscriptVar<F, PoseidonSponge<F>> for PoseidonSpongeVar<F> { + fn absorb_point< + C: CurveGroup<BaseField = F>, + GC: CurveVar<C, F> + ToConstraintFieldGadget<F>, + >( + &mut self, + v: &GC, + ) -> Result<(), SynthesisError> { + let mut vec = v.to_constraint_field()?; + // The last element in the vector tells whether the point is infinity, + // but we can in fact avoid absorbing it without loss of soundness. + // This is because the `to_constraint_field` method internally invokes + // [`ProjectiveVar::to_afine`](https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/groups/curves/short_weierstrass/mod.rs#L160-L195), + // which guarantees that an infinity point is represented as `(0, 0)`, + // but the y-coordinate of a non-infinity point is never 0 (for why, see + // https://crypto.stackexchange.com/a/108242 ). + vec.pop(); + self.absorb(&vec) } - fn absorb(&mut self, v: FpVar<F>) -> Result<(), SynthesisError> { - self.sponge.absorb(&v) - } - fn absorb_vec(&mut self, v: &[FpVar<F>]) -> Result<(), SynthesisError> { - self.sponge.absorb(&v) + fn absorb_nonnative<V: AbsorbNonNativeGadget<F>>( + &mut self, + v: &V, + ) -> Result<(), SynthesisError> { + self.absorb(&v.to_native_sponge_field_elements()?) } fn get_challenge(&mut self) -> Result<FpVar<F>, SynthesisError> { - let c = self.sponge.squeeze_field_elements(1)?; - self.sponge.absorb(&c[0])?; + let c = self.squeeze_field_elements(1)?; + self.absorb(&c[0])?; Ok(c[0].clone()) } /// returns the bit representation of the challenge, we use its output in-circuit for the /// `GC.scalar_mul_le` method. fn get_challenge_nbits(&mut self, nbits: usize) -> Result<Vec<Boolean<F>>, SynthesisError> { - self.sponge.squeeze_bits(nbits) + let bits = self.squeeze_bits(nbits)?; + self.absorb(&Boolean::le_bits_to_fp_var(&bits)?)?; + Ok(bits) } fn get_challenges(&mut self, n: usize) -> Result<Vec<FpVar<F>>, SynthesisError> { - let c = self.sponge.squeeze_field_elements(n)?; - self.sponge.absorb(&c)?; + let c = self.squeeze_field_elements(n)?; + self.absorb(&c)?; Ok(c) } }

@@ -147,12 +119,17 @@ }

#[cfg(test)] pub mod tests { + use crate::folding::circuits::nonnative::affine::NonNativeAffineVar; + use super::*; - use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as G1}; - use ark_grumpkin::Projective; - use ark_r1cs_std::{alloc::AllocVar, groups::CurveVar, R1CSVar}; + use ark_bn254::{constraints::GVar, g1::Config, Fq, Fr, G1Projective as G1}; + use ark_ec::Group; + use ark_ff::UniformRand; + use ark_r1cs_std::{ + alloc::AllocVar, groups::curves::short_weierstrass::ProjectiveVar, R1CSVar, + }; use ark_relations::r1cs::ConstraintSystem; - use std::ops::Mul; + use ark_std::test_rng; // Test with value taken from https://github.com/iden3/circomlibjs/blob/43cc582b100fc3459cf78d903a6f538e5d7f38ee/test/poseidon.js#L32 #[test]

@@ -179,18 +156,68 @@ );

} #[test] + fn test_transcript_and_transcriptvar_absorb_native_point() { + // use 'native' transcript + let config = poseidon_canonical_config::<Fq>(); + let mut tr = PoseidonSponge::<Fq>::new(&config); + let rng = &mut test_rng(); + + let p = G1::rand(rng); + tr.absorb_point(&p); + let c = tr.get_challenge(); + + // use 'gadget' transcript + let cs = ConstraintSystem::<Fq>::new_ref(); + let mut tr_var = PoseidonSpongeVar::<Fq>::new(cs.clone(), &config); + let p_var = ProjectiveVar::<Config, FpVar<Fq>>::new_witness( + ConstraintSystem::<Fq>::new_ref(), + || Ok(p), + ) + .unwrap(); + tr_var.absorb_point(&p_var).unwrap(); + let c_var = tr_var.get_challenge().unwrap(); + + // assert that native & gadget transcripts return the same challenge + assert_eq!(c, c_var.value().unwrap()); + } + + #[test] + fn test_transcript_and_transcriptvar_absorb_nonnative_point() { + // use 'native' transcript + let config = poseidon_canonical_config::<Fr>(); + let mut tr = PoseidonSponge::<Fr>::new(&config); + let rng = &mut test_rng(); + + let p = G1::rand(rng); + tr.absorb_nonnative(&p); + let c = tr.get_challenge(); + + // use 'gadget' transcript + let cs = ConstraintSystem::<Fr>::new_ref(); + let mut tr_var = PoseidonSpongeVar::<Fr>::new(cs.clone(), &config); + let p_var = + NonNativeAffineVar::<G1>::new_witness(ConstraintSystem::<Fr>::new_ref(), || Ok(p)) + .unwrap(); + tr_var.absorb_nonnative(&p_var).unwrap(); + let c_var = tr_var.get_challenge().unwrap(); + + // assert that native & gadget transcripts return the same challenge + assert_eq!(c, c_var.value().unwrap()); + } + + #[test] fn test_transcript_and_transcriptvar_get_challenge() { // use 'native' transcript let config = poseidon_canonical_config::<Fr>(); - let mut tr = PoseidonTranscript::<G1>::new(&config); + let mut tr = PoseidonSponge::<Fr>::new(&config); tr.absorb(&Fr::from(42_u32)); let c = tr.get_challenge(); // use 'gadget' transcript let cs = ConstraintSystem::<Fr>::new_ref(); - let mut tr_var = PoseidonTranscriptVar::<Fr>::new(cs.clone(), &config); + let mut tr_var = PoseidonSpongeVar::<Fr>::new(cs.clone(), &config); let v = FpVar::<Fr>::new_witness(cs.clone(), || Ok(Fr::from(42_u32))).unwrap(); - tr_var.absorb(v).unwrap(); + tr_var.absorb(&v).unwrap(); let c_var = tr_var.get_challenge().unwrap(); // assert that native & gadget transcripts return the same challenge

@@ -203,7 +230,7 @@ let nbits = crate::constants::N_BITS_RO;

// use 'native' transcript let config = poseidon_canonical_config::<Fq>(); - let mut tr = PoseidonTranscript::<Projective>::new(&config); + let mut tr = PoseidonSponge::<Fq>::new(&config); tr.absorb(&Fq::from(42_u32)); // get challenge from native transcript

@@ -211,9 +238,9 @@ let c_bits = tr.get_challenge_nbits(nbits);

// use 'gadget' transcript let cs = ConstraintSystem::<Fq>::new_ref(); - let mut tr_var = PoseidonTranscriptVar::<Fq>::new(cs.clone(), &config); + let mut tr_var = PoseidonSpongeVar::<Fq>::new(cs.clone(), &config); let v = FpVar::<Fq>::new_witness(cs.clone(), || Ok(Fq::from(42_u32))).unwrap(); - tr_var.absorb(v).unwrap(); + tr_var.absorb(&v).unwrap(); // get challenge from circuit transcript let c_var = tr_var.get_challenge_nbits(nbits).unwrap();

@@ -226,7 +253,7 @@ // result natively and in-circuit

// native c*P let c_Fr = Fr::from_bigint(BigInteger::from_bits_le(&c_bits)).unwrap(); - let cP_native = P.mul(c_Fr); + let cP_native = P * c_Fr; // native c*P using mul_bits_be (notice the .rev to convert the LE to BE) let cP_native_bits = P.mul_bits_be(c_bits.into_iter().rev());
M folding-schemes/src/utils/espresso/sum_check/mod.rs → folding-schemes/src/utils/espresso/sum_check/mod.rs

@@ -13,7 +13,7 @@ use crate::{

transcript::Transcript, utils::virtual_polynomial::{VPAuxInfo, VirtualPolynomial}, }; -use ark_ec::CurveGroup; +use ark_crypto_primitives::sponge::Absorb; use ark_ff::PrimeField; use ark_poly::univariate::DensePolynomial; use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, Polynomial};

@@ -22,7 +22,6 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc};

use crate::utils::sum_check::structs::IOPProverMessage; use crate::utils::sum_check::structs::IOPVerifierState; -use ark_ff::Field; use espresso_subroutines::poly_iop::prelude::PolyIOPErrors; use structs::{IOPProof, IOPProverState};

@@ -31,7 +30,7 @@ pub mod structs;

pub mod verifier; /// A generic sum-check trait over a curve group -pub trait SumCheck<C: CurveGroup> { +pub trait SumCheck<F: PrimeField> { type VirtualPolynomial; type VPAuxInfo; type MultilinearExtension;

@@ -40,27 +39,27 @@ type SumCheckProof: Clone + Debug + Default + PartialEq;

type SumCheckSubClaim: Clone + Debug + Default + PartialEq; /// Extract sum from the proof - fn extract_sum(proof: &Self::SumCheckProof) -> C::ScalarField; + fn extract_sum(proof: &Self::SumCheckProof) -> F; /// Generate proof of the sum of polynomial over {0,1}^`num_vars` /// /// The polynomial is represented in the form of a VirtualPolynomial. fn prove( poly: &Self::VirtualPolynomial, - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<F>, ) -> Result<Self::SumCheckProof, PolyIOPErrors>; /// Verify the claimed sum using the proof fn verify( - sum: C::ScalarField, + sum: F, proof: &Self::SumCheckProof, aux_info: &Self::VPAuxInfo, - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<F>, ) -> Result<Self::SumCheckSubClaim, PolyIOPErrors>; } /// Trait for sum check protocol prover side APIs. -pub trait SumCheckProver<C: CurveGroup> +pub trait SumCheckProver<F: PrimeField> where Self: Sized, {

@@ -77,12 +76,12 @@ ///

/// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2). fn prove_round_and_update_state( &mut self, - challenge: &Option<C::ScalarField>, + challenge: &Option<F>, ) -> Result<Self::ProverMessage, PolyIOPErrors>; } /// Trait for sum check protocol verifier side APIs. -pub trait SumCheckVerifier<C: CurveGroup> { +pub trait SumCheckVerifier<F: PrimeField> { type VPAuxInfo; type ProverMessage; type Challenge;

@@ -100,7 +99,7 @@ /// at the last step.

fn verify_round_and_update_state( &mut self, prover_msg: &Self::ProverMessage, - transcript: &mut impl Transcript<C>, + transcript: &mut impl Transcript<F>, ) -> Result<Self::Challenge, PolyIOPErrors>; /// This function verifies the deferred checks in the interactive version of

@@ -113,7 +112,7 @@ /// Otherwise, it is highly unlikely that those two will be equal.

/// Larger field size guarantees smaller soundness error. fn check_and_generate_subclaim( &self, - asserted_sum: &C::ScalarField, + asserted_sum: &F, ) -> Result<Self::SumCheckSubClaim, PolyIOPErrors>; }

@@ -129,42 +128,42 @@ pub expected_evaluation: F,

} #[derive(Clone, Debug, Default, Copy, PartialEq, Eq)] -pub struct IOPSumCheck<C: CurveGroup, T: Transcript<C>> { +pub struct IOPSumCheck<F: PrimeField, T: Transcript<F>> { #[doc(hidden)] - phantom: PhantomData<C>, + phantom: PhantomData<F>, #[doc(hidden)] phantom2: PhantomData<T>, } -impl<C: CurveGroup, T: Transcript<C>> SumCheck<C> for IOPSumCheck<C, T> { - type SumCheckProof = IOPProof<C::ScalarField>; - type VirtualPolynomial = VirtualPolynomial<C::ScalarField>; - type VPAuxInfo = VPAuxInfo<C::ScalarField>; - type MultilinearExtension = Arc<DenseMultilinearExtension<C::ScalarField>>; - type SumCheckSubClaim = SumCheckSubClaim<C::ScalarField>; +impl<F: PrimeField + Absorb, T: Transcript<F>> SumCheck<F> for IOPSumCheck<F, T> { + type SumCheckProof = IOPProof<F>; + type VirtualPolynomial = VirtualPolynomial<F>; + type VPAuxInfo = VPAuxInfo<F>; + type MultilinearExtension = Arc<DenseMultilinearExtension<F>>; + type SumCheckSubClaim = SumCheckSubClaim<F>; - fn extract_sum(proof: &Self::SumCheckProof) -> C::ScalarField { + fn extract_sum(proof: &Self::SumCheckProof) -> F { let start = start_timer!(|| "extract sum"); let poly = DensePolynomial::from_coefficients_vec(proof.proofs[0].coeffs.clone()); - let res = poly.evaluate(&C::ScalarField::ONE) + poly.evaluate(&C::ScalarField::ZERO); + let res = poly.evaluate(&F::ONE) + poly.evaluate(&F::ZERO); end_timer!(start); res } fn prove( - poly: &VirtualPolynomial<C::ScalarField>, - transcript: &mut impl Transcript<C>, - ) -> Result<IOPProof<C::ScalarField>, PolyIOPErrors> { - transcript.absorb(&C::ScalarField::from(poly.aux_info.num_variables as u64)); - transcript.absorb(&C::ScalarField::from(poly.aux_info.max_degree as u64)); - let mut prover_state: IOPProverState<C> = IOPProverState::prover_init(poly)?; - let mut challenge: Option<C::ScalarField> = None; - let mut prover_msgs: Vec<IOPProverMessage<C::ScalarField>> = + poly: &VirtualPolynomial<F>, + transcript: &mut impl Transcript<F>, + ) -> Result<IOPProof<F>, PolyIOPErrors> { + transcript.absorb(&F::from(poly.aux_info.num_variables as u64)); + transcript.absorb(&F::from(poly.aux_info.max_degree as u64)); + let mut prover_state: IOPProverState<F> = IOPProverState::prover_init(poly)?; + let mut challenge: Option<F> = None; + let mut prover_msgs: Vec<IOPProverMessage<F>> = Vec::with_capacity(poly.aux_info.num_variables); for _ in 0..poly.aux_info.num_variables { - let prover_msg: IOPProverMessage<C::ScalarField> = + let prover_msg: IOPProverMessage<F> = IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)?; - transcript.absorb_vec(&prover_msg.coeffs); + transcript.absorb(&prover_msg.coeffs); prover_msgs.push(prover_msg); challenge = Some(transcript.get_challenge()); }

@@ -178,17 +177,17 @@ })

} fn verify( - claimed_sum: C::ScalarField, - proof: &IOPProof<C::ScalarField>, - aux_info: &VPAuxInfo<C::ScalarField>, - transcript: &mut impl Transcript<C>, - ) -> Result<SumCheckSubClaim<C::ScalarField>, PolyIOPErrors> { - transcript.absorb(&C::ScalarField::from(aux_info.num_variables as u64)); - transcript.absorb(&C::ScalarField::from(aux_info.max_degree as u64)); + claimed_sum: F, + proof: &IOPProof<F>, + aux_info: &VPAuxInfo<F>, + transcript: &mut impl Transcript<F>, + ) -> Result<SumCheckSubClaim<F>, PolyIOPErrors> { + transcript.absorb(&F::from(aux_info.num_variables as u64)); + transcript.absorb(&F::from(aux_info.max_degree as u64)); let mut verifier_state = IOPVerifierState::verifier_init(aux_info); for i in 0..aux_info.num_variables { let prover_msg = proof.proofs.get(i).expect("proof is incomplete"); - transcript.absorb_vec(&prover_msg.coeffs); + transcript.absorb(&prover_msg.coeffs); IOPVerifierState::verify_round_and_update_state( &mut verifier_state, prover_msg,

@@ -204,16 +203,15 @@ #[cfg(test)]

pub mod tests { use std::sync::Arc; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + use ark_crypto_primitives::sponge::CryptographicSponge; use ark_ff::Field; use ark_pallas::Fr; - use ark_pallas::Projective; use ark_poly::DenseMultilinearExtension; use ark_poly::MultilinearExtension; use ark_std::test_rng; use crate::transcript::poseidon::poseidon_canonical_config; - use crate::transcript::poseidon::PoseidonTranscript; - use crate::transcript::Transcript; use crate::utils::sum_check::SumCheck; use crate::utils::virtual_polynomial::VirtualPolynomial;

@@ -227,20 +225,19 @@ let virtual_poly = VirtualPolynomial::new_from_mle(&Arc::new(poly_mle), Fr::ONE);

let poseidon_config = poseidon_canonical_config::<Fr>(); // sum-check prove - let mut poseidon_transcript_prove: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); - let sum_check = IOPSumCheck::<Projective, PoseidonTranscript<Projective>>::prove( + let mut poseidon_transcript_prove: PoseidonSponge<Fr> = + PoseidonSponge::<Fr>::new(&poseidon_config); + let sum_check = IOPSumCheck::<Fr, PoseidonSponge<Fr>>::prove( &virtual_poly, &mut poseidon_transcript_prove, ) .unwrap(); // sum-check verify - let claimed_sum = - IOPSumCheck::<Projective, PoseidonTranscript<Projective>>::extract_sum(&sum_check); - let mut poseidon_transcript_verify: PoseidonTranscript<Projective> = - PoseidonTranscript::<Projective>::new(&poseidon_config); - let res_verify = IOPSumCheck::<Projective, PoseidonTranscript<Projective>>::verify( + let claimed_sum = IOPSumCheck::<Fr, PoseidonSponge<Fr>>::extract_sum(&sum_check); + let mut poseidon_transcript_verify: PoseidonSponge<Fr> = + PoseidonSponge::<Fr>::new(&poseidon_config); + let res_verify = IOPSumCheck::<Fr, PoseidonSponge<Fr>>::verify( claimed_sum, &sum_check, &virtual_poly.aux_info,
M folding-schemes/src/utils/espresso/sum_check/prover.rs → folding-schemes/src/utils/espresso/sum_check/prover.rs

@@ -14,11 +14,9 @@ use crate::utils::{

lagrange_poly::compute_lagrange_interpolated_poly, multilinear_polynomial::fix_variables, virtual_polynomial::VirtualPolynomial, }; -use ark_ec::CurveGroup; -use ark_ff::Field; use ark_ff::{batch_inversion, PrimeField}; use ark_poly::DenseMultilinearExtension; -use ark_std::{cfg_into_iter, end_timer, start_timer, vec::Vec}; +use ark_std::{cfg_into_iter, end_timer, start_timer}; use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator}; use std::sync::Arc;

@@ -28,9 +26,9 @@

// #[cfg(feature = "parallel")] use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; -impl<C: CurveGroup> SumCheckProver<C> for IOPProverState<C> { - type VirtualPolynomial = VirtualPolynomial<C::ScalarField>; - type ProverMessage = IOPProverMessage<C::ScalarField>; +impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> { + type VirtualPolynomial = VirtualPolynomial<F>; + type ProverMessage = IOPProverMessage<F>; /// Initialize the prover state to argue for the sum of the input polynomial /// over {0,1}^`num_vars`.

@@ -49,9 +47,7 @@ round: 0,

poly: polynomial.clone(), extrapolation_aux: (1..polynomial.aux_info.max_degree) .map(|degree| { - let points = (0..1 + degree as u64) - .map(C::ScalarField::from) - .collect::<Vec<_>>(); + let points = (0..1 + degree as u64).map(F::from).collect::<Vec<_>>(); let weights = barycentric_weights(&points); (points, weights) })

@@ -65,7 +61,7 @@ ///

/// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2). fn prove_round_and_update_state( &mut self, - challenge: &Option<C::ScalarField>, + challenge: &Option<F>, ) -> Result<Self::ProverMessage, PolyIOPErrors> { // let start = // start_timer!(|| format!("sum check prove {}-th round and update state",

@@ -90,7 +86,7 @@ //

// g(r_1, ..., r_{m-1}, x_m ... x_n) // // eval g over r_m, and mutate g to g(r_1, ... r_m,, x_{m+1}... x_n) - let mut flattened_ml_extensions: Vec<DenseMultilinearExtension<C::ScalarField>> = self + let mut flattened_ml_extensions: Vec<DenseMultilinearExtension<F>> = self .poly .flattened_ml_extensions .par_iter()

@@ -124,7 +120,7 @@

self.round += 1; let products_list = self.poly.products.clone(); - let mut products_sum = vec![C::ScalarField::ZERO; self.poly.aux_info.max_degree + 1]; + let mut products_sum = vec![F::ZERO; self.poly.aux_info.max_degree + 1]; // Step 2: generate sum for the partial evaluated polynomial: // f(r_1, ... r_m,, x_{m+1}... x_n)

@@ -134,8 +130,8 @@ let mut sum = cfg_into_iter!(0..1 << (self.poly.aux_info.num_variables - self.round))

.fold( || { ( - vec![(C::ScalarField::ZERO, C::ScalarField::ZERO); products.len()], - vec![C::ScalarField::ZERO; products.len() + 1], + vec![(F::ZERO, F::ZERO); products.len()], + vec![F::ZERO; products.len() + 1], ) }, |(mut buf, mut acc), b| {

@@ -146,17 +142,17 @@ let table = &flattened_ml_extensions[*f];

*eval = table[b << 1]; *step = table[(b << 1) + 1] - table[b << 1]; }); - acc[0] += buf.iter().map(|(eval, _)| eval).product::<C::ScalarField>(); + acc[0] += buf.iter().map(|(eval, _)| eval).product::<F>(); acc[1..].iter_mut().for_each(|acc| { buf.iter_mut().for_each(|(eval, step)| *eval += step as &_); - *acc += buf.iter().map(|(eval, _)| eval).product::<C::ScalarField>(); + *acc += buf.iter().map(|(eval, _)| eval).product::<F>(); }); (buf, acc) }, ) .map(|(_, partial)| partial) .reduce( - || vec![C::ScalarField::ZERO; products.len() + 1], + || vec![F::ZERO; products.len() + 1], |mut sum, partial| { sum.iter_mut() .zip(partial.iter())

@@ -168,7 +164,7 @@ sum.iter_mut().for_each(|sum| *sum *= coefficient);

let extraploation = cfg_into_iter!(0..self.poly.aux_info.max_degree - products.len()) .map(|i| { let (points, weights) = &self.extrapolation_aux[products.len() - 1]; - let at = C::ScalarField::from((products.len() + 1 + i) as u64); + let at = F::from((products.len() + 1 + i) as u64); extrapolate(points, weights, &sum, &at) }) .collect::<Vec<_>>();

@@ -184,7 +180,7 @@ .par_iter()

.map(|x| Arc::new(x.clone())) .collect(); - let prover_poly = compute_lagrange_interpolated_poly::<C::ScalarField>(&products_sum); + let prover_poly = compute_lagrange_interpolated_poly::<F>(&products_sum); Ok(IOPProverMessage { coeffs: prover_poly.coeffs, })
M folding-schemes/src/utils/espresso/sum_check/structs.rs → folding-schemes/src/utils/espresso/sum_check/structs.rs

@@ -10,7 +10,6 @@

//! This module defines structs that are shared by all sub protocols. use crate::utils::virtual_polynomial::VirtualPolynomial; -use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_serialize::CanonicalSerialize;

@@ -33,28 +32,28 @@ }

/// Prover State of a PolyIOP. #[derive(Debug)] -pub struct IOPProverState<C: CurveGroup> { +pub struct IOPProverState<F: PrimeField> { /// sampled randomness given by the verifier - pub challenges: Vec<C::ScalarField>, + pub challenges: Vec<F>, /// the current round number pub(crate) round: usize, /// pointer to the virtual polynomial - pub(crate) poly: VirtualPolynomial<C::ScalarField>, + pub(crate) poly: VirtualPolynomial<F>, /// points with precomputed barycentric weights for extrapolating smaller /// degree uni-polys to `max_degree + 1` evaluations. #[allow(clippy::type_complexity)] - pub(crate) extrapolation_aux: Vec<(Vec<C::ScalarField>, Vec<C::ScalarField>)>, + pub(crate) extrapolation_aux: Vec<(Vec<F>, Vec<F>)>, } /// Verifier State of a PolyIOP, generic over a curve group #[derive(Debug)] -pub struct IOPVerifierState<C: CurveGroup> { +pub struct IOPVerifierState<F: PrimeField> { pub(crate) round: usize, pub(crate) num_vars: usize, pub(crate) finished: bool, /// a list storing the univariate polynomial in evaluation form sent by the /// prover at each round - pub(crate) polynomials_received: Vec<Vec<C::ScalarField>>, + pub(crate) polynomials_received: Vec<Vec<F>>, /// a list storing the randomness sampled by the verifier at each round - pub(crate) challenges: Vec<C::ScalarField>, + pub(crate) challenges: Vec<F>, }
M folding-schemes/src/utils/espresso/sum_check/verifier.rs → folding-schemes/src/utils/espresso/sum_check/verifier.rs

@@ -14,7 +14,7 @@ structs::{IOPProverMessage, IOPVerifierState},

SumCheckSubClaim, SumCheckVerifier, }; use crate::{transcript::Transcript, utils::virtual_polynomial::VPAuxInfo}; -use ark_ec::CurveGroup; +use ark_crypto_primitives::sponge::Absorb; use ark_ff::PrimeField; use ark_poly::Polynomial; use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial};

@@ -24,11 +24,11 @@

#[cfg(feature = "parallel")] use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; -impl<C: CurveGroup> SumCheckVerifier<C> for IOPVerifierState<C> { - type VPAuxInfo = VPAuxInfo<C::ScalarField>; - type ProverMessage = IOPProverMessage<C::ScalarField>; - type Challenge = C::ScalarField; - type SumCheckSubClaim = SumCheckSubClaim<C::ScalarField>; +impl<F: PrimeField + Absorb> SumCheckVerifier<F> for IOPVerifierState<F> { + type VPAuxInfo = VPAuxInfo<F>; + type ProverMessage = IOPProverMessage<F>; + type Challenge = F; + type SumCheckSubClaim = SumCheckSubClaim<F>; /// Initialize the verifier's state. fn verifier_init(index_info: &Self::VPAuxInfo) -> Self {

@@ -46,9 +46,9 @@ }

fn verify_round_and_update_state( &mut self, - prover_msg: &<IOPVerifierState<C> as SumCheckVerifier<C>>::ProverMessage, - transcript: &mut impl Transcript<C>, - ) -> Result<<IOPVerifierState<C> as SumCheckVerifier<C>>::Challenge, PolyIOPErrors> { + prover_msg: &<IOPVerifierState<F> as SumCheckVerifier<F>>::ProverMessage, + transcript: &mut impl Transcript<F>, + ) -> Result<<IOPVerifierState<F> as SumCheckVerifier<F>>::Challenge, PolyIOPErrors> { let start = start_timer!(|| format!("sum check verify {}-th round and update state", self.round));

@@ -83,7 +83,7 @@ }

fn check_and_generate_subclaim( &self, - asserted_sum: &C::ScalarField, + asserted_sum: &F, ) -> Result<Self::SumCheckSubClaim, PolyIOPErrors> { let start = start_timer!(|| "sum check check and generate subclaim"); if !self.finished {

@@ -136,8 +136,8 @@ .zip(expected_vec.iter())

.take(self.num_vars) { let poly = DensePolynomial::from_coefficients_slice(coeffs); - let eval_at_one: C::ScalarField = poly.iter().sum(); - let eval_at_zero: C::ScalarField = poly.coeffs[0]; + let eval_at_one: F = poly.iter().sum(); + let eval_at_zero: F = poly.coeffs[0]; let eval = eval_at_one + eval_at_zero; // the deferred check during the interactive phase:
M folding-schemes/src/utils/espresso/virtual_polynomial.rs → folding-schemes/src/utils/espresso/virtual_polynomial.rs

@@ -18,8 +18,6 @@ use rayon::prelude::*;

use std::{cmp::max, collections::HashMap, marker::PhantomData, ops::Add, sync::Arc}; use thiserror::Error; -use ark_std::string::String; - //-- aritherrors /// A `enum` specifying the possible failure modes of the arithmetics. #[derive(Error, Debug)]
M folding-schemes/src/utils/lagrange_poly.rs → folding-schemes/src/utils/lagrange_poly.rs

@@ -52,7 +52,7 @@ use crate::utils::espresso::sum_check::verifier::interpolate_uni_poly;

use crate::utils::lagrange_poly::compute_lagrange_interpolated_poly; use ark_pallas::Fr; use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; - use ark_std::{vec::Vec, UniformRand}; + use ark_std::UniformRand; use espresso_subroutines::poly_iop::prelude::PolyIOPErrors; #[test]
M solidity-verifiers/src/verifiers/kzg.rs → solidity-verifiers/src/verifiers/kzg.rs

@@ -78,7 +78,8 @@ evm::{compile_solidity, Evm},

utils::HeaderInclusion, ProtocolVerifierKey, }; - use ark_bn254::{Bn254, Fr, G1Projective as G1}; + use ark_bn254::{Bn254, Fr}; + use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{BigInteger, PrimeField}; use ark_std::rand::{RngCore, SeedableRng};

@@ -89,10 +90,7 @@ use itertools::chain;

use folding_schemes::{ commitment::{kzg::KZG, CommitmentScheme}, - transcript::{ - poseidon::{poseidon_canonical_config, PoseidonTranscript}, - Transcript, - }, + transcript::{poseidon::poseidon_canonical_config, Transcript}, }; use super::KZG10Verifier;

@@ -133,8 +131,8 @@ #[test]

fn kzg_verifier_accepts_and_rejects_proofs() { let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); let poseidon_config = poseidon_canonical_config::<Fr>(); - let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config); - let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config); + let transcript_p = &mut PoseidonSponge::<Fr>::new(&poseidon_config); + let transcript_v = &mut PoseidonSponge::<Fr>::new(&poseidon_config); let (_, kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN); let kzg_vk = KZG10VerifierKey::from((kzg_vk.clone(), kzg_pk.powers_of_g[0..3].to_vec()));

@@ -159,7 +157,7 @@ let (x_comm, y_comm) = cm_affine.xy().unwrap();

let (x_proof, y_proof) = proof_affine.xy().unwrap(); let y = proof.eval.into_bigint().to_bytes_be(); - transcript_v.absorb_point(&cm).unwrap(); + transcript_v.absorb_nonnative(&cm); let x = transcript_v.get_challenge(); let x = x.into_bigint().to_bytes_be();