Browse Source

impl hash committed instance gadget (#17) (#31)

together with the native impl compatible with the gadget one
main
arnaucube 11 months ago
committed by GitHub
parent
commit
422db752f9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 249 additions and 44 deletions
  1. +1
    -2
      Cargo.toml
  2. +1
    -0
      src/folding/circuits/mod.rs
  3. +71
    -0
      src/folding/circuits/nonnative.rs
  4. +136
    -40
      src/folding/nova/circuits.rs
  5. +40
    -2
      src/folding/nova/mod.rs

+ 1
- 2
Cargo.toml

@ -8,7 +8,7 @@ ark-ec = "0.4.2"
ark-ff = "^0.4.0"
ark-poly = "^0.4.0"
ark-std = "^0.4.0"
ark-crypto-primitives = { version = "^0.4.0", default-features = false, features = ["r1cs", "sponge"] }
ark-crypto-primitives = { version = "^0.4.0", default-features = false, features = ["r1cs", "sponge", "crh"] }
ark-relations = { version = "^0.4.0", default-features = false }
ark-r1cs-std = { version = "^0.4.0", default-features = false }
thiserror = "1.0"
@ -23,7 +23,6 @@ espresso_transcript = {git="https://github.com/EspressoSystems/hyperplonk", pack
[dev-dependencies]
ark-pallas = {version="0.4.0", features=["r1cs"]}
ark-vesta = {version="0.4.0"}
ark-crypto-primitives = { version = "^0.4.0", default-features = false, features = ["crh"] }
[features]
default = ["parallel", "nova", "hypernova"]

+ 1
- 0
src/folding/circuits/mod.rs

@ -3,6 +3,7 @@ use ark_ec::CurveGroup;
use ark_ff::Field;
pub mod cyclefold;
pub mod nonnative;
// CF represents the constraints field
pub type CF<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;

+ 71
- 0
src/folding/circuits/nonnative.rs

@ -0,0 +1,71 @@
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::PrimeField;
use ark_r1cs_std::fields::nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar};
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
fields::nonnative::NonNativeFieldVar,
};
use ark_relations::r1cs::{Namespace, SynthesisError};
use core::borrow::Borrow;
/// NonNativeAffineVar represents an elliptic curve point in Affine represenation in the non-native
/// 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<F: PrimeField, CF: PrimeField> {
pub x: NonNativeFieldVar<F, CF>,
pub y: NonNativeFieldVar<F, CF>,
}
impl<C> AllocVar<C, C::ScalarField> for NonNativeAffineVar<C::BaseField, C::ScalarField>
where
C: CurveGroup,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
fn new_variable<T: Borrow<C>>(
cs: impl Into<Namespace<C::ScalarField>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|val| {
let cs = cs.into();
let affine = val.borrow().into_affine();
let xy = affine.xy().unwrap();
let x = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
cs.clone(),
|| Ok(xy.0),
mode,
)?;
let y = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
cs.clone(),
|| Ok(xy.1),
mode,
)?;
Ok(Self { x, y })
})
}
}
/// point_to_nonnative_limbs is used to return (outside the circuit) the limbs representation that
/// matches the one used in-circuit.
#[allow(clippy::type_complexity)]
pub fn point_to_nonnative_limbs<C: CurveGroup>(
p: C,
) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
let affine = p.into_affine();
let (x, y) = affine.xy().unwrap();
let x = AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
x,
OptimizationType::Weight,
)?;
let y = AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
y,
OptimizationType::Weight,
)?;
Ok((x, y))
}

+ 136
- 40
src/folding/nova/circuits.rs

@ -1,4 +1,9 @@
use ark_ec::{AffineRepr, CurveGroup};
use ark_crypto_primitives::crh::{
poseidon::constraints::{CRHGadget, CRHParametersVar},
CRHSchemeGadget,
};
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::Field;
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
@ -7,30 +12,38 @@ use ark_r1cs_std::{
fields::fp::FpVar,
groups::GroupOpsBounds,
prelude::CurveVar,
ToConstraintFieldGadget,
};
use ark_relations::r1cs::{Namespace, SynthesisError};
use core::{borrow::Borrow, marker::PhantomData};
use super::CommittedInstance;
use crate::folding::circuits::cyclefold::ECRLC;
use crate::folding::circuits::{cyclefold::ECRLC, nonnative::NonNativeAffineVar};
/// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr.
/// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr, where
/// E1 is the main curve where we do the folding.
pub type CF1<C> = <<C as CurveGroup>::Affine as AffineRepr>::ScalarField;
/// CF2 represents the ConstraintField used for the CycleFold circuit which is over E2::Fr=E1::Fq.
/// CF2 represents the ConstraintField used for the CycleFold circuit which is over E2::Fr=E1::Fq,
/// where E2 is the auxiliary curve (from [CycleFold](https://eprint.iacr.org/2023/1192.pdf)
/// approach) where we check the folding of the commitments.
pub type CF2<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
/// CommittedInstance on E1 contains the u and x values which are folded on the main Nova
/// constraints field (E1::Fr).
/// 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).
#[derive(Debug, Clone)]
pub struct CommittedInstanceE1Var<C: CurveGroup> {
pub struct CommittedInstanceVar<C: CurveGroup> {
_c: PhantomData<C>,
u: FpVar<C::ScalarField>,
x: Vec<FpVar<C::ScalarField>>,
#[allow(dead_code)] // tmp while we don't have the code of the AugmentedFGadget
cmE: NonNativeAffineVar<CF2<C>, C::ScalarField>,
cmW: NonNativeAffineVar<CF2<C>, C::ScalarField>,
}
impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceE1Var<C>
impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceVar<C>
where
C: CurveGroup,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
fn new_variable<T: Borrow<CommittedInstance<C>>>(
cs: impl Into<Namespace<CF1<C>>>,
@ -42,20 +55,63 @@ where
let u = FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
let x: Vec<FpVar<C::ScalarField>> =
Vec::new_variable(cs, || Ok(val.borrow().x.clone()), mode)?;
Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?;
let cmE = NonNativeAffineVar::<CF2<C>, C::ScalarField>::new_variable(
cs.clone(),
|| Ok(val.borrow().cmE),
mode,
)?;
let cmW = NonNativeAffineVar::<CF2<C>, C::ScalarField>::new_variable(
cs.clone(),
|| Ok(val.borrow().cmW),
mode,
)?;
Ok(Self {
_c: PhantomData,
u,
x,
cmE,
cmW,
})
})
}
}
/// CommittedInstance on E2 contains the commitments to E and W, which are folded on the auxiliary
/// curve constraints field (E2::Fr = E1::Fq).
pub struct CommittedInstanceE2Var<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
impl<C> CommittedInstanceVar<C>
where
C: CurveGroup,
<C as Group>::ScalarField: Absorb,
{
/// hash implements the committed instance hash compatible with the native implementation from
/// CommittedInstance.hash.
/// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the
/// `CommittedInstance`.
#[allow(dead_code)] // tmp while we don't have the code of the AugmentedFGadget
fn hash(
self,
crh_params: &CRHParametersVar<CF1<C>>,
i: FpVar<CF1<C>>,
z_0: FpVar<CF1<C>>,
z_i: FpVar<CF1<C>>,
) -> Result<FpVar<CF1<C>>, SynthesisError> {
let input = vec![
vec![i, z_0, z_i, self.u],
self.x,
self.cmE.x.to_constraint_field()?,
self.cmE.y.to_constraint_field()?,
self.cmW.x.to_constraint_field()?,
self.cmW.y.to_constraint_field()?,
]
.concat();
CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)
}
}
/// CommittedInstanceCycleFoldVar represents the commitments to E and W from the CommittedInstance
/// on the E2, which are folded on the auxiliary curve constraints field (E2::Fr = E1::Fq).
pub struct CommittedInstanceCycleFoldVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
where
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
@ -64,7 +120,7 @@ where
cmW: GC,
}
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CommittedInstanceE2Var<C, GC>
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CommittedInstanceCycleFoldVar<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
@ -105,9 +161,9 @@ where
/// the CycleFold circuit.
pub fn verify(
r: FpVar<CF1<C>>,
ci1: CommittedInstanceE1Var<C>,
ci2: CommittedInstanceE1Var<C>,
ci3: CommittedInstanceE1Var<C>,
ci1: CommittedInstanceVar<C>,
ci2: CommittedInstanceVar<C>,
ci3: CommittedInstanceVar<C>,
) -> Result<(), SynthesisError> {
// ensure that: ci3.u == ci1.u + r * ci2.u
ci3.u.enforce_equal(&(ci1.u + r.clone() * ci2.u))?;
@ -140,9 +196,9 @@ where
pub fn verify(
r_bits: Vec<Boolean<CF2<C>>>,
cmT: GC,
ci1: CommittedInstanceE2Var<C, GC>,
ci2: CommittedInstanceE2Var<C, GC>,
ci3: CommittedInstanceE2Var<C, GC>,
ci1: CommittedInstanceCycleFoldVar<C, GC>,
ci2: CommittedInstanceCycleFoldVar<C, GC>,
ci3: CommittedInstanceCycleFoldVar<C, GC>,
) -> Result<(), SynthesisError> {
// cm(E) check: ci3.cmE == ci1.cmE + r * cmT + r^2 * ci2.cmE
ci3.cmE.enforce_equal(
@ -183,16 +239,17 @@ mod tests {
let cs = ConstraintSystem::<Fr>::new_ref();
let ciVar =
CommittedInstanceE1Var::<Projective>::new_witness(cs.clone(), || Ok(ci.clone()))
.unwrap();
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
assert_eq!(ciVar.u.value().unwrap(), ci.u);
assert_eq!(ciVar.x.value().unwrap(), ci.x);
// check the instantiation of the CycleFold side:
let cs = ConstraintSystem::<Fq>::new_ref();
let ciVar =
CommittedInstanceE2Var::<Projective, GVar>::new_witness(cs.clone(), || Ok(ci.clone()))
.unwrap();
CommittedInstanceCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(ci.clone())
})
.unwrap();
assert_eq!(ciVar.cmE.value().unwrap(), ci.cmE);
assert_eq!(ciVar.cmW.value().unwrap(), ci.cmW);
}
@ -216,8 +273,8 @@ mod tests {
let ci2 = w2.commit(&pedersen_params, x2.clone());
// get challenge from transcript
let config = poseidon_test_config::<Fr>();
let mut tr = PoseidonTranscript::<Projective>::new(&config);
let poseidon_config = poseidon_test_config::<Fr>();
let mut tr = PoseidonTranscript::<Projective>::new(&poseidon_config);
let r_bits = tr.get_challenge_nbits(128);
let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
@ -228,13 +285,13 @@ mod tests {
let rVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
let ci1Var =
CommittedInstanceE1Var::<Projective>::new_witness(cs.clone(), || Ok(ci1.clone()))
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci1.clone()))
.unwrap();
let ci2Var =
CommittedInstanceE1Var::<Projective>::new_witness(cs.clone(), || Ok(ci2.clone()))
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci2.clone()))
.unwrap();
let ci3Var =
CommittedInstanceE1Var::<Projective>::new_witness(cs.clone(), || Ok(ci3.clone()))
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci3.clone()))
.unwrap();
NIFSGadget::<Projective>::verify(
@ -253,21 +310,60 @@ mod tests {
let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs_CC.clone(), || Ok(r_bits)).unwrap();
let cmTVar = GVar::new_witness(cs_CC.clone(), || Ok(cmT)).unwrap();
let ci1Var = CommittedInstanceE2Var::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci1.clone())
})
.unwrap();
let ci2Var = CommittedInstanceE2Var::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci2.clone())
})
.unwrap();
let ci3Var = CommittedInstanceE2Var::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci3.clone())
})
.unwrap();
let ci1Var =
CommittedInstanceCycleFoldVar::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci1.clone())
})
.unwrap();
let ci2Var =
CommittedInstanceCycleFoldVar::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci2.clone())
})
.unwrap();
let ci3Var =
CommittedInstanceCycleFoldVar::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci3.clone())
})
.unwrap();
NIFSCycleFoldGadget::<Projective, GVar>::verify(r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var)
.unwrap();
assert!(cs_CC.is_satisfied().unwrap());
}
#[test]
fn test_committed_instance_hash() {
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_test_config::<Fr>();
let i = Fr::from(3_u32);
let z_0 = Fr::from(3_u32);
let z_i = Fr::from(3_u32);
let ci = CommittedInstance::<Projective> {
cmE: Projective::rand(&mut rng),
u: Fr::rand(&mut rng),
cmW: Projective::rand(&mut rng),
x: vec![Fr::rand(&mut rng); 1],
};
// compute the CommittedInstance hash natively
let h = ci.hash(&poseidon_config, i, z_0, z_i).unwrap();
let cs = ConstraintSystem::<Fr>::new_ref();
let iVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(i)).unwrap();
let z_0Var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(z_0)).unwrap();
let z_iVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
let ciVar =
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap();
// compute the CommittedInstance hash in-circuit
let hVar = ciVar.hash(&crh_params, iVar, z_0Var, z_iVar).unwrap();
assert!(cs.is_satisfied().unwrap());
// check that the natively computed and in-circuit computed hashes match
assert_eq!(hVar.value().unwrap(), h);
}
}

+ 40
- 2
src/folding/nova/mod.rs

@ -1,10 +1,15 @@
/// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf)
use ark_crypto_primitives::sponge::Absorb;
use ark_crypto_primitives::{
crh::{poseidon::CRH, CRHScheme},
sponge::{poseidon::PoseidonConfig, Absorb},
};
use ark_ec::{CurveGroup, Group};
use ark_std::fmt::Debug;
use ark_std::{One, Zero};
use crate::folding::circuits::nonnative::point_to_nonnative_limbs;
use crate::pedersen::{Params as PedersenParams, Pedersen};
use crate::Error;
pub mod circuits;
pub mod nifs;
@ -17,7 +22,11 @@ pub struct CommittedInstance {
pub x: Vec<C::ScalarField>,
}
impl<C: CurveGroup> CommittedInstance<C> {
impl<C: CurveGroup> CommittedInstance<C>
where
<C as Group>::ScalarField: Absorb,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
pub fn empty() -> Self {
CommittedInstance {
cmE: C::zero(),
@ -26,6 +35,35 @@ impl CommittedInstance {
x: Vec::new(),
}
}
/// 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` is the
/// `CommittedInstance`.
pub fn hash(
&self,
poseidon_config: &PoseidonConfig<C::ScalarField>,
i: C::ScalarField,
z_0: C::ScalarField,
z_i: C::ScalarField,
) -> Result<C::ScalarField, Error> {
let (cmE_x, cmE_y) = point_to_nonnative_limbs::<C>(self.cmE)?;
let (cmW_x, cmW_y) = point_to_nonnative_limbs::<C>(self.cmW)?;
Ok(CRH::<C::ScalarField>::evaluate(
poseidon_config,
vec![
vec![i, z_0, z_i, self.u],
self.x.clone(),
cmE_x,
cmE_y,
cmW_x,
cmW_y,
]
.concat(),
)
.unwrap())
}
}
#[derive(Debug, Clone, Eq, PartialEq)]

Loading…
Cancel
Save