Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impl hash committed instance gadget (#17) #31

Merged
merged 1 commit into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"]
Expand Down
1 change: 1 addition & 0 deletions src/folding/circuits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 71 additions & 0 deletions src/folding/circuits/nonnative.rs
Original file line number Diff line number Diff line change
@@ -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))
}
176 changes: 136 additions & 40 deletions src/folding/nova/circuits.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -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>>>,
Expand All @@ -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>,
{
Expand All @@ -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>>,
Expand Down Expand Up @@ -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))?;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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);
}
Expand All @@ -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();

Expand All @@ -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(
Expand All @@ -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);
}
}
Loading
Loading