Browse Source

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`
update-nifs-interface
winderica 4 months ago
committed by GitHub
parent
commit
16d51d757b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
36 changed files with 1028 additions and 1031 deletions
  1. +0
    -1
      folding-schemes/src/arith/ccs.rs
  2. +0
    -1
      folding-schemes/src/arith/r1cs.rs
  3. +19
    -19
      folding-schemes/src/commitment/ipa.rs
  4. +8
    -7
      folding-schemes/src/commitment/kzg.rs
  5. +9
    -9
      folding-schemes/src/commitment/mod.rs
  6. +10
    -10
      folding-schemes/src/commitment/pedersen.rs
  7. +54
    -92
      folding-schemes/src/folding/circuits/cyclefold.rs
  8. +34
    -40
      folding-schemes/src/folding/circuits/nonnative/affine.rs
  9. +43
    -10
      folding-schemes/src/folding/circuits/nonnative/uint.rs
  10. +46
    -70
      folding-schemes/src/folding/circuits/sum_check.rs
  11. +22
    -0
      folding-schemes/src/folding/hypernova/cccs.rs
  12. +67
    -114
      folding-schemes/src/folding/hypernova/circuits.rs
  13. +35
    -27
      folding-schemes/src/folding/hypernova/lcccs.rs
  14. +38
    -32
      folding-schemes/src/folding/hypernova/mod.rs
  15. +36
    -73
      folding-schemes/src/folding/hypernova/nimfs.rs
  16. +0
    -1
      folding-schemes/src/folding/hypernova/utils.rs
  17. +74
    -100
      folding-schemes/src/folding/nova/circuits.rs
  18. +1
    -3
      folding-schemes/src/folding/nova/decider_eth.rs
  19. +50
    -63
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  20. +103
    -91
      folding-schemes/src/folding/nova/mod.rs
  21. +11
    -8
      folding-schemes/src/folding/nova/nifs.rs
  22. +19
    -23
      folding-schemes/src/folding/protogalaxy/folding.rs
  23. +10
    -0
      folding-schemes/src/folding/protogalaxy/mod.rs
  24. +36
    -17
      folding-schemes/src/folding/protogalaxy/traits.rs
  25. +1
    -2
      folding-schemes/src/frontend/circom/mod.rs
  26. +0
    -1
      folding-schemes/src/frontend/circom/utils.rs
  27. +1
    -3
      folding-schemes/src/frontend/mod.rs
  28. +89
    -17
      folding-schemes/src/transcript/mod.rs
  29. +123
    -96
      folding-schemes/src/transcript/poseidon.rs
  30. +48
    -51
      folding-schemes/src/utils/espresso/sum_check/mod.rs
  31. +15
    -19
      folding-schemes/src/utils/espresso/sum_check/prover.rs
  32. +7
    -8
      folding-schemes/src/utils/espresso/sum_check/structs.rs
  33. +12
    -12
      folding-schemes/src/utils/espresso/sum_check/verifier.rs
  34. +0
    -2
      folding-schemes/src/utils/espresso/virtual_polynomial.rs
  35. +1
    -1
      folding-schemes/src/utils/lagrange_poly.rs
  36. +6
    -8
      solidity-verifiers/src/verifiers/kzg.rs

+ 0
- 1
folding-schemes/src/arith/ccs.rs

@ -114,7 +114,6 @@ impl CCS {
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> {

+ 0
- 1
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> {

+ 19
- 19
folding-schemes/src/commitment/ipa.rs

@ -97,7 +97,7 @@ impl CommitmentScheme for IPA {
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 @@ impl CommitmentScheme for IPA {
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 @@ impl CommitmentScheme for IPA {
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 @@ impl CommitmentScheme for IPA {
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 @@ where
#[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 @@ mod tests {
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 @@ mod tests {
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 @@ mod tests {
// 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();
}

+ 8
- 7
folding-schemes/src/commitment/kzg.rs

@ -156,13 +156,13 @@ where
/// 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 @@ where
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 @@ fn convert_to_bigints(p: &[F]) -> Vec {
#[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>) =

+ 9
- 9
folding-schemes/src/commitment/mod.rs

@ -34,7 +34,7 @@ pub trait CommitmentScheme: Clone + Debug
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 @@ pub trait CommitmentScheme: Clone + Debug
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 @@ pub trait CommitmentScheme: Clone + Debug
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 @@ mod tests {
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 @@ mod tests {
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 @@ mod tests {
.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();
}
}

+ 10
- 10
folding-schemes/src/commitment/pedersen.rs

@ -81,13 +81,13 @@ impl CommitmentScheme for Pedersen {
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 @@ impl CommitmentScheme for Pedersen {
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 @@ impl CommitmentScheme for Pedersen {
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 @@ where
#[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 @@ mod tests {
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)

+ 54
- 92
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 @@ where
}
}
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 @@ where
/// 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 @@ where
/// 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 @@ where
<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 @@ where
#[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 @@ where
)?;
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 @@ where
#[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 @@ pub mod tests {
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 @@ pub mod tests {
// 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 @@ pub mod tests {
})
.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 @@ pub mod tests {
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 @@ pub mod tests {
.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 @@ pub mod tests {
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();

+ 34
- 40
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 @@ where
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 @@ where
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 @@ where
/// 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 @@ mod tests {
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()

+ 43
- 10
folding-schemes/src/folding/circuits/nonnative/uint.rs

@ -3,7 +3,7 @@ use std::{
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 @@ impl AllocVar for NonNativeUintVar {
}
}
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 @@ impl AllocVar for NonNativeUintVar {
) -> 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 AllocVar for NonNativeUintVar {
}
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 @@ impl]>> From for NonNativeUintVar {
}
}
// 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};

+ 46
- 70
folding-schemes/src/folding/circuits/sum_check.rs

@ -3,8 +3,7 @@
/// - 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 std::{borrow::Borrow, marker::PhantomData};
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 @@ impl DensePolynomialVar {
}
#[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 @@ impl AllocVar, F> for VPAuxInfoVar {
}
#[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 @@ impl SumCheckVerifierGadget {
#[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 @@ mod tests {
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 @@ mod tests {
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

+ 22
- 0
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 ark_std::rand::Rng;
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};
@ -118,6 +120,26 @@ impl CCCS {
}
}
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);
}
}
#[cfg(test)]
pub mod tests {
use ark_pallas::Fr;

+ 67
- 114
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 @@ where
#[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 @@ where
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 @@ where
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 @@ where
/// 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 @@ where
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 @@ where
}
// 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 @@ where
// 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 @@ where
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 @@ where
})?;
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 @@ where
// 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 @@ where
// 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 @@ where
// 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 @@ where
// 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 @@ where
// 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 @@ mod tests {
// 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 @@ mod tests {
.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 @@ mod tests {
.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 @@ mod tests {
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 @@ mod tests {
.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 @@ mod tests {
let (hVar, _) = lcccsVar
.clone()
.hash(
&crh_params,
&spongeVar,
pp_hashVar,
iVar.clone(),
z_0Var.clone(),
@ -1108,6 +1089,7 @@ mod tests {
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 @@ mod tests {
let mut cf_W_i = cf_W_dummy.clone();
let mut cf_U_i = cf_U_dummy.clone();
u_i.x = vec![
U_i.hash(
&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 @@ mod tests {
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 @@ mod tests {
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 @@ mod tests {
// 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 @@ mod tests {
Pedersen<Projective>,
Pedersen<Projective2>,
>(
&poseidon_config,
&mut transcript_p,
cf_r1cs.clone(),
cf_pedersen_params.clone(),
pp_hash,
@ -1295,7 +1255,7 @@ mod tests {
// 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 @@ mod tests {
assert_eq!(u_i.x, r1cs_x_i1);
assert_eq!(u_i.x[0], augmented_f_circuit.x.unwrap());
assert_eq!(u_i.x[1], augmented_f_circuit.cf_x.unwrap());
let expected_u_i1_x = U_i1
.hash(
&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);

+ 35
- 27
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 ark_std::Zero;
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 @@ impl LCCCS {
}
}
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 @@ where
/// [`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 @@ pub mod tests {
use ark_std::test_rng;
use ark_std::One;
use ark_std::UniformRand;
use ark_std::Zero;
use std::sync::Arc;
use super::*;

+ 38
- 32
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 @@ where
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 @@ where
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 @@ where
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 @@ where
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 @@ where
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 @@ where
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 @@ where
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 @@ where
}
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 @@ where
// check that u_i's output points to the running instance
// u_i.X[0] == H(i, z_0, z_i, U_i)
let expected_u_i_x = U_i.hash(&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);
}

+ 36
- 73
folding-schemes/src/folding/hypernova/nimfs.rs

@ -13,7 +13,6 @@ use super::{
};
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 @@ pub struct SigmasThetas(pub Vec>, pub Vec>);
#[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 @@ where
/// 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 @@ where
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 @@ where
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 @@ where
/// 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 @@ where
}
// 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 @@ pub mod tests {
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 @@ pub mod tests {
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 @@ pub mod tests {
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 @@ pub mod tests {
// 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 @@ pub mod tests {
.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 @@ pub mod tests {
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 @@ pub mod tests {
// 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 @@ pub mod tests {
.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 @@ pub mod tests {
// 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 @@ pub mod tests {
.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 @@ pub mod tests {
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 @@ pub mod tests {
// 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 @@ pub mod tests {
.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,

+ 0
- 1
folding-schemes/src/folding/hypernova/utils.rs

@ -176,7 +176,6 @@ pub mod tests {
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

+ 74
- 100
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 @@ use ark_r1cs_std::{
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 @@ where
}
}
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 @@ where
/// 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 @@ where
<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 @@ where
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 @@ where
// 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 @@ where
// 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 @@ where
// 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 @@ where
// 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 @@ where
// 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 @@ where
// 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 @@ where
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 @@ pub mod tests {
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 @@ pub mod tests {
};
// 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 @@ pub mod tests {
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 @@ pub mod tests {
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 @@ pub mod tests {
// 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 @@ pub mod tests {
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 @@ pub mod tests {
]
.concat();
let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget(
cs.clone(),
&poseidon_config,
&mut transcriptVar,
pp_hashVar,
U_iVar_vec,
u_iVar,

+ 1
- 3
folding-schemes/src/folding/nova/decider_eth.rs

@ -317,13 +317,11 @@ fn point2_to_eth_format(p: ark_bn254::G2Affine) -> Result, Error> {
#[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;

+ 50
- 63
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 @@ where
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 @@ where
&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 @@ where
// 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 @@ where
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 @@ where
// 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 @@ where
})?;
// 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 @@ where
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 @@ where
// 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 @@ where
<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 @@ pub mod tests {
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 @@ pub mod tests {
// 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

+ 103
- 91
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 CommittedInstance {
}
}
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 @@ where
/// 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 @@ where
/// 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 @@ where
_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 @@ where
// 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 @@ where
// 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 @@ where
// 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 @@ where
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 @@ where
// check that u_i's output points to the running instance
// u_i.X[0] == H(i, z_0, z_i, U_i)
let expected_u_i_x = U_i.hash(&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 @@ where
{
// 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 @@ where
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,

+ 11
- 8
folding-schemes/src/folding/nova/nifs.rs

@ -183,7 +183,7 @@ where
}
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 @@ where
#[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 @@ pub mod tests {
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 @@ pub mod tests {
.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 @@ pub mod tests {
.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(

+ 19
- 23
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 @@ where
#![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 @@ where
}
// 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 @@ where
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 @@ where
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 @@ where
/// 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 @@ where
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 @@ where
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 @@ fn eval_f(r1cs: &R1CS, w: &[F]) -> Result, Error> {
#[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 @@ mod tests {
.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 @@ mod tests {
// 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 @@ mod tests {
// 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);

+ 10
- 0
folding-schemes/src/folding/protogalaxy/mod.rs

@ -1,8 +1,11 @@
/// 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;
pub(crate) mod utils;
@ -14,6 +17,13 @@ pub struct CommittedInstance {
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)]
pub struct Witness<F: PrimeField> {
w: Vec<F>,

+ 36
- 17
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;
/// 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(())
// 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!()
}
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())
}
}

+ 1
- 2
folding-schemes/src/frontend/circom/mod.rs

@ -207,8 +207,7 @@ impl CircomFCircuit {
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]

+ 0
- 1
folding-schemes/src/frontend/circom/utils.rs

@ -110,7 +110,6 @@ mod tests {
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

+ 1
- 3
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

+ 89
- 17
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>);
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;
/// 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>;
}
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.

+ 123
- 96
folding-schemes/src/transcript/poseidon.rs

@ -3,114 +3,86 @@ use ark_crypto_primitives::sponge::{
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 super::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>,
}
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;
impl<C: CurveGroup> Transcript<C> for PoseidonTranscript<C>
where
<C as Group>::ScalarField: Absorb,
{
type TranscriptConfig = PoseidonConfig<C::ScalarField>;
use super::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar};
fn new(poseidon_config: &Self::TranscriptConfig) -> Self {
let sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config);
Self { sponge }
}
fn absorb(&mut self, v: &C::ScalarField) {
self.sponge.absorb(&v);
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_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 @@ pub fn poseidon_canonical_config() -> PoseidonConfig {
#[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]
@ -178,19 +155,69 @@ pub mod tests {
);
}
#[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 @@ pub mod tests {
// 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 @@ pub mod tests {
// 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 @@ pub mod tests {
// 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());

+ 48
- 51
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 @@ pub trait SumCheck {
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 @@ where
/// 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 @@ pub trait SumCheckVerifier {
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 @@ pub trait SumCheckVerifier {
/// 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 struct SumCheckSubClaim {
}
#[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 @@ impl> SumCheck for IOPSumCheck {
}
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 @@ impl> SumCheck for IOPSumCheck {
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 @@ pub mod tests {
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,

+ 15
- 19
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 @@ use espresso_subroutines::poly_iop::prelude::PolyIOPErrors;
// #[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 @@ impl SumCheckProver for IOPProverState {
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 @@ impl SumCheckProver for IOPProverState {
/// 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 @@ impl SumCheckProver for IOPProverState {
// 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 @@ impl SumCheckProver for IOPProverState {
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 @@ impl SumCheckProver for IOPProverState {
.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 @@ impl SumCheckProver for IOPProverState {
*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 @@ impl SumCheckProver for IOPProverState {
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 @@ impl SumCheckProver for IOPProverState {
.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,
})

+ 7
- 8
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 @@ pub struct IOPProverMessage {
/// 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>,
}

+ 12
- 12
folding-schemes/src/utils/espresso/sum_check/verifier.rs

@ -14,7 +14,7 @@ use super::{
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 @@ use espresso_subroutines::poly_iop::prelude::PolyIOPErrors;
#[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 @@ impl SumCheckVerifier for IOPVerifierState {
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 @@ impl SumCheckVerifier for IOPVerifierState {
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 @@ impl SumCheckVerifier for IOPVerifierState {
.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:

+ 0
- 2
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)]

+ 1
- 1
folding-schemes/src/utils/lagrange_poly.rs

@ -52,7 +52,7 @@ mod tests {
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]

+ 6
- 8
solidity-verifiers/src/verifiers/kzg.rs

@ -78,7 +78,8 @@ mod tests {
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 @@ mod tests {
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 @@ mod tests {
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 @@ mod tests {
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();

Loading…
Cancel
Save