mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-28 14:56:40 +01:00
Add solidity groth16, kzg10 and final decider verifiers in a dedicated workspace (#70)
* change: Refactor structure into workspace * chore: Add empty readme * change: Transform repo into workspace * add: Create folding-verifier-solidity crate * add: Include askama.toml for `sol` extension escaper * add: Jordi's old Groth16 verifier .sol template and adapt it * tmp: create simple template struct to test * Update FoldingSchemes trait, fit Nova+CycleFold - update lib.rs's `FoldingScheme` trait interface - fit Nova+CycleFold into the `FoldingScheme` trait - refactor `src/nova/*` * chore: add serialization assets for testing Now we include an `assets` folder with a serialized proof & vk for tests * Add `examples` dir, with Nova's `FoldingScheme` example * polishing * expose poseidon_test_config outside tests * change: Refactor structure into workspace * chore: Add empty readme * change: Transform repo into workspace * add: Create folding-verifier-solidity crate * add: Include askama.toml for `sol` extension escaper * add: Jordi's old Groth16 verifier .sol template and adapt it * tmp: create simple template struct to test * feat: templating kzg working * chore: add emv and revm * feat: start evm file * chore: add ark-poly-commit * chore: move `commitment` to `folding-schemes` * chore: update `.gitignore` to ignore generated contracts * chore: update template with bn254 lib on it (avoids import), update for loop to account for whitespaces * refactor: update template with no lib * feat: add evm deploy code, compile and create kzg verifier * chore: update `Cargo.toml` to have `folding-schemes` available with verifiers * feat: start kzg prove and verify with sol * chore: compute crs from kzg prover * feat: evm kzg verification passing * tmp * change: Swap order of G2 coordinates within the template * Update way to serialize proof with correct order * chore: update `Cargo.toml` * chore: add revm * chore: add `save_solidity` * refactor: verifiers in dedicated mod * refactor: have dedicated `utils` module * chore: expose modules * chore: update verifier for kzg * chore: rename templates * fix: look for binary using also name of contract * refactor: generate groth16 proof for sha256 pre-image, generate groth16 template with verifying key * chore: template renaming * fix: switch circuit for circuit that simply adds * feat: generates test data on the fly * feat: update to latest groth16 verifier * refactor: rename folder, update `.gitignore` * chore: update `Cargo.toml` * chore: update templates extension to indicate that they are templates * chore: rename templates, both files and structs * fix: template inheritance working * feat: template spdx and pragma statements * feat: decider verifier compiles, update test for kzg10 and groth16 templates * feat: parameterize which size of the crs should be stored on the contract * chore: add comment on how the groth16 and kzg10 proofs will be linked together * chore: cargo clippy run * chore: cargo clippy tests * chore: cargo fmt * refactor: remove unused lifetime parameter * chore: end merge * chore: move examples to `folding-schemes` workspace * get latest main changes * fix: temp fix clippy warnings, will remove lints once not used in tests only * fix: cargo clippy lint added on `code_size` * fix: update path to test circuit and add step for installing solc * chore: remove `save_solidity` steps * fix: the borrowed expression implements the required traits * chore: update `Cargo.toml` * chore: remove extra `[patch.crates-io]` * fix: update to patch at the workspace level and add comment explaining this * refactor: correct `staticcall` with valid input/output sizes and change return syntax for pairing * refactor: expose modules and remove `dead_code` calls * chore: update `README.md`, add additional comments on `kzg10` template and update `groth16` template comments * chore: be clearer on attributions on `kzg10` --------- Co-authored-by: CPerezz <c.perezbaro@gmail.com> Co-authored-by: arnaucube <root@arnaucube.com>
This commit is contained in:
848
folding-schemes/src/folding/nova/circuits.rs
Normal file
848
folding-schemes/src/folding/nova/circuits.rs
Normal file
@@ -0,0 +1,848 @@
|
||||
/// 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},
|
||||
Absorb, CryptographicSponge,
|
||||
};
|
||||
use ark_ec::{AffineRepr, CurveGroup, Group};
|
||||
use ark_ff::{Field, PrimeField};
|
||||
use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
|
||||
groups::GroupOpsBounds,
|
||||
prelude::CurveVar,
|
||||
ToBitsGadget, ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
|
||||
use ark_std::fmt::Debug;
|
||||
use ark_std::{One, Zero};
|
||||
use core::{borrow::Borrow, marker::PhantomData};
|
||||
|
||||
use super::{
|
||||
cyclefold::{
|
||||
CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, CF_IO_LEN,
|
||||
},
|
||||
CommittedInstance,
|
||||
};
|
||||
use crate::constants::N_BITS_RO;
|
||||
use crate::folding::circuits::nonnative::{point_to_nonnative_limbs, NonNativeAffineVar};
|
||||
use crate::frontend::FCircuit;
|
||||
|
||||
/// 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,
|
||||
/// where E2 is the auxiliary curve (from [CycleFold](https://eprint.iacr.org/2023/1192.pdf)
|
||||
/// approach) where we check the folding of the commitments (elliptic curve points).
|
||||
pub type CF2<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
|
||||
|
||||
/// CommittedInstanceVar contains the u, x, cmE and cmW values which are folded on the main Nova
|
||||
/// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are
|
||||
/// represented non-natively over the constraint field.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommittedInstanceVar<C: CurveGroup> {
|
||||
pub u: FpVar<C::ScalarField>,
|
||||
pub x: Vec<FpVar<C::ScalarField>>,
|
||||
pub cmE: NonNativeAffineVar<C::ScalarField>,
|
||||
pub cmW: NonNativeAffineVar<C::ScalarField>,
|
||||
}
|
||||
|
||||
impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceVar<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as ark_ec::CurveGroup>::BaseField: PrimeField,
|
||||
{
|
||||
fn new_variable<T: Borrow<CommittedInstance<C>>>(
|
||||
cs: impl Into<Namespace<CF1<C>>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
|
||||
let u = FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
|
||||
let x: Vec<FpVar<C::ScalarField>> =
|
||||
Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?;
|
||||
|
||||
let cmE = NonNativeAffineVar::<C::ScalarField>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(val.borrow().cmE),
|
||||
mode,
|
||||
)?;
|
||||
let cmW = NonNativeAffineVar::<C::ScalarField>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(val.borrow().cmW),
|
||||
mode,
|
||||
)?;
|
||||
|
||||
Ok(Self { u, x, cmE, cmW })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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`.
|
||||
pub fn hash(
|
||||
self,
|
||||
crh_params: &CRHParametersVar<CF1<C>>,
|
||||
i: FpVar<CF1<C>>,
|
||||
z_0: Vec<FpVar<CF1<C>>>,
|
||||
z_i: Vec<FpVar<CF1<C>>>,
|
||||
) -> Result<FpVar<CF1<C>>, SynthesisError> {
|
||||
let input = vec![
|
||||
vec![i],
|
||||
z_0,
|
||||
z_i,
|
||||
vec![self.u],
|
||||
self.x,
|
||||
self.cmE.x,
|
||||
self.cmE.y,
|
||||
self.cmW.x,
|
||||
self.cmW.y,
|
||||
]
|
||||
.concat();
|
||||
CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier
|
||||
/// described in section 4 of [Nova](https://eprint.iacr.org/2021/370.pdf), where the cmE & cmW checks are
|
||||
/// delegated to the NIFSCycleFoldGadget.
|
||||
pub struct NIFSGadget<C: CurveGroup> {
|
||||
_c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> NIFSGadget<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
{
|
||||
/// Implements the constraints for NIFS.V for u and x, since cm(E) and cm(W) are delegated to
|
||||
/// the CycleFold circuit.
|
||||
pub fn verify(
|
||||
r: FpVar<CF1<C>>,
|
||||
ci1: CommittedInstanceVar<C>,
|
||||
ci2: CommittedInstanceVar<C>,
|
||||
ci3: CommittedInstanceVar<C>,
|
||||
) -> Result<Boolean<CF1<C>>, SynthesisError> {
|
||||
// ensure that: ci3.u == ci1.u + r * ci2.u
|
||||
let first_check = ci3.u.is_eq(&(ci1.u + r.clone() * ci2.u))?;
|
||||
|
||||
// ensure that: ci3.x == ci1.x + r * ci2.x
|
||||
let x_rlc = ci1
|
||||
.x
|
||||
.iter()
|
||||
.zip(ci2.x)
|
||||
.map(|(a, b)| a + &r * &b)
|
||||
.collect::<Vec<FpVar<CF1<C>>>>();
|
||||
let second_check = x_rlc.is_eq(&ci3.x)?;
|
||||
|
||||
first_check.and(&second_check)
|
||||
}
|
||||
}
|
||||
|
||||
/// ChallengeGadget computes the RO challenge used for the Nova instances NIFS, it contains a
|
||||
/// rust-native and a in-circuit compatible versions.
|
||||
pub struct ChallengeGadget<C: CurveGroup> {
|
||||
_c: PhantomData<C>,
|
||||
}
|
||||
impl<C: CurveGroup> ChallengeGadget<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as CurveGroup>::BaseField: PrimeField,
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
{
|
||||
pub fn get_challenge_native(
|
||||
poseidon_config: &PoseidonConfig<C::ScalarField>,
|
||||
u_i: CommittedInstance<C>,
|
||||
U_i: CommittedInstance<C>,
|
||||
cmT: C,
|
||||
) -> Result<Vec<bool>, SynthesisError> {
|
||||
let (u_cmE_x, u_cmE_y) = point_to_nonnative_limbs::<C>(u_i.cmE)?;
|
||||
let (u_cmW_x, u_cmW_y) = point_to_nonnative_limbs::<C>(u_i.cmW)?;
|
||||
let (U_cmE_x, U_cmE_y) = point_to_nonnative_limbs::<C>(U_i.cmE)?;
|
||||
let (U_cmW_x, U_cmW_y) = point_to_nonnative_limbs::<C>(U_i.cmW)?;
|
||||
let (cmT_x, cmT_y) = point_to_nonnative_limbs::<C>(cmT)?;
|
||||
|
||||
let mut sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config);
|
||||
let input = vec![
|
||||
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)
|
||||
}
|
||||
|
||||
// compatible with the native get_challenge_native
|
||||
pub fn get_challenge_gadget(
|
||||
cs: ConstraintSystemRef<C::ScalarField>,
|
||||
poseidon_config: &PoseidonConfig<C::ScalarField>,
|
||||
u_i: CommittedInstanceVar<C>,
|
||||
U_i: CommittedInstanceVar<C>,
|
||||
cmT: NonNativeAffineVar<C::ScalarField>,
|
||||
) -> Result<Vec<Boolean<C::ScalarField>>, SynthesisError> {
|
||||
let mut sponge = PoseidonSpongeVar::<C::ScalarField>::new(cs, poseidon_config);
|
||||
|
||||
let input: Vec<FpVar<C::ScalarField>> = vec![
|
||||
vec![u_i.u.clone()],
|
||||
u_i.x.clone(),
|
||||
u_i.cmE.x,
|
||||
u_i.cmE.y,
|
||||
u_i.cmW.x,
|
||||
u_i.cmW.y,
|
||||
vec![U_i.u.clone()],
|
||||
U_i.x.clone(),
|
||||
U_i.cmE.x,
|
||||
U_i.cmE.y,
|
||||
U_i.cmW.x,
|
||||
U_i.cmW.y,
|
||||
cmT.x,
|
||||
cmT.y,
|
||||
]
|
||||
.concat();
|
||||
sponge.absorb(&input)?;
|
||||
let bits = sponge.squeeze_bits(N_BITS_RO)?;
|
||||
Ok(bits)
|
||||
}
|
||||
}
|
||||
|
||||
/// AugmentedFCircuit implements the F' circuit (augmented F) defined in
|
||||
/// [Nova](https://eprint.iacr.org/2021/370.pdf) together with the extra constraints defined in
|
||||
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AugmentedFCircuit<
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<CF1<C1>>,
|
||||
> where
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
pub _gc2: PhantomData<GC2>,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
pub i: Option<CF1<C1>>,
|
||||
pub z_0: Option<Vec<C1::ScalarField>>,
|
||||
pub z_i: Option<Vec<C1::ScalarField>>,
|
||||
pub u_i: Option<CommittedInstance<C1>>,
|
||||
pub U_i: Option<CommittedInstance<C1>>,
|
||||
pub U_i1: Option<CommittedInstance<C1>>,
|
||||
pub cmT: Option<C1>,
|
||||
pub F: FC, // F circuit
|
||||
pub x: Option<CF1<C1>>, // public inputs (u_{i+1}.x)
|
||||
|
||||
// cyclefold verifier on C1
|
||||
pub cf_u_i: Option<CommittedInstance<C2>>,
|
||||
pub cf_U_i: Option<CommittedInstance<C2>>,
|
||||
pub cf_U_i1: Option<CommittedInstance<C2>>,
|
||||
pub cf_cmT: Option<C2>,
|
||||
pub cf_r_nonnat: Option<C2::ScalarField>,
|
||||
}
|
||||
|
||||
impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>
|
||||
AugmentedFCircuit<C1, C2, GC2, FC>
|
||||
where
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
pub fn empty(poseidon_config: &PoseidonConfig<CF1<C1>>, F_circuit: FC) -> Self {
|
||||
Self {
|
||||
_gc2: PhantomData,
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
i: None,
|
||||
z_0: None,
|
||||
z_i: None,
|
||||
u_i: None,
|
||||
U_i: None,
|
||||
U_i1: None,
|
||||
cmT: None,
|
||||
F: F_circuit,
|
||||
x: None,
|
||||
// cyclefold values
|
||||
cf_u_i: None,
|
||||
cf_U_i: None,
|
||||
cf_U_i1: None,
|
||||
cf_cmT: None,
|
||||
cf_r_nonnat: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1, C2, GC2, FC> ConstraintSynthesizer<CF1<C1>> for AugmentedFCircuit<C1, C2, GC2, FC>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<CF1<C1>>,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
|
||||
let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
|
||||
Ok(self.i.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let z_0 = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
|
||||
Ok(self.z_0.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
let z_i = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
|
||||
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
|
||||
let u_dummy_native = CommittedInstance::<C1>::dummy(1);
|
||||
let u_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let U_i1 = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.U_i1.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let cmT =
|
||||
NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?;
|
||||
let x =
|
||||
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.x.unwrap_or_else(CF1::<C1>::zero)))?;
|
||||
|
||||
let crh_params = CRHParametersVar::<C1::ScalarField>::new_constant(
|
||||
cs.clone(),
|
||||
self.poseidon_config.clone(),
|
||||
)?;
|
||||
|
||||
// get z_{i+1} from the F circuit
|
||||
let z_i1 = self.F.generate_step_constraints(cs.clone(), z_i.clone())?;
|
||||
|
||||
let zero = FpVar::<CF1<C1>>::new_constant(cs.clone(), CF1::<C1>::zero())?;
|
||||
let is_not_basecase = i.is_neq(&zero)?;
|
||||
|
||||
// 1. u_i.x == H(i, z_0, z_i, U_i)
|
||||
let u_i_x = U_i
|
||||
.clone()
|
||||
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
|
||||
|
||||
// check that h == u_i.x
|
||||
(u_i.x[0]).conditional_enforce_equal(&u_i_x, &is_not_basecase)?;
|
||||
|
||||
// 2. u_i.cmE==cm(0), u_i.u==1
|
||||
let zero_x = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
|
||||
cs.clone(),
|
||||
C1::BaseField::zero(),
|
||||
)?
|
||||
.to_constraint_field()?;
|
||||
let zero_y = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
|
||||
cs.clone(),
|
||||
C1::BaseField::one(),
|
||||
)?
|
||||
.to_constraint_field()?;
|
||||
(u_i.cmE.x.is_eq(&zero_x)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
|
||||
(u_i.cmE.y.is_eq(&zero_y)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
|
||||
(u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
|
||||
|
||||
// 3. nifs.verify, checks that folding u_i & U_i obtains U_{i+1}.
|
||||
// compute r = H(u_i, U_i, cmT)
|
||||
let r_bits = ChallengeGadget::<C1>::get_challenge_gadget(
|
||||
cs.clone(),
|
||||
&self.poseidon_config,
|
||||
u_i.clone(),
|
||||
U_i.clone(),
|
||||
cmT.clone(),
|
||||
)?;
|
||||
let r = Boolean::le_bits_to_fp_var(&r_bits)?;
|
||||
|
||||
// Notice that NIFSGadget::verify is not checking the folding of cmE & cmW, since it will
|
||||
// be done on the other curve.
|
||||
let nifs_check = NIFSGadget::<C1>::verify(r, u_i.clone(), U_i.clone(), U_i1.clone())?;
|
||||
nifs_check.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
|
||||
|
||||
// 4. u_{i+1}.x = H(i+1, z_0, z_i+1, U_{i+1}), this is the output of F'
|
||||
let u_i1_x = U_i1.clone().hash(
|
||||
&crh_params,
|
||||
i + FpVar::<CF1<C1>>::one(),
|
||||
z_0.clone(),
|
||||
z_i1.clone(),
|
||||
)?;
|
||||
|
||||
u_i1_x.enforce_equal(&x)?;
|
||||
|
||||
// CycleFold part
|
||||
let cf_u_dummy_native = CommittedInstance::<C2>::dummy(CF_IO_LEN);
|
||||
let cf_u_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_u_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
|
||||
})?;
|
||||
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
|
||||
})?;
|
||||
let cf_U_i1 = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone()))
|
||||
})?;
|
||||
let cf_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf_cmT.unwrap_or_else(C2::zero)))?;
|
||||
// cf_r_nonnat is an auxiliary input
|
||||
let cf_r_nonnat =
|
||||
NonNativeFieldVar::<C2::ScalarField, CF2<C2>>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_r_nonnat.unwrap_or_else(C2::ScalarField::zero))
|
||||
})?;
|
||||
|
||||
// check that cf_u_i.x == [u_i, U_i, U_{i+1}]
|
||||
let incircuit_x = vec![
|
||||
u_i.cmE.x, u_i.cmE.y, u_i.cmW.x, u_i.cmW.y, U_i.cmE.x, U_i.cmE.y, U_i.cmW.x, U_i.cmW.y,
|
||||
U_i1.cmE.x, U_i1.cmE.y, U_i1.cmW.x, U_i1.cmW.y,
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut cf_u_i_x: Vec<FpVar<CF2<C2>>> = vec![];
|
||||
for x_i in cf_u_i.x.iter() {
|
||||
let mut x_fpvar = x_i.to_constraint_field()?;
|
||||
cf_u_i_x.append(&mut x_fpvar);
|
||||
}
|
||||
cf_u_i_x.conditional_enforce_equal(&incircuit_x, &is_not_basecase)?;
|
||||
|
||||
// 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,
|
||||
cf_u_i.clone(),
|
||||
cf_U_i.clone(),
|
||||
cf_cmT.clone(),
|
||||
)?;
|
||||
// assert that cf_r_bits == cf_r_nonnat converted to bits. cf_r_nonnat is just an auxiliary
|
||||
// value used to compute RLC of NonNativeFieldVar values, since we can convert
|
||||
// NonNativeFieldVar into Vec<Boolean>, but not in the other direction.
|
||||
let cf_r_nonnat_bits = cf_r_nonnat.to_bits_le()?;
|
||||
cf_r_bits.conditional_enforce_equal(&cf_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?;
|
||||
|
||||
// check cf_u_i.cmE=0, cf_u_i.u=1
|
||||
(cf_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
|
||||
(cf_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
|
||||
|
||||
// check the fold of all the parameteres of the CycleFold instances, where the elliptic
|
||||
// curve points relations are checked natively in Curve1 circuit (this one)
|
||||
let v = NIFSFullGadget::<C2, GC2>::verify(
|
||||
cf_r_bits,
|
||||
cf_r_nonnat,
|
||||
cf_cmT,
|
||||
cf_U_i,
|
||||
cf_u_i,
|
||||
cf_U_i1,
|
||||
)?;
|
||||
v.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ark_ff::BigInteger;
|
||||
use ark_pallas::{Fq, Fr, Projective};
|
||||
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
|
||||
use ark_relations::r1cs::{ConstraintLayer, ConstraintSystem, TracingMode};
|
||||
use ark_std::One;
|
||||
use ark_std::UniformRand;
|
||||
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
use crate::ccs::r1cs::{extract_r1cs, extract_w_x};
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
|
||||
use crate::folding::nova::{
|
||||
get_committed_instance_coordinates, nifs::NIFS, traits::NovaR1CS, Witness,
|
||||
};
|
||||
use crate::frontend::tests::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_test_config;
|
||||
|
||||
#[test]
|
||||
fn test_committed_instance_var() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
|
||||
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],
|
||||
};
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let ciVar =
|
||||
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);
|
||||
// the values cmE and cmW are checked in the CycleFold's circuit
|
||||
// CommittedInstanceInCycleFoldVar in
|
||||
// nova::cyclefold::tests::test_committed_instance_cyclefold_var
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nifs_gadget() {
|
||||
let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, _, r_Fr) = prepare_simple_fold_inputs();
|
||||
|
||||
let ci3_verifier = NIFS::<Projective, Pedersen<Projective>>::verify(r_Fr, &ci1, &ci2, &cmT);
|
||||
assert_eq!(ci3_verifier, ci3);
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
let rVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
|
||||
let ci1Var =
|
||||
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci1.clone()))
|
||||
.unwrap();
|
||||
let ci2Var =
|
||||
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci2.clone()))
|
||||
.unwrap();
|
||||
let ci3Var =
|
||||
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci3.clone()))
|
||||
.unwrap();
|
||||
|
||||
let nifs_check = NIFSGadget::<Projective>::verify(
|
||||
rVar.clone(),
|
||||
ci1Var.clone(),
|
||||
ci2Var.clone(),
|
||||
ci3Var.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
nifs_check.enforce_equal(&Boolean::<Fr>::TRUE).unwrap();
|
||||
assert!(cs.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 = vec![Fr::from(3_u32)];
|
||||
let z_i = vec![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.clone(), z_i.clone())
|
||||
.unwrap();
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
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();
|
||||
let z_iVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();
|
||||
let ciVar =
|
||||
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
|
||||
|
||||
let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// checks that the gadget and native implementations of the challenge computation matcbh
|
||||
#[test]
|
||||
fn test_challenge_gadget() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
|
||||
let u_i = CommittedInstance::<Projective> {
|
||||
cmE: Projective::rand(&mut rng),
|
||||
u: Fr::rand(&mut rng),
|
||||
cmW: Projective::rand(&mut rng),
|
||||
x: vec![Fr::rand(&mut rng); 1],
|
||||
};
|
||||
let U_i = CommittedInstance::<Projective> {
|
||||
cmE: Projective::rand(&mut rng),
|
||||
u: Fr::rand(&mut rng),
|
||||
cmW: Projective::rand(&mut rng),
|
||||
x: vec![Fr::rand(&mut rng); 1],
|
||||
};
|
||||
let cmT = Projective::rand(&mut rng);
|
||||
|
||||
// compute the challenge natively
|
||||
let r_bits = ChallengeGadget::<Projective>::get_challenge_native(
|
||||
&poseidon_config,
|
||||
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();
|
||||
let u_iVar =
|
||||
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(u_i.clone()))
|
||||
.unwrap();
|
||||
let U_iVar =
|
||||
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(U_i.clone()))
|
||||
.unwrap();
|
||||
let cmTVar = NonNativeAffineVar::<Fr>::new_witness(cs.clone(), || Ok(cmT)).unwrap();
|
||||
|
||||
// compute the challenge in-circuit
|
||||
let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget(
|
||||
cs.clone(),
|
||||
&poseidon_config,
|
||||
u_iVar,
|
||||
U_iVar,
|
||||
cmTVar,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
|
||||
// check that the natively computed and in-circuit computed hashes match
|
||||
let rVar = Boolean::le_bits_to_fp_var(&r_bitsVar).unwrap();
|
||||
assert_eq!(rVar.value().unwrap(), r);
|
||||
assert_eq!(r_bitsVar.value().unwrap(), r_bits);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// test_augmented_f_circuit folds the CubicFCircuit circuit in multiple iterations, feeding the
|
||||
/// values into the AugmentedFCircuit.
|
||||
fn test_augmented_f_circuit() {
|
||||
let mut layer = ConstraintLayer::default();
|
||||
layer.mode = TracingMode::OnlyConstraints;
|
||||
let subscriber = tracing_subscriber::Registry::default().with(layer);
|
||||
let _guard = tracing::subscriber::set_default(subscriber);
|
||||
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
|
||||
// compute z vector for the initial instance
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
// prepare the circuit to obtain its R1CS
|
||||
let F_circuit = CubicFCircuit::<Fr>::new(());
|
||||
let mut augmented_F_circuit =
|
||||
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>>::empty(
|
||||
&poseidon_config,
|
||||
F_circuit,
|
||||
);
|
||||
augmented_F_circuit
|
||||
.generate_constraints(cs.clone())
|
||||
.unwrap();
|
||||
cs.finalize();
|
||||
println!("num_constraints={:?}", cs.num_constraints());
|
||||
let cs = cs.into_inner().unwrap();
|
||||
let r1cs = extract_r1cs::<Fr>(&cs);
|
||||
let (w, x) = extract_w_x::<Fr>(&cs);
|
||||
assert_eq!(1 + x.len() + w.len(), r1cs.A.n_cols);
|
||||
assert_eq!(r1cs.l, x.len());
|
||||
|
||||
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_rows);
|
||||
|
||||
// first step, set z_i=z_0=3 and z_{i+1}=35 (initial values)
|
||||
let z_0 = vec![Fr::from(3_u32)];
|
||||
let mut z_i = z_0.clone();
|
||||
let mut z_i1 = vec![Fr::from(35_u32)];
|
||||
|
||||
let w_dummy = Witness::<Projective>::new(vec![Fr::zero(); w.len()], r1cs.A.n_rows);
|
||||
let u_dummy = CommittedInstance::<Projective>::dummy(x.len());
|
||||
|
||||
// W_i is a 'dummy witness', all zeroes, but with the size corresponding to the R1CS that
|
||||
// we're working with.
|
||||
// set U_i <-- dummy instance
|
||||
let mut W_i = w_dummy.clone();
|
||||
let mut U_i = u_dummy.clone();
|
||||
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
|
||||
|
||||
let mut w_i = w_dummy.clone();
|
||||
let mut u_i = u_dummy.clone();
|
||||
let (mut W_i1, mut U_i1, mut cmT): (
|
||||
Witness<Projective>,
|
||||
CommittedInstance<Projective>,
|
||||
Projective,
|
||||
) = (w_dummy.clone(), u_dummy.clone(), Projective::generator());
|
||||
// as expected, dummy instances pass the relaxed_r1cs check
|
||||
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
|
||||
|
||||
let mut i = Fr::zero();
|
||||
let mut u_i1_x: Fr;
|
||||
for _ in 0..4 {
|
||||
if i == Fr::zero() {
|
||||
// base case: i=0, z_i=z_0, U_i = U_d := dummy instance
|
||||
// u_1.x = H(1, z_0, z_i, U_i)
|
||||
u_i1_x = U_i
|
||||
.hash(&poseidon_config, Fr::one(), z_0.clone(), z_i1.clone())
|
||||
.unwrap();
|
||||
|
||||
// base case
|
||||
augmented_F_circuit =
|
||||
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
|
||||
_gc2: PhantomData,
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
i: Some(i), // = 0
|
||||
z_0: Some(z_0.clone()), // = z_i=3
|
||||
z_i: Some(z_i.clone()),
|
||||
u_i: Some(u_i.clone()), // = dummy
|
||||
U_i: Some(U_i.clone()), // = dummy
|
||||
U_i1: Some(U_i1.clone()), // = dummy
|
||||
cmT: Some(cmT),
|
||||
F: F_circuit,
|
||||
x: Some(u_i1_x),
|
||||
// cyclefold instances (not tested in this test)
|
||||
cf_u_i: None,
|
||||
cf_U_i: None,
|
||||
cf_U_i1: None,
|
||||
cf_cmT: None,
|
||||
cf_r_nonnat: None,
|
||||
};
|
||||
} else {
|
||||
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
|
||||
|
||||
// U_{i+1}
|
||||
let T: Vec<Fr>;
|
||||
(T, cmT) = NIFS::<Projective, Pedersen<Projective>>::compute_cmT(
|
||||
&pedersen_params,
|
||||
&r1cs,
|
||||
&w_i,
|
||||
&u_i,
|
||||
&W_i,
|
||||
&U_i,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// get challenge r
|
||||
let r_bits = ChallengeGadget::<Projective>::get_challenge_native(
|
||||
&poseidon_config,
|
||||
u_i.clone(),
|
||||
U_i.clone(),
|
||||
cmT,
|
||||
)
|
||||
.unwrap();
|
||||
let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
|
||||
|
||||
(W_i1, U_i1) = NIFS::<Projective, Pedersen<Projective>>::fold_instances(
|
||||
r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
|
||||
|
||||
// folded instance output (public input, x)
|
||||
// u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1})
|
||||
u_i1_x = U_i1
|
||||
.hash(&poseidon_config, i + Fr::one(), z_0.clone(), z_i1.clone())
|
||||
.unwrap();
|
||||
|
||||
// set up dummy cyclefold instances just for the sake of this test. Warning, this
|
||||
// is only because we are in a test were we're not testing the cyclefold side of
|
||||
// things.
|
||||
let cf_W_i = Witness::<Projective2>::new(vec![Fq::zero(); 1], 1);
|
||||
let cf_U_i = CommittedInstance::<Projective2>::dummy(CF_IO_LEN);
|
||||
let cf_u_i_x = [
|
||||
get_committed_instance_coordinates(&u_i),
|
||||
get_committed_instance_coordinates(&U_i),
|
||||
get_committed_instance_coordinates(&U_i1),
|
||||
]
|
||||
.concat();
|
||||
let cf_u_i = CommittedInstance::<Projective2> {
|
||||
cmE: cf_U_i.cmE,
|
||||
u: Fq::one(),
|
||||
cmW: cf_U_i.cmW,
|
||||
x: cf_u_i_x,
|
||||
};
|
||||
let cf_w_i = cf_W_i.clone();
|
||||
let (cf_T, cf_cmT): (Vec<Fq>, Projective2) =
|
||||
(vec![Fq::zero(); cf_W_i.E.len()], Projective2::zero());
|
||||
let cf_r_bits =
|
||||
CycleFoldChallengeGadget::<Projective2, GVar2>::get_challenge_native(
|
||||
&poseidon_config,
|
||||
cf_u_i.clone(),
|
||||
cf_U_i.clone(),
|
||||
cf_cmT,
|
||||
)
|
||||
.unwrap();
|
||||
let cf_r_Fq = Fq::from_bigint(BigInteger::from_bits_le(&cf_r_bits)).unwrap();
|
||||
let (_, cf_U_i1) = NIFS::<Projective2, Pedersen<Projective2>>::fold_instances(
|
||||
cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
augmented_F_circuit =
|
||||
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
|
||||
_gc2: PhantomData,
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
i: Some(i),
|
||||
z_0: Some(z_0.clone()),
|
||||
z_i: Some(z_i.clone()),
|
||||
u_i: Some(u_i),
|
||||
U_i: Some(U_i.clone()),
|
||||
U_i1: Some(U_i1.clone()),
|
||||
cmT: Some(cmT),
|
||||
F: F_circuit,
|
||||
x: Some(u_i1_x),
|
||||
cf_u_i: Some(cf_u_i),
|
||||
cf_U_i: Some(cf_U_i),
|
||||
cf_U_i1: Some(cf_U_i1),
|
||||
cf_cmT: Some(cf_cmT),
|
||||
cf_r_nonnat: Some(cf_r_Fq),
|
||||
};
|
||||
}
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
augmented_F_circuit
|
||||
.generate_constraints(cs.clone())
|
||||
.unwrap();
|
||||
let is_satisfied = cs.is_satisfied().unwrap();
|
||||
if !is_satisfied {
|
||||
dbg!(cs.which_is_unsatisfied().unwrap());
|
||||
}
|
||||
assert!(is_satisfied);
|
||||
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().unwrap();
|
||||
let (w_i1, x_i1) = extract_w_x::<Fr>(&cs);
|
||||
assert_eq!(x_i1.len(), 1);
|
||||
assert_eq!(x_i1[0], u_i1_x);
|
||||
|
||||
// compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we
|
||||
// assign them directly to w_i, u_i.
|
||||
w_i = Witness::<Projective>::new(w_i1.clone(), r1cs.A.n_rows);
|
||||
u_i = w_i
|
||||
.commit::<Pedersen<Projective>>(&pedersen_params, vec![u_i1_x])
|
||||
.unwrap();
|
||||
|
||||
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
|
||||
|
||||
// set values for next iteration
|
||||
i += Fr::one();
|
||||
// advance the F circuit state
|
||||
z_i = z_i1.clone();
|
||||
z_i1 = F_circuit.step_native(z_i.clone()).unwrap();
|
||||
U_i = U_i1.clone();
|
||||
W_i = W_i1.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
567
folding-schemes/src/folding/nova/cyclefold.rs
Normal file
567
folding-schemes/src/folding/nova/cyclefold.rs
Normal file
@@ -0,0 +1,567 @@
|
||||
/// contains [CycleFold](https://eprint.iacr.org/2023/1192.pdf) related circuits
|
||||
use ark_crypto_primitives::sponge::{
|
||||
constraints::CryptographicSpongeVar,
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
|
||||
Absorb, CryptographicSponge,
|
||||
};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
bits::uint8::UInt8,
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
|
||||
groups::GroupOpsBounds,
|
||||
prelude::CurveVar,
|
||||
ToBytesGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
|
||||
use ark_serialize::CanonicalSerialize;
|
||||
use ark_std::fmt::Debug;
|
||||
use ark_std::Zero;
|
||||
use core::{borrow::Borrow, marker::PhantomData};
|
||||
|
||||
use super::circuits::CF2;
|
||||
use super::CommittedInstance;
|
||||
use crate::constants::N_BITS_RO;
|
||||
use crate::Error;
|
||||
|
||||
// publi inputs length for the CycleFoldCircuit, |[u_i, U_i, U_{i+1}]|
|
||||
pub const CF_IO_LEN: usize = 12;
|
||||
|
||||
/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova
|
||||
/// circuit.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CycleFoldCommittedInstanceVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
|
||||
where
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
_c: PhantomData<C>,
|
||||
pub cmE: GC,
|
||||
pub u: NonNativeFieldVar<C::ScalarField, CF2<C>>,
|
||||
pub cmW: GC,
|
||||
pub x: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>>,
|
||||
}
|
||||
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CycleFoldCommittedInstanceVar<C, GC>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>>,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
fn new_variable<T: Borrow<CommittedInstance<C>>>(
|
||||
cs: impl Into<Namespace<CF2<C>>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
|
||||
let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
|
||||
let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
|
||||
let u = NonNativeFieldVar::<C::ScalarField, CF2<C>>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(val.borrow().u),
|
||||
mode,
|
||||
)?;
|
||||
let x = Vec::<NonNativeFieldVar<C::ScalarField, CF2<C>>>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(val.borrow().x.clone()),
|
||||
mode,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
_c: PhantomData,
|
||||
cmE,
|
||||
u,
|
||||
cmW,
|
||||
x,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// CommittedInstanceInCycleFoldVar represents the Nova CommittedInstance in the CycleFold circuit,
|
||||
/// where the commitments to E and W (cmW and cmW) from the CommittedInstance on the E2,
|
||||
/// represented as native points, which are folded on the auxiliary curve constraints field (E2::Fr
|
||||
/// = E1::Fq).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommittedInstanceInCycleFoldVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
|
||||
where
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
_c: PhantomData<C>,
|
||||
pub cmE: GC,
|
||||
pub cmW: GC,
|
||||
}
|
||||
|
||||
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CommittedInstanceInCycleFoldVar<C, GC>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>>,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
fn new_variable<T: Borrow<CommittedInstance<C>>>(
|
||||
cs: impl Into<Namespace<CF2<C>>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
|
||||
let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
|
||||
let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
|
||||
|
||||
Ok(Self {
|
||||
_c: PhantomData,
|
||||
cmE,
|
||||
cmW,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// NIFSinCycleFoldGadget performs the Nova NIFS.V elliptic curve points relation checks in the other
|
||||
/// curve (natively) following [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
|
||||
pub struct NIFSinCycleFoldGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
|
||||
_c: PhantomData<C>,
|
||||
_gc: PhantomData<GC>,
|
||||
}
|
||||
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSinCycleFoldGadget<C, GC>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>>,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
pub fn verify(
|
||||
r_bits: Vec<Boolean<CF2<C>>>,
|
||||
cmT: GC,
|
||||
ci1: CommittedInstanceInCycleFoldVar<C, GC>,
|
||||
ci2: CommittedInstanceInCycleFoldVar<C, GC>,
|
||||
ci3: CommittedInstanceInCycleFoldVar<C, GC>,
|
||||
) -> Result<Boolean<CF2<C>>, SynthesisError> {
|
||||
// cm(E) check: ci3.cmE == ci1.cmE + r * cmT + r^2 * ci2.cmE
|
||||
let first_check = ci3.cmE.is_eq(
|
||||
&((ci2.cmE.scalar_mul_le(r_bits.iter())? + cmT).scalar_mul_le(r_bits.iter())?
|
||||
+ ci1.cmE),
|
||||
)?;
|
||||
// cm(W) check: ci3.cmW == ci1.cmW + r * ci2.cmW
|
||||
let second_check = ci3
|
||||
.cmW
|
||||
.is_eq(&(ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?))?;
|
||||
|
||||
first_check.and(&second_check)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the gadget used in the AugmentedFCircuit to verify the CycleFold instances folding,
|
||||
/// which checks the correct RLC of u,x,cmE,cmW (hence the name containing 'Full', since it checks
|
||||
/// all the RLC values, not only the native ones). It assumes that ci2.cmE=0, ci2.u=1.
|
||||
pub struct NIFSFullGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
|
||||
_c: PhantomData<C>,
|
||||
_gc: PhantomData<GC>,
|
||||
}
|
||||
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSFullGadget<C, GC>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>>,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
pub fn verify(
|
||||
r_bits: Vec<Boolean<CF2<C>>>,
|
||||
r_nonnat: NonNativeFieldVar<C::ScalarField, CF2<C>>,
|
||||
cmT: GC,
|
||||
// ci1 is assumed to be always with cmE=0, u=1 (checks done previous to this method)
|
||||
ci1: CycleFoldCommittedInstanceVar<C, GC>,
|
||||
ci2: CycleFoldCommittedInstanceVar<C, GC>,
|
||||
ci3: CycleFoldCommittedInstanceVar<C, GC>,
|
||||
) -> Result<Boolean<CF2<C>>, SynthesisError> {
|
||||
// cm(E) check: ci3.cmE == ci1.cmE + r * cmT (ci2.cmE=0)
|
||||
let first_check = ci3
|
||||
.cmE
|
||||
.is_eq(&(cmT.scalar_mul_le(r_bits.iter())? + ci1.cmE))?;
|
||||
|
||||
// cm(W) check: ci3.cmW == ci1.cmW + r * ci2.cmW
|
||||
let second_check = ci3
|
||||
.cmW
|
||||
.is_eq(&(ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?))?;
|
||||
|
||||
let u_rlc: NonNativeFieldVar<C::ScalarField, CF2<C>> = ci1.u + r_nonnat.clone();
|
||||
let third_check = u_rlc.is_eq(&ci3.u)?;
|
||||
|
||||
// ensure that: ci3.x == ci1.x + r * ci2.x
|
||||
let x_rlc: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>> = ci1
|
||||
.x
|
||||
.iter()
|
||||
.zip(ci2.x)
|
||||
.map(|(a, b)| a + &r_nonnat * &b)
|
||||
.collect::<Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>>>();
|
||||
let fourth_check = x_rlc.is_eq(&ci3.x)?;
|
||||
|
||||
first_check
|
||||
.and(&second_check)?
|
||||
.and(&third_check)?
|
||||
.and(&fourth_check)
|
||||
}
|
||||
}
|
||||
|
||||
/// ChallengeGadget computes the RO challenge used for the CycleFold instances NIFS, it contains a
|
||||
/// rust-native and a in-circuit compatible versions.
|
||||
pub struct CycleFoldChallengeGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
|
||||
_c: PhantomData<C>, // Nova's Curve2, the one used for the CycleFold circuit
|
||||
_gc: PhantomData<GC>,
|
||||
}
|
||||
impl<C, GC> CycleFoldChallengeGadget<C, GC>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>>,
|
||||
<C as CurveGroup>::BaseField: PrimeField,
|
||||
<C as CurveGroup>::BaseField: Absorb,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
pub fn get_challenge_native(
|
||||
poseidon_config: &PoseidonConfig<C::BaseField>,
|
||||
u_i: CommittedInstance<C>,
|
||||
U_i: CommittedInstance<C>,
|
||||
cmT: C,
|
||||
) -> Result<Vec<bool>, Error> {
|
||||
let mut sponge = PoseidonSponge::<C::BaseField>::new(poseidon_config);
|
||||
|
||||
let u_i_cmE_bytes = point_to_bytes(u_i.cmE);
|
||||
let u_i_cmW_bytes = point_to_bytes(u_i.cmW);
|
||||
let U_i_cmE_bytes = point_to_bytes(U_i.cmE);
|
||||
let U_i_cmW_bytes = point_to_bytes(U_i.cmW);
|
||||
let cmT_bytes = point_to_bytes(cmT);
|
||||
|
||||
let mut u_i_u_bytes = Vec::new();
|
||||
u_i.u.serialize_uncompressed(&mut u_i_u_bytes)?;
|
||||
let mut u_i_x_bytes = Vec::new();
|
||||
u_i.x.serialize_uncompressed(&mut u_i_x_bytes)?;
|
||||
u_i_x_bytes = u_i_x_bytes[8..].to_vec();
|
||||
let mut U_i_u_bytes = Vec::new();
|
||||
U_i.u.serialize_uncompressed(&mut U_i_u_bytes)?;
|
||||
let mut U_i_x_bytes = Vec::new();
|
||||
U_i.x.serialize_uncompressed(&mut U_i_x_bytes)?;
|
||||
U_i_x_bytes = U_i_x_bytes[8..].to_vec();
|
||||
|
||||
let input: Vec<u8> = [
|
||||
u_i_cmE_bytes,
|
||||
u_i_u_bytes,
|
||||
u_i_cmW_bytes,
|
||||
u_i_x_bytes,
|
||||
U_i_cmE_bytes,
|
||||
U_i_u_bytes,
|
||||
U_i_cmW_bytes,
|
||||
U_i_x_bytes,
|
||||
cmT_bytes,
|
||||
]
|
||||
.concat();
|
||||
sponge.absorb(&input);
|
||||
let bits = sponge.squeeze_bits(N_BITS_RO);
|
||||
Ok(bits)
|
||||
}
|
||||
// compatible with the native get_challenge_native
|
||||
pub fn get_challenge_gadget(
|
||||
cs: ConstraintSystemRef<C::BaseField>,
|
||||
poseidon_config: &PoseidonConfig<C::BaseField>,
|
||||
u_i: CycleFoldCommittedInstanceVar<C, GC>,
|
||||
U_i: CycleFoldCommittedInstanceVar<C, GC>,
|
||||
cmT: GC,
|
||||
) -> Result<Vec<Boolean<C::BaseField>>, SynthesisError> {
|
||||
let mut sponge = PoseidonSpongeVar::<C::BaseField>::new(cs, poseidon_config);
|
||||
|
||||
let u_i_x_bytes: Vec<UInt8<CF2<C>>> = u_i
|
||||
.x
|
||||
.iter()
|
||||
.flat_map(|e| e.to_bytes().unwrap_or(vec![]))
|
||||
.collect::<Vec<UInt8<CF2<C>>>>();
|
||||
let U_i_x_bytes: Vec<UInt8<CF2<C>>> = U_i
|
||||
.x
|
||||
.iter()
|
||||
.flat_map(|e| e.to_bytes().unwrap_or(vec![]))
|
||||
.collect::<Vec<UInt8<CF2<C>>>>();
|
||||
|
||||
let input: Vec<UInt8<CF2<C>>> = [
|
||||
u_i.cmE.to_bytes()?,
|
||||
u_i.u.to_bytes()?,
|
||||
u_i.cmW.to_bytes()?,
|
||||
u_i_x_bytes,
|
||||
U_i.cmE.to_bytes()?,
|
||||
U_i.u.to_bytes()?,
|
||||
U_i.cmW.to_bytes()?,
|
||||
U_i_x_bytes,
|
||||
cmT.to_bytes()?,
|
||||
// TODO instead of bytes, use field elements, but needs x,y coordinates from
|
||||
// u_i.{cmE,cmW}, U_i.{cmE,cmW}, cmT. Depends exposing x,y coordinates of GC. Issue to
|
||||
// keep track of this:
|
||||
// https://github.com/privacy-scaling-explorations/folding-schemes/issues/44
|
||||
]
|
||||
.concat();
|
||||
sponge.absorb(&input)?;
|
||||
let bits = sponge.squeeze_bits(N_BITS_RO)?;
|
||||
Ok(bits)
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the bytes being compatible with the ark_r1cs_std `.to_bytes` approach
|
||||
fn point_to_bytes<C: CurveGroup>(p: C) -> Vec<u8> {
|
||||
let l = p.uncompressed_size();
|
||||
let mut b = Vec::new();
|
||||
p.serialize_uncompressed(&mut b).unwrap();
|
||||
b[l - 1] = 0;
|
||||
if p.is_zero() {
|
||||
b[l / 2] = 1;
|
||||
b[l - 1] = 1;
|
||||
}
|
||||
b
|
||||
}
|
||||
|
||||
/// CycleFoldCircuit contains the constraints that check the correct fold of the committed
|
||||
/// instances from Curve1. Namely, it checks the random linear combinations of the elliptic curve
|
||||
/// (Curve1) points of u_i, U_i leading to U_{i+1}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CycleFoldCircuit<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
|
||||
pub _gc: PhantomData<GC>,
|
||||
pub r_bits: Option<Vec<bool>>,
|
||||
pub cmT: Option<C>,
|
||||
// u_i,U_i,U_i1 are the nova instances from AugmentedFCircuit which will be (their elliptic
|
||||
// curve points) checked natively in CycleFoldCircuit
|
||||
pub u_i: Option<CommittedInstance<C>>,
|
||||
pub U_i: Option<CommittedInstance<C>>,
|
||||
pub U_i1: Option<CommittedInstance<C>>,
|
||||
pub x: Option<Vec<CF2<C>>>, // public inputs (cf_u_{i+1}.x)
|
||||
}
|
||||
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> CycleFoldCircuit<C, GC> {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
_gc: PhantomData,
|
||||
r_bits: None,
|
||||
cmT: None,
|
||||
u_i: None,
|
||||
U_i: None,
|
||||
U_i1: None,
|
||||
x: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<C, GC> ConstraintSynthesizer<CF2<C>> for CycleFoldCircuit<C, GC>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>>,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF2<C>>) -> Result<(), SynthesisError> {
|
||||
let r_bits: Vec<Boolean<CF2<C>>> = Vec::new_witness(cs.clone(), || {
|
||||
Ok(self.r_bits.unwrap_or(vec![false; N_BITS_RO]))
|
||||
})?;
|
||||
let cmT = GC::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or(C::zero())))?;
|
||||
|
||||
let u_dummy_native = CommittedInstance::<C>::dummy(1);
|
||||
|
||||
let u_i = CommittedInstanceInCycleFoldVar::<C, GC>::new_witness(cs.clone(), || {
|
||||
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let U_i = CommittedInstanceInCycleFoldVar::<C, GC>::new_witness(cs.clone(), || {
|
||||
Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let U_i1 = CommittedInstanceInCycleFoldVar::<C, GC>::new_witness(cs.clone(), || {
|
||||
Ok(self.U_i1.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let _x = Vec::<FpVar<CF2<C>>>::new_input(cs.clone(), || {
|
||||
Ok(self.x.unwrap_or(vec![CF2::<C>::zero(); CF_IO_LEN]))
|
||||
})?;
|
||||
#[cfg(test)]
|
||||
assert_eq!(_x.len(), CF_IO_LEN); // non-constrained sanity check
|
||||
|
||||
// fold the original Nova instances natively in CycleFold
|
||||
let v =
|
||||
NIFSinCycleFoldGadget::<C, GC>::verify(r_bits.clone(), cmT, u_i.clone(), U_i, U_i1)?;
|
||||
v.enforce_equal(&Boolean::TRUE)?;
|
||||
|
||||
// check that x == [u_i, U_i, U_{i+1}], check that the cmW & cmW from u_i, U_i, U_{i+1} in
|
||||
// the CycleFoldCircuit are the sames used in the public inputs 'x', which come from the
|
||||
// AugmentedFCircuit.
|
||||
// TODO: Issue to keep track of this: https://github.com/privacy-scaling-explorations/folding-schemes/issues/44
|
||||
// and https://github.com/privacy-scaling-explorations/folding-schemes/issues/48
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ark_ff::BigInteger;
|
||||
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
|
||||
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_std::UniformRand;
|
||||
|
||||
use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
|
||||
use crate::transcript::poseidon::poseidon_test_config;
|
||||
|
||||
#[test]
|
||||
fn test_committed_instance_cyclefold_var() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
|
||||
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],
|
||||
};
|
||||
|
||||
// check the instantiation of the CycleFold side:
|
||||
let cs = ConstraintSystem::<Fq>::new_ref();
|
||||
let ciVar =
|
||||
CommittedInstanceInCycleFoldVar::<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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nifs_gadget_cyclefold() {
|
||||
let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs();
|
||||
|
||||
// cs is the Constraint System on the Curve Cycle auxiliary curve constraints field
|
||||
// (E2::Fr)
|
||||
let cs = ConstraintSystem::<Fq>::new_ref();
|
||||
|
||||
let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
|
||||
|
||||
let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
|
||||
let ci1Var =
|
||||
CommittedInstanceInCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
|
||||
Ok(ci1.clone())
|
||||
})
|
||||
.unwrap();
|
||||
let ci2Var =
|
||||
CommittedInstanceInCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
|
||||
Ok(ci2.clone())
|
||||
})
|
||||
.unwrap();
|
||||
let ci3Var =
|
||||
CommittedInstanceInCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
|
||||
Ok(ci3.clone())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let nifs_cf_check = NIFSinCycleFoldGadget::<Projective, GVar>::verify(
|
||||
r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var,
|
||||
)
|
||||
.unwrap();
|
||||
nifs_cf_check.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
dbg!(cs.num_constraints());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nifs_full_gadget() {
|
||||
let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, r_Fr) = prepare_simple_fold_inputs();
|
||||
|
||||
let cs = ConstraintSystem::<Fq>::new_ref();
|
||||
|
||||
let r_nonnatVar =
|
||||
NonNativeFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
|
||||
let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
|
||||
|
||||
let ci1Var =
|
||||
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
|
||||
Ok(ci1.clone())
|
||||
})
|
||||
.unwrap();
|
||||
let ci2Var =
|
||||
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
|
||||
Ok(ci2.clone())
|
||||
})
|
||||
.unwrap();
|
||||
let ci3Var =
|
||||
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
|
||||
Ok(ci3.clone())
|
||||
})
|
||||
.unwrap();
|
||||
let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
|
||||
|
||||
let nifs_check = NIFSFullGadget::<Projective, GVar>::verify(
|
||||
r_bitsVar,
|
||||
r_nonnatVar,
|
||||
cmTVar,
|
||||
ci1Var,
|
||||
ci2Var,
|
||||
ci3Var,
|
||||
)
|
||||
.unwrap();
|
||||
nifs_check.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
dbg!(cs.num_constraints());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cyclefold_challenge_gadget() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_test_config::<Fq>();
|
||||
|
||||
let u_i = CommittedInstance::<Projective> {
|
||||
cmE: Projective::zero(), // zero on purpose, so we test also the zero point case
|
||||
u: Fr::zero(),
|
||||
cmW: Projective::rand(&mut rng),
|
||||
x: std::iter::repeat_with(|| Fr::rand(&mut rng))
|
||||
.take(CF_IO_LEN)
|
||||
.collect(),
|
||||
};
|
||||
let U_i = CommittedInstance::<Projective> {
|
||||
cmE: Projective::rand(&mut rng),
|
||||
u: Fr::rand(&mut rng),
|
||||
cmW: Projective::rand(&mut rng),
|
||||
x: std::iter::repeat_with(|| Fr::rand(&mut rng))
|
||||
.take(CF_IO_LEN)
|
||||
.collect(),
|
||||
};
|
||||
let cmT = Projective::rand(&mut rng);
|
||||
|
||||
// compute the challenge natively
|
||||
let r_bits = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_native(
|
||||
&poseidon_config,
|
||||
u_i.clone(),
|
||||
U_i.clone(),
|
||||
cmT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let cs = ConstraintSystem::<Fq>::new_ref();
|
||||
let u_iVar =
|
||||
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
|
||||
Ok(u_i.clone())
|
||||
})
|
||||
.unwrap();
|
||||
let U_iVar =
|
||||
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
|
||||
Ok(U_i.clone())
|
||||
})
|
||||
.unwrap();
|
||||
let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
|
||||
|
||||
let r_bitsVar = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_gadget(
|
||||
cs.clone(),
|
||||
&poseidon_config,
|
||||
u_iVar,
|
||||
U_iVar,
|
||||
cmTVar,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
|
||||
// check that the natively computed and in-circuit computed hashes match
|
||||
let rVar = Boolean::le_bits_to_fp_var(&r_bitsVar).unwrap();
|
||||
let r = Fq::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
|
||||
assert_eq!(rVar.value().unwrap(), r);
|
||||
assert_eq!(r_bitsVar.value().unwrap(), r_bits);
|
||||
}
|
||||
}
|
||||
205
folding-schemes/src/folding/nova/decider_eth.rs
Normal file
205
folding-schemes/src/folding/nova/decider_eth.rs
Normal file
@@ -0,0 +1,205 @@
|
||||
/// This file implements the onchain (Ethereum's EVM) decider.
|
||||
use ark_crypto_primitives::sponge::Absorb;
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar};
|
||||
use ark_snark::SNARK;
|
||||
use ark_std::rand::CryptoRng;
|
||||
use ark_std::rand::RngCore;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use super::decider_eth_circuit::DeciderEthCircuit;
|
||||
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentProver};
|
||||
use crate::folding::circuits::nonnative::point_to_nonnative_limbs_custom_opt;
|
||||
use crate::folding::nova::{circuits::CF2, CommittedInstance, Nova};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::Error;
|
||||
use crate::{Decider as DeciderTrait, FoldingScheme};
|
||||
use ark_r1cs_std::fields::nonnative::params::OptimizationType;
|
||||
|
||||
/// Onchain Decider, for ethereum use cases
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Decider<C1, GC1, C2, GC2, FC, CP1, CP2, S, FS> {
|
||||
_c1: PhantomData<C1>,
|
||||
_gc1: PhantomData<GC1>,
|
||||
_c2: PhantomData<C2>,
|
||||
_gc2: PhantomData<GC2>,
|
||||
_fc: PhantomData<FC>,
|
||||
_cp1: PhantomData<CP1>,
|
||||
_cp2: PhantomData<CP2>,
|
||||
_s: PhantomData<S>,
|
||||
_fs: PhantomData<FS>,
|
||||
}
|
||||
|
||||
impl<C1, GC1, C2, GC2, FC, CP1, CP2, S, FS> DeciderTrait<C1, C2, FC, FS>
|
||||
for Decider<C1, GC1, C2, GC2, FC, CP1, CP2, S, FS>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
CP1: CommitmentProver<C1>,
|
||||
// enforce that the CP2 is Pedersen commitment, since we're at Ethereum's EVM decider
|
||||
CP2: CommitmentProver<C2, Params = PedersenParams<C2>>,
|
||||
S: SNARK<C1::ScalarField>,
|
||||
FS: FoldingScheme<C1, C2, FC>,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
|
||||
// constrain FS into Nova, since this is a Decider specificly for Nova
|
||||
Nova<C1, GC1, C2, GC2, FC, CP1, CP2>: From<FS>,
|
||||
{
|
||||
type ProverParam = S::ProvingKey;
|
||||
type Proof = S::Proof;
|
||||
type VerifierParam = S::VerifyingKey;
|
||||
type PublicInput = Vec<C1::ScalarField>;
|
||||
type CommittedInstanceWithWitness = ();
|
||||
type CommittedInstance = CommittedInstance<C1>;
|
||||
|
||||
fn prove(
|
||||
pp: &Self::ProverParam,
|
||||
mut rng: impl RngCore + CryptoRng,
|
||||
folding_scheme: FS,
|
||||
) -> Result<Self::Proof, Error> {
|
||||
let circuit =
|
||||
DeciderEthCircuit::<C1, GC1, C2, GC2, CP1, CP2>::from_nova::<FC>(folding_scheme.into());
|
||||
|
||||
let proof = S::prove(pp, circuit.clone(), &mut rng).unwrap();
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
fn verify(
|
||||
vp: &Self::VerifierParam,
|
||||
i: C1::ScalarField,
|
||||
z_0: Vec<C1::ScalarField>,
|
||||
z_i: Vec<C1::ScalarField>,
|
||||
running_instance: &Self::CommittedInstance,
|
||||
proof: Self::Proof,
|
||||
) -> Result<bool, Error> {
|
||||
let (cmE_x, cmE_y) = point_to_nonnative_limbs_custom_opt::<C1>(
|
||||
running_instance.cmE,
|
||||
OptimizationType::Constraints,
|
||||
)?;
|
||||
let (cmW_x, cmW_y) = point_to_nonnative_limbs_custom_opt::<C1>(
|
||||
running_instance.cmW,
|
||||
OptimizationType::Constraints,
|
||||
)?;
|
||||
let public_input: Vec<C1::ScalarField> = vec![
|
||||
vec![i],
|
||||
z_0,
|
||||
z_i,
|
||||
vec![running_instance.u],
|
||||
running_instance.x.clone(),
|
||||
cmE_x,
|
||||
cmE_y,
|
||||
cmW_x,
|
||||
cmW_y,
|
||||
]
|
||||
.concat();
|
||||
S::verify(vp, &public_input, &proof).map_err(|e| Error::Other(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ark_groth16::Groth16;
|
||||
use ark_mnt4_298::{constraints::G1Var as GVar, Fr, G1Projective as Projective, MNT4_298};
|
||||
use ark_mnt6_298::{constraints::G1Var as GVar2, G1Projective as Projective2};
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::{get_pedersen_params_len, ProverParams};
|
||||
use crate::frontend::tests::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_test_config;
|
||||
|
||||
// Note: since we're testing a big circuit, this test takes a bit more of computation and time,
|
||||
// do not run in the normal CI.
|
||||
// To run the test use `--ignored` flag, eg. `cargo test -- --ignored`
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_decider() {
|
||||
type NOVA = Nova<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
CubicFCircuit<Fr>,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
>;
|
||||
type DECIDER = Decider<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
CubicFCircuit<Fr>,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
Groth16<MNT4_298>, // here we define the Snark to use in the decider
|
||||
NOVA, // here we define the FoldingScheme to use
|
||||
>;
|
||||
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
|
||||
let F_circuit = CubicFCircuit::<Fr>::new(());
|
||||
let z_0 = vec![Fr::from(3_u32)];
|
||||
|
||||
let (cm_len, cf_cm_len) =
|
||||
get_pedersen_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>(
|
||||
&poseidon_config,
|
||||
F_circuit,
|
||||
)
|
||||
.unwrap();
|
||||
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, cm_len);
|
||||
let cf_pedersen_params = Pedersen::<Projective2>::new_params(&mut rng, cf_cm_len);
|
||||
|
||||
let start = Instant::now();
|
||||
let prover_params =
|
||||
ProverParams::<Projective, Projective2, Pedersen<Projective>, Pedersen<Projective2>> {
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
cm_params: pedersen_params,
|
||||
cf_cm_params: cf_pedersen_params,
|
||||
};
|
||||
println!("generating pedersen params, {:?}", start.elapsed());
|
||||
|
||||
// use Nova as FoldingScheme
|
||||
let start = Instant::now();
|
||||
let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap();
|
||||
println!("Nova initialized, {:?}", start.elapsed());
|
||||
let start = Instant::now();
|
||||
nova.prove_step().unwrap();
|
||||
println!("prove_step, {:?}", start.elapsed());
|
||||
|
||||
// generate Groth16 setup
|
||||
let circuit = DeciderEthCircuit::<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
>::from_nova::<CubicFCircuit<Fr>>(nova.clone());
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
|
||||
let start = Instant::now();
|
||||
let (pk, vk) =
|
||||
Groth16::<MNT4_298>::circuit_specific_setup(circuit.clone(), &mut rng).unwrap();
|
||||
println!("Groth16 setup, {:?}", start.elapsed());
|
||||
|
||||
// decider proof generation
|
||||
let start = Instant::now();
|
||||
let proof = DECIDER::prove(&pk, rng, nova.clone()).unwrap();
|
||||
println!("Decider Groth16 prove, {:?}", start.elapsed());
|
||||
|
||||
// decider proof verification
|
||||
let verified = DECIDER::verify(&vk, nova.i, nova.z_0, nova.z_i, &nova.U_i, proof).unwrap();
|
||||
assert!(verified);
|
||||
}
|
||||
}
|
||||
686
folding-schemes/src/folding/nova/decider_eth_circuit.rs
Normal file
686
folding-schemes/src/folding/nova/decider_eth_circuit.rs
Normal file
@@ -0,0 +1,686 @@
|
||||
/// 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_ec::{CurveGroup, Group};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
|
||||
groups::GroupOpsBounds,
|
||||
prelude::CurveVar,
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
|
||||
use ark_std::{One, Zero};
|
||||
use core::{borrow::Borrow, marker::PhantomData};
|
||||
|
||||
use crate::ccs::r1cs::R1CS;
|
||||
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentProver};
|
||||
use crate::folding::nova::{
|
||||
circuits::{CommittedInstanceVar, CF1, CF2},
|
||||
CommittedInstance, Nova, Witness,
|
||||
};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::utils::gadgets::{
|
||||
hadamard, mat_vec_mul_sparse, vec_add, vec_scalar_mul, SparseMatrixVar,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RelaxedR1CSGadget<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> {
|
||||
_f: PhantomData<F>,
|
||||
_cf: PhantomData<CF>,
|
||||
_fv: PhantomData<FV>,
|
||||
}
|
||||
impl<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> RelaxedR1CSGadget<F, CF, FV> {
|
||||
/// performs the RelaxedR1CS check (Az∘Bz==uCz+E)
|
||||
pub fn check(
|
||||
r1cs: R1CSVar<F, CF, FV>,
|
||||
E: Vec<FV>,
|
||||
u: FV,
|
||||
z: Vec<FV>,
|
||||
) -> Result<(), SynthesisError> {
|
||||
let Az = mat_vec_mul_sparse(r1cs.A, z.clone());
|
||||
let Bz = mat_vec_mul_sparse(r1cs.B, z.clone());
|
||||
let Cz = mat_vec_mul_sparse(r1cs.C, z.clone());
|
||||
let uCz = vec_scalar_mul(&Cz, &u);
|
||||
let uCzE = vec_add(&uCz, &E)?;
|
||||
let AzBz = hadamard(&Az, &Bz)?;
|
||||
for i in 0..AzBz.len() {
|
||||
AzBz[i].enforce_equal(&uCzE[i].clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct R1CSVar<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> {
|
||||
_f: PhantomData<F>,
|
||||
_cf: PhantomData<CF>,
|
||||
_fv: PhantomData<FV>,
|
||||
pub A: SparseMatrixVar<F, CF, FV>,
|
||||
pub B: SparseMatrixVar<F, CF, FV>,
|
||||
pub C: SparseMatrixVar<F, CF, FV>,
|
||||
}
|
||||
|
||||
impl<F, CF, FV> AllocVar<R1CS<F>, CF> for R1CSVar<F, CF, FV>
|
||||
where
|
||||
F: PrimeField,
|
||||
CF: PrimeField,
|
||||
FV: FieldVar<F, CF>,
|
||||
{
|
||||
fn new_variable<T: Borrow<R1CS<F>>>(
|
||||
cs: impl Into<Namespace<CF>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
_mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
|
||||
let A = SparseMatrixVar::<F, CF, FV>::new_constant(cs.clone(), &val.borrow().A)?;
|
||||
let B = SparseMatrixVar::<F, CF, FV>::new_constant(cs.clone(), &val.borrow().B)?;
|
||||
let C = SparseMatrixVar::<F, CF, FV>::new_constant(cs.clone(), &val.borrow().C)?;
|
||||
|
||||
Ok(Self {
|
||||
_f: PhantomData,
|
||||
_cf: PhantomData,
|
||||
_fv: PhantomData,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// In-circuit representation of the Witness associated to the CommittedInstance.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WitnessVar<C: CurveGroup> {
|
||||
pub E: Vec<FpVar<C::ScalarField>>,
|
||||
pub rE: FpVar<C::ScalarField>,
|
||||
pub W: Vec<FpVar<C::ScalarField>>,
|
||||
pub rW: FpVar<C::ScalarField>,
|
||||
}
|
||||
|
||||
impl<C> AllocVar<Witness<C>, CF1<C>> for WitnessVar<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as ark_ec::CurveGroup>::BaseField: PrimeField,
|
||||
{
|
||||
fn new_variable<T: Borrow<Witness<C>>>(
|
||||
cs: impl Into<Namespace<CF1<C>>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
|
||||
let E: Vec<FpVar<C::ScalarField>> =
|
||||
Vec::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?;
|
||||
let rE =
|
||||
FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(val.borrow().rE), mode)?;
|
||||
|
||||
let W: Vec<FpVar<C::ScalarField>> =
|
||||
Vec::new_variable(cs.clone(), || Ok(val.borrow().W.clone()), mode)?;
|
||||
let rW =
|
||||
FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(val.borrow().rW), mode)?;
|
||||
|
||||
Ok(Self { E, rE, W, rW })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// In-circuit representation of the Witness associated to the CommittedInstance, but with
|
||||
/// non-native representation, since it is used to represent the CycleFold witness.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CycleFoldWitnessVar<C: CurveGroup> {
|
||||
pub E: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>>,
|
||||
pub rE: NonNativeFieldVar<C::ScalarField, CF2<C>>,
|
||||
pub W: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>>,
|
||||
pub rW: NonNativeFieldVar<C::ScalarField, CF2<C>>,
|
||||
}
|
||||
|
||||
impl<C> AllocVar<Witness<C>, CF2<C>> for CycleFoldWitnessVar<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as ark_ec::CurveGroup>::BaseField: PrimeField,
|
||||
{
|
||||
fn new_variable<T: Borrow<Witness<C>>>(
|
||||
cs: impl Into<Namespace<CF2<C>>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
|
||||
let E: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>> =
|
||||
Vec::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?;
|
||||
let rE = NonNativeFieldVar::<C::ScalarField, CF2<C>>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(val.borrow().rE),
|
||||
mode,
|
||||
)?;
|
||||
|
||||
let W: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>> =
|
||||
Vec::new_variable(cs.clone(), || Ok(val.borrow().W.clone()), mode)?;
|
||||
let rW = NonNativeFieldVar::<C::ScalarField, CF2<C>>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(val.borrow().rW),
|
||||
mode,
|
||||
)?;
|
||||
|
||||
Ok(Self { E, rE, W, rW })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Circuit that implements the in-circuit checks needed for the onchain (Ethereum's EVM)
|
||||
/// verification.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DeciderEthCircuit<C1, GC1, C2, GC2, CP1, CP2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
CP1: CommitmentProver<C1>,
|
||||
CP2: CommitmentProver<C2>,
|
||||
{
|
||||
_c1: PhantomData<C1>,
|
||||
_gc1: PhantomData<GC1>,
|
||||
_c2: PhantomData<C2>,
|
||||
_gc2: PhantomData<GC2>,
|
||||
_cp1: PhantomData<CP1>,
|
||||
_cp2: PhantomData<CP2>,
|
||||
|
||||
/// E vector's length of the Nova instance witness
|
||||
pub E_len: usize,
|
||||
/// E vector's length of the CycleFold instance witness
|
||||
pub cf_E_len: usize,
|
||||
/// R1CS of the Augmented Function circuit
|
||||
pub r1cs: R1CS<C1::ScalarField>,
|
||||
/// R1CS of the CycleFold circuit
|
||||
pub cf_r1cs: R1CS<C2::ScalarField>,
|
||||
/// CycleFold PedersenParams over C2
|
||||
pub cf_pedersen_params: PedersenParams<C2>,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
pub i: Option<CF1<C1>>,
|
||||
/// initial state
|
||||
pub z_0: Option<Vec<C1::ScalarField>>,
|
||||
/// current i-th state
|
||||
pub z_i: Option<Vec<C1::ScalarField>>,
|
||||
/// Nova instances
|
||||
pub u_i: Option<CommittedInstance<C1>>,
|
||||
pub w_i: Option<Witness<C1>>,
|
||||
pub U_i: Option<CommittedInstance<C1>>,
|
||||
pub W_i: Option<Witness<C1>>,
|
||||
/// CycleFold running instance
|
||||
pub cf_U_i: Option<CommittedInstance<C2>>,
|
||||
pub cf_W_i: Option<Witness<C2>>,
|
||||
}
|
||||
impl<C1, GC1, C2, GC2, CP1, CP2> DeciderEthCircuit<C1, GC1, C2, GC2, CP1, CP2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
CP1: CommitmentProver<C1>,
|
||||
// enforce that the CP2 is Pedersen commitment, since we're at Ethereum's EVM decider
|
||||
CP2: CommitmentProver<C2, Params = PedersenParams<C2>>,
|
||||
{
|
||||
pub fn from_nova<FC: FCircuit<C1::ScalarField>>(
|
||||
nova: Nova<C1, GC1, C2, GC2, FC, CP1, CP2>,
|
||||
) -> Self {
|
||||
Self {
|
||||
_c1: PhantomData,
|
||||
_gc1: PhantomData,
|
||||
_c2: PhantomData,
|
||||
_gc2: PhantomData,
|
||||
_cp1: PhantomData,
|
||||
_cp2: PhantomData,
|
||||
|
||||
E_len: nova.W_i.E.len(),
|
||||
cf_E_len: nova.cf_W_i.E.len(),
|
||||
r1cs: nova.r1cs,
|
||||
cf_r1cs: nova.cf_r1cs,
|
||||
cf_pedersen_params: nova.cf_cm_params,
|
||||
poseidon_config: nova.poseidon_config,
|
||||
i: Some(nova.i),
|
||||
z_0: Some(nova.z_0),
|
||||
z_i: Some(nova.z_i),
|
||||
u_i: Some(nova.u_i),
|
||||
w_i: Some(nova.w_i),
|
||||
U_i: Some(nova.U_i),
|
||||
W_i: Some(nova.W_i),
|
||||
cf_U_i: Some(nova.cf_U_i),
|
||||
cf_W_i: Some(nova.cf_W_i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1, GC1, C2, GC2, CP1, CP2> ConstraintSynthesizer<CF1<C1>>
|
||||
for DeciderEthCircuit<C1, GC1, C2, GC2, CP1, CP2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
CP1: CommitmentProver<C1>,
|
||||
CP2: CommitmentProver<C2>,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
|
||||
let r1cs =
|
||||
R1CSVar::<C1::ScalarField, CF1<C1>, FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
|
||||
Ok(self.r1cs.clone())
|
||||
})?;
|
||||
|
||||
let i =
|
||||
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::<C1>::zero)))?;
|
||||
let z_0 = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_0.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
let z_i = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
|
||||
let u_dummy_native = CommittedInstance::<C1>::dummy(1);
|
||||
let w_dummy_native = Witness::<C1>::new(
|
||||
vec![C1::ScalarField::zero(); self.r1cs.A.n_cols - 2 /* (2=1+1, since u_i.x.len=1) */],
|
||||
self.E_len,
|
||||
);
|
||||
|
||||
let u_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let w_i = WitnessVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.w_i.unwrap_or(w_dummy_native.clone()))
|
||||
})?;
|
||||
let U_i = CommittedInstanceVar::<C1>::new_input(cs.clone(), || {
|
||||
Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let W_i = WitnessVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.W_i.unwrap_or(w_dummy_native.clone()))
|
||||
})?;
|
||||
|
||||
let crh_params = CRHParametersVar::<C1::ScalarField>::new_constant(
|
||||
cs.clone(),
|
||||
self.poseidon_config.clone(),
|
||||
)?;
|
||||
|
||||
// 1. check RelaxedR1CS of u_i
|
||||
let z_u: Vec<FpVar<CF1<C1>>> = [
|
||||
vec![FpVar::<CF1<C1>>::one()],
|
||||
u_i.x.to_vec(),
|
||||
w_i.W.to_vec(),
|
||||
]
|
||||
.concat();
|
||||
RelaxedR1CSGadget::<C1::ScalarField, CF1<C1>, FpVar<CF1<C1>>>::check(
|
||||
r1cs.clone(),
|
||||
w_i.E,
|
||||
u_i.u.clone(),
|
||||
z_u,
|
||||
)?;
|
||||
|
||||
// 2. check RelaxedR1CS of U_i
|
||||
let z_U: Vec<FpVar<CF1<C1>>> =
|
||||
[vec![U_i.u.clone()], U_i.x.to_vec(), W_i.W.to_vec()].concat();
|
||||
RelaxedR1CSGadget::<C1::ScalarField, CF1<C1>, FpVar<CF1<C1>>>::check(
|
||||
r1cs,
|
||||
W_i.E,
|
||||
U_i.u.clone(),
|
||||
z_U,
|
||||
)?;
|
||||
|
||||
// 3. u_i.cmE==cm(0), u_i.u==1
|
||||
// Here zero_x & zero_y are the x & y coordinates of the zero point affine representation.
|
||||
let zero_x = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
|
||||
cs.clone(),
|
||||
C1::BaseField::zero(),
|
||||
)?
|
||||
.to_constraint_field()?;
|
||||
let zero_y = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
|
||||
cs.clone(),
|
||||
C1::BaseField::one(),
|
||||
)?
|
||||
.to_constraint_field()?;
|
||||
(u_i.cmE.x.is_eq(&zero_x)?).enforce_equal(&Boolean::TRUE)?;
|
||||
(u_i.cmE.y.is_eq(&zero_y)?).enforce_equal(&Boolean::TRUE)?;
|
||||
(u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?;
|
||||
|
||||
// 4. u_i.x == H(i, z_0, z_i, U_i)
|
||||
let u_i_x = U_i
|
||||
.clone()
|
||||
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
|
||||
(u_i.x[0]).enforce_equal(&u_i_x)?;
|
||||
|
||||
// The following two checks (and their respective allocations) are disabled for normal
|
||||
// tests since they take ~24.5M constraints and would take several minutes (and RAM) to run
|
||||
// the test
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
// imports here instead of at the top of the file, so we avoid having multiple
|
||||
// `#[cfg(not(test))]`
|
||||
use crate::commitment::pedersen::PedersenGadget;
|
||||
use crate::folding::nova::cyclefold::{CycleFoldCommittedInstanceVar, CF_IO_LEN};
|
||||
use ark_r1cs_std::ToBitsGadget;
|
||||
|
||||
let cf_r1cs = R1CSVar::<
|
||||
C1::BaseField,
|
||||
CF1<C1>,
|
||||
NonNativeFieldVar<C1::BaseField, CF1<C1>>,
|
||||
>::new_witness(cs.clone(), || Ok(self.cf_r1cs.clone()))?;
|
||||
|
||||
let cf_u_dummy_native = CommittedInstance::<C2>::dummy(CF_IO_LEN);
|
||||
let w_dummy_native = Witness::<C2>::new(
|
||||
vec![C2::ScalarField::zero(); self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l],
|
||||
self.cf_E_len,
|
||||
);
|
||||
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
|
||||
})?;
|
||||
let cf_W_i = CycleFoldWitnessVar::<C2>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_W_i.unwrap_or(w_dummy_native.clone()))
|
||||
})?;
|
||||
|
||||
// 5. check Pedersen commitments of cf_U_i.{cmE, cmW}
|
||||
let H = GC2::new_constant(cs.clone(), self.cf_pedersen_params.h)?;
|
||||
let G = Vec::<GC2>::new_constant(cs.clone(), self.cf_pedersen_params.generators)?;
|
||||
let cf_W_i_E_bits: Vec<Vec<Boolean<CF1<C1>>>> = cf_W_i
|
||||
.E
|
||||
.iter()
|
||||
.map(|E_i| E_i.to_bits_le().unwrap())
|
||||
.collect();
|
||||
let cf_W_i_W_bits: Vec<Vec<Boolean<CF1<C1>>>> = cf_W_i
|
||||
.W
|
||||
.iter()
|
||||
.map(|W_i| W_i.to_bits_le().unwrap())
|
||||
.collect();
|
||||
|
||||
let computed_cmE = PedersenGadget::<C2, GC2>::commit(
|
||||
H.clone(),
|
||||
G.clone(),
|
||||
cf_W_i_E_bits,
|
||||
cf_W_i.rE.to_bits_le()?,
|
||||
)?;
|
||||
cf_U_i.cmE.enforce_equal(&computed_cmE)?;
|
||||
let computed_cmW =
|
||||
PedersenGadget::<C2, GC2>::commit(H, G, cf_W_i_W_bits, cf_W_i.rW.to_bits_le()?)?;
|
||||
cf_U_i.cmW.enforce_equal(&computed_cmW)?;
|
||||
|
||||
// 6. check RelaxedR1CS of cf_U_i
|
||||
let cf_z_U: Vec<NonNativeFieldVar<C2::ScalarField, CF1<C1>>> =
|
||||
[vec![cf_U_i.u.clone()], cf_U_i.x.to_vec(), cf_W_i.W.to_vec()].concat();
|
||||
RelaxedR1CSGadget::<
|
||||
C2::ScalarField,
|
||||
CF1<C1>,
|
||||
NonNativeFieldVar<C2::ScalarField, CF1<C1>>,
|
||||
>::check(cf_r1cs, cf_W_i.E, cf_U_i.u.clone(), cf_z_U)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ark_crypto_primitives::crh::{
|
||||
sha256::{
|
||||
constraints::{Sha256Gadget, UnitVar},
|
||||
Sha256,
|
||||
},
|
||||
CRHScheme, CRHSchemeGadget,
|
||||
};
|
||||
use ark_ff::BigInteger;
|
||||
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
|
||||
use ark_r1cs_std::{
|
||||
alloc::AllocVar,
|
||||
bits::uint8::UInt8,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
|
||||
};
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
|
||||
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::{get_pedersen_params_len, ProverParams, VerifierParams};
|
||||
use crate::frontend::tests::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
|
||||
use crate::transcript::poseidon::poseidon_test_config;
|
||||
use crate::FoldingScheme;
|
||||
|
||||
use crate::ccs::r1cs::{extract_r1cs, extract_w_x};
|
||||
use crate::ccs::r1cs::{
|
||||
tests::{get_test_r1cs, get_test_z},
|
||||
R1CS,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_small_gadget_handcrafted() {
|
||||
let r1cs: R1CS<Fr> = get_test_r1cs();
|
||||
let rel_r1cs = r1cs.clone().relax();
|
||||
let z = get_test_z(3);
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
|
||||
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(rel_r1cs.E)).unwrap();
|
||||
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(rel_r1cs.u)).unwrap();
|
||||
let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
|
||||
|
||||
RelaxedR1CSGadget::<Fr, Fr, FpVar<Fr>>::check(r1csVar, EVar, uVar, zVar).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
|
||||
// gets as input a circuit that implements the ConstraintSynthesizer trait, and that has been
|
||||
// initialized.
|
||||
fn test_relaxed_r1cs_gadget<CS: ConstraintSynthesizer<Fr>>(circuit: CS) {
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
circuit.generate_constraints(cs.clone()).unwrap();
|
||||
cs.finalize();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
|
||||
let cs = cs.into_inner().unwrap();
|
||||
|
||||
let r1cs = extract_r1cs::<Fr>(&cs);
|
||||
let (w, x) = extract_w_x::<Fr>(&cs);
|
||||
let z = [vec![Fr::one()], x, w].concat();
|
||||
r1cs.check_relation(&z).unwrap();
|
||||
|
||||
let relaxed_r1cs = r1cs.clone().relax();
|
||||
relaxed_r1cs.check_relation(&z).unwrap();
|
||||
|
||||
// set new CS for the circuit that checks the RelaxedR1CS of our original circuit
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
// prepare the inputs for our circuit
|
||||
let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
|
||||
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(relaxed_r1cs.E)).unwrap();
|
||||
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
|
||||
let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
|
||||
|
||||
RelaxedR1CSGadget::<Fr, Fr, FpVar<Fr>>::check(r1csVar, EVar, uVar, zVar).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_small_gadget_arkworks() {
|
||||
let z_i = vec![Fr::from(3_u32)];
|
||||
let cubic_circuit = CubicFCircuit::<Fr>::new(());
|
||||
let circuit = WrapperCircuit::<Fr, CubicFCircuit<Fr>> {
|
||||
FC: cubic_circuit,
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(cubic_circuit.step_native(z_i).unwrap()),
|
||||
};
|
||||
|
||||
test_relaxed_r1cs_gadget(circuit);
|
||||
}
|
||||
|
||||
struct Sha256TestCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
pub x: Vec<u8>,
|
||||
pub y: Vec<u8>,
|
||||
}
|
||||
impl<F: PrimeField> ConstraintSynthesizer<F> for Sha256TestCircuit<F> {
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
|
||||
let x = Vec::<UInt8<F>>::new_witness(cs.clone(), || Ok(self.x))?;
|
||||
let y = Vec::<UInt8<F>>::new_input(cs.clone(), || Ok(self.y))?;
|
||||
|
||||
let unitVar = UnitVar::default();
|
||||
let comp_y = <Sha256Gadget<F> as CRHSchemeGadget<Sha256, F>>::evaluate(&unitVar, &x)?;
|
||||
comp_y.0.enforce_equal(&y)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_medium_gadget_arkworks() {
|
||||
let x = Fr::from(5_u32).into_bigint().to_bytes_le();
|
||||
let y = <Sha256 as CRHScheme>::evaluate(&(), x.clone()).unwrap();
|
||||
|
||||
let circuit = Sha256TestCircuit::<Fr> {
|
||||
_f: PhantomData,
|
||||
x,
|
||||
y,
|
||||
};
|
||||
test_relaxed_r1cs_gadget(circuit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_custom_circuit() {
|
||||
let n_constraints = 10_000;
|
||||
let custom_circuit = CustomFCircuit::<Fr>::new(n_constraints);
|
||||
let z_i = vec![Fr::from(5_u32)];
|
||||
let circuit = WrapperCircuit::<Fr, CustomFCircuit<Fr>> {
|
||||
FC: custom_circuit,
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(custom_circuit.step_native(z_i).unwrap()),
|
||||
};
|
||||
test_relaxed_r1cs_gadget(circuit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_nonnative_circuit() {
|
||||
let cs = ConstraintSystem::<Fq>::new_ref();
|
||||
// in practice we would use CycleFoldCircuit, but is a very big circuit (when computed
|
||||
// non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a
|
||||
// custom circuit.
|
||||
let custom_circuit = CustomFCircuit::<Fq>::new(10);
|
||||
let z_i = vec![Fq::from(5_u32)];
|
||||
let circuit = WrapperCircuit::<Fq, CustomFCircuit<Fq>> {
|
||||
FC: custom_circuit,
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(custom_circuit.step_native(z_i).unwrap()),
|
||||
};
|
||||
circuit.generate_constraints(cs.clone()).unwrap();
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().unwrap();
|
||||
let r1cs = extract_r1cs::<Fq>(&cs);
|
||||
let (w, x) = extract_w_x::<Fq>(&cs);
|
||||
let z = [vec![Fq::one()], x, w].concat();
|
||||
|
||||
let relaxed_r1cs = r1cs.clone().relax();
|
||||
|
||||
// natively
|
||||
let cs = ConstraintSystem::<Fq>::new_ref();
|
||||
let zVar = Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(z.clone())).unwrap();
|
||||
let EVar =
|
||||
Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(relaxed_r1cs.clone().E)).unwrap();
|
||||
let uVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
|
||||
let r1csVar =
|
||||
R1CSVar::<Fq, Fq, FpVar<Fq>>::new_witness(cs.clone(), || Ok(r1cs.clone())).unwrap();
|
||||
RelaxedR1CSGadget::<Fq, Fq, FpVar<Fq>>::check(r1csVar, EVar, uVar, zVar).unwrap();
|
||||
|
||||
// non-natively
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let zVar = Vec::<NonNativeFieldVar<Fq, Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
|
||||
let EVar = Vec::<NonNativeFieldVar<Fq, Fr>>::new_witness(cs.clone(), || Ok(relaxed_r1cs.E))
|
||||
.unwrap();
|
||||
let uVar =
|
||||
NonNativeFieldVar::<Fq, Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
|
||||
let r1csVar =
|
||||
R1CSVar::<Fq, Fr, NonNativeFieldVar<Fq, Fr>>::new_witness(cs.clone(), || Ok(r1cs))
|
||||
.unwrap();
|
||||
RelaxedR1CSGadget::<Fq, Fr, NonNativeFieldVar<Fq, Fr>>::check(r1csVar, EVar, uVar, zVar)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decider_circuit() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
|
||||
let F_circuit = CubicFCircuit::<Fr>::new(());
|
||||
let z_0 = vec![Fr::from(3_u32)];
|
||||
|
||||
// get the CM & CF_CM len
|
||||
let (cm_len, cf_cm_len) =
|
||||
get_pedersen_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>(
|
||||
&poseidon_config,
|
||||
F_circuit,
|
||||
)
|
||||
.unwrap();
|
||||
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, cm_len);
|
||||
let cf_pedersen_params = Pedersen::<Projective2>::new_params(&mut rng, cf_cm_len);
|
||||
|
||||
let prover_params =
|
||||
ProverParams::<Projective, Projective2, Pedersen<Projective>, Pedersen<Projective2>> {
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
cm_params: pedersen_params,
|
||||
cf_cm_params: cf_pedersen_params,
|
||||
};
|
||||
|
||||
type NOVA = Nova<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
CubicFCircuit<Fr>,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
>;
|
||||
|
||||
// generate a Nova instance and do a step of it
|
||||
let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap();
|
||||
nova.prove_step().unwrap();
|
||||
let ivc_v = nova.clone();
|
||||
let verifier_params = VerifierParams::<Projective, Projective2> {
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
r1cs: ivc_v.r1cs,
|
||||
cf_r1cs: ivc_v.cf_r1cs,
|
||||
};
|
||||
NOVA::verify(
|
||||
verifier_params,
|
||||
z_0,
|
||||
ivc_v.z_i,
|
||||
Fr::one(),
|
||||
(ivc_v.U_i, ivc_v.W_i),
|
||||
(ivc_v.u_i, ivc_v.w_i),
|
||||
(ivc_v.cf_U_i, ivc_v.cf_W_i),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// load the DeciderEthCircuit from the generated Nova instance
|
||||
let decider_circuit = DeciderEthCircuit::<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
>::from_nova(nova);
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
// generate the constraints and check that are satisfied by the inputs
|
||||
decider_circuit.generate_constraints(cs.clone()).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
dbg!(cs.num_constraints());
|
||||
}
|
||||
}
|
||||
717
folding-schemes/src/folding/nova/mod.rs
Normal file
717
folding-schemes/src/folding/nova/mod.rs
Normal file
@@ -0,0 +1,717 @@
|
||||
/// 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_ec::{AffineRepr, CurveGroup, Group};
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar};
|
||||
use ark_std::fmt::Debug;
|
||||
use ark_std::{One, Zero};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
||||
|
||||
use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS};
|
||||
use crate::commitment::CommitmentProver;
|
||||
use crate::folding::circuits::nonnative::point_to_nonnative_limbs;
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::utils::vec::is_zero_vec;
|
||||
use crate::Error;
|
||||
use crate::FoldingScheme;
|
||||
|
||||
pub mod circuits;
|
||||
pub mod cyclefold;
|
||||
pub mod decider_eth;
|
||||
pub mod decider_eth_circuit;
|
||||
pub mod nifs;
|
||||
pub mod traits;
|
||||
|
||||
use circuits::{AugmentedFCircuit, ChallengeGadget, CF2};
|
||||
use cyclefold::{CycleFoldChallengeGadget, CycleFoldCircuit};
|
||||
use nifs::NIFS;
|
||||
use traits::NovaR1CS;
|
||||
|
||||
#[cfg(test)]
|
||||
use cyclefold::CF_IO_LEN;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct CommittedInstance<C: CurveGroup> {
|
||||
pub cmE: C,
|
||||
pub u: C::ScalarField,
|
||||
pub cmW: C,
|
||||
pub x: Vec<C::ScalarField>,
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> CommittedInstance<C> {
|
||||
pub fn dummy(io_len: usize) -> Self {
|
||||
Self {
|
||||
cmE: C::zero(),
|
||||
u: C::ScalarField::zero(),
|
||||
cmW: C::zero(),
|
||||
x: vec![C::ScalarField::zero(); io_len],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> CommittedInstance<C>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
/// hash implements the committed instance hash compatible with the gadget implemented in
|
||||
/// nova/circuits.rs::CommittedInstanceVar.hash.
|
||||
/// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U_i` is the
|
||||
/// `CommittedInstance`.
|
||||
pub fn hash(
|
||||
&self,
|
||||
poseidon_config: &PoseidonConfig<C::ScalarField>,
|
||||
i: C::ScalarField,
|
||||
z_0: Vec<C::ScalarField>,
|
||||
z_i: Vec<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)?;
|
||||
|
||||
CRH::<C::ScalarField>::evaluate(
|
||||
poseidon_config,
|
||||
vec![
|
||||
vec![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()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Witness<C: CurveGroup> {
|
||||
pub E: Vec<C::ScalarField>,
|
||||
pub rE: C::ScalarField,
|
||||
pub W: Vec<C::ScalarField>,
|
||||
pub rW: C::ScalarField,
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> Witness<C>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
{
|
||||
pub fn new(w: Vec<C::ScalarField>, e_len: usize) -> Self {
|
||||
// note: at the current version, we don't use the blinding factors and we set them to 0
|
||||
// always.
|
||||
Self {
|
||||
E: vec![C::ScalarField::zero(); e_len],
|
||||
rE: C::ScalarField::zero(),
|
||||
W: w,
|
||||
rW: C::ScalarField::zero(),
|
||||
}
|
||||
}
|
||||
pub fn commit<CP: CommitmentProver<C>>(
|
||||
&self,
|
||||
params: &CP::Params,
|
||||
x: Vec<C::ScalarField>,
|
||||
) -> Result<CommittedInstance<C>, Error> {
|
||||
let mut cmE = C::zero();
|
||||
if !is_zero_vec::<C::ScalarField>(&self.E) {
|
||||
cmE = CP::commit(params, &self.E, &self.rE)?;
|
||||
}
|
||||
let cmW = CP::commit(params, &self.W, &self.rW)?;
|
||||
Ok(CommittedInstance {
|
||||
cmE,
|
||||
u: C::ScalarField::one(),
|
||||
cmW,
|
||||
x,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProverParams<C1, C2, CP1, CP2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
CP1: CommitmentProver<C1>,
|
||||
CP2: CommitmentProver<C2>,
|
||||
{
|
||||
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
|
||||
pub cm_params: CP1::Params,
|
||||
pub cf_cm_params: CP2::Params,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VerifierParams<C1: CurveGroup, C2: CurveGroup> {
|
||||
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
|
||||
pub r1cs: R1CS<C1::ScalarField>,
|
||||
pub cf_r1cs: R1CS<C2::ScalarField>,
|
||||
}
|
||||
|
||||
/// Implements Nova+CycleFold's IVC, described in [Nova](https://eprint.iacr.org/2021/370.pdf) and
|
||||
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Nova<C1, GC1, C2, GC2, FC, CP1, CP2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
CP1: CommitmentProver<C1>,
|
||||
CP2: CommitmentProver<C2>,
|
||||
{
|
||||
_gc1: PhantomData<GC1>,
|
||||
_c2: PhantomData<C2>,
|
||||
_gc2: PhantomData<GC2>,
|
||||
/// R1CS of the Augmented Function circuit
|
||||
pub r1cs: R1CS<C1::ScalarField>,
|
||||
/// R1CS of the CycleFold circuit
|
||||
pub cf_r1cs: R1CS<C2::ScalarField>,
|
||||
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
|
||||
/// CommitmentProver::Params over C1
|
||||
pub cm_params: CP1::Params,
|
||||
/// CycleFold CommitmentProver::Params, over C2
|
||||
pub cf_cm_params: CP2::Params,
|
||||
/// F circuit, the circuit that is being folded
|
||||
pub F: FC,
|
||||
pub i: C1::ScalarField,
|
||||
/// initial state
|
||||
pub z_0: Vec<C1::ScalarField>,
|
||||
/// current i-th state
|
||||
pub z_i: Vec<C1::ScalarField>,
|
||||
/// Nova instances
|
||||
pub w_i: Witness<C1>,
|
||||
pub u_i: CommittedInstance<C1>,
|
||||
pub W_i: Witness<C1>,
|
||||
pub U_i: CommittedInstance<C1>,
|
||||
|
||||
/// CycleFold running instance
|
||||
pub cf_W_i: Witness<C2>,
|
||||
pub cf_U_i: CommittedInstance<C2>,
|
||||
}
|
||||
|
||||
impl<C1, GC1, C2, GC2, FC, CP1, CP2> FoldingScheme<C1, C2, FC>
|
||||
for Nova<C1, GC1, C2, GC2, FC, CP1, CP2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
CP1: CommitmentProver<C1>,
|
||||
CP2: CommitmentProver<C2>,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
type PreprocessorParam = (Self::ProverParam, FC);
|
||||
type ProverParam = ProverParams<C1, C2, CP1, CP2>;
|
||||
type VerifierParam = VerifierParams<C1, C2>;
|
||||
type CommittedInstanceWithWitness = (CommittedInstance<C1>, Witness<C1>);
|
||||
type CFCommittedInstanceWithWitness = (CommittedInstance<C2>, Witness<C2>);
|
||||
|
||||
fn preprocess(
|
||||
prep_param: &Self::PreprocessorParam,
|
||||
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
|
||||
let (prover_params, F_circuit) = prep_param;
|
||||
|
||||
let (r1cs, cf_r1cs) =
|
||||
get_r1cs::<C1, GC1, C2, GC2, FC>(&prover_params.poseidon_config, *F_circuit)?;
|
||||
|
||||
let verifier_params = VerifierParams::<C1, C2> {
|
||||
poseidon_config: prover_params.poseidon_config.clone(),
|
||||
r1cs,
|
||||
cf_r1cs,
|
||||
};
|
||||
Ok((prover_params.clone(), verifier_params))
|
||||
}
|
||||
|
||||
/// Initializes the Nova+CycleFold's IVC for the given parameters and initial state `z_0`.
|
||||
fn init(pp: &Self::ProverParam, F: FC, z_0: Vec<C1::ScalarField>) -> Result<Self, Error> {
|
||||
// prepare the circuit to obtain its R1CS
|
||||
let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
|
||||
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
|
||||
|
||||
let augmented_F_circuit =
|
||||
AugmentedFCircuit::<C1, C2, GC2, FC>::empty(&pp.poseidon_config, F);
|
||||
let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty();
|
||||
|
||||
augmented_F_circuit.generate_constraints(cs.clone())?;
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
|
||||
|
||||
cf_circuit.generate_constraints(cs2.clone())?;
|
||||
cs2.finalize();
|
||||
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2);
|
||||
|
||||
// setup the dummy instances
|
||||
let (w_dummy, u_dummy) = r1cs.dummy_instance();
|
||||
let (cf_w_dummy, cf_u_dummy) = cf_r1cs.dummy_instance();
|
||||
|
||||
// W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the
|
||||
// R1CS that we're working with.
|
||||
Ok(Self {
|
||||
_gc1: PhantomData,
|
||||
_c2: PhantomData,
|
||||
_gc2: PhantomData,
|
||||
r1cs,
|
||||
cf_r1cs,
|
||||
poseidon_config: pp.poseidon_config.clone(),
|
||||
cm_params: pp.cm_params.clone(),
|
||||
cf_cm_params: pp.cf_cm_params.clone(),
|
||||
F,
|
||||
i: C1::ScalarField::zero(),
|
||||
z_0: z_0.clone(),
|
||||
z_i: z_0,
|
||||
w_i: w_dummy.clone(),
|
||||
u_i: u_dummy.clone(),
|
||||
W_i: w_dummy,
|
||||
U_i: u_dummy,
|
||||
// cyclefold running instance
|
||||
cf_W_i: cf_w_dummy.clone(),
|
||||
cf_U_i: cf_u_dummy.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Implements IVC.P of Nova+CycleFold
|
||||
fn prove_step(&mut self) -> Result<(), Error> {
|
||||
let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>;
|
||||
let cf_circuit: CycleFoldCircuit<C1, GC1>;
|
||||
|
||||
let z_i1 = self.F.step_native(self.z_i.clone())?;
|
||||
|
||||
// compute T and cmT for AugmentedFCircuit
|
||||
let (T, cmT) = self.compute_cmT()?;
|
||||
|
||||
let r_bits = ChallengeGadget::<C1>::get_challenge_native(
|
||||
&self.poseidon_config,
|
||||
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)?;
|
||||
|
||||
// fold Nova instances
|
||||
let (W_i1, U_i1): (Witness<C1>, CommittedInstance<C1>) = NIFS::<C1, CP1>::fold_instances(
|
||||
r_Fr, &self.w_i, &self.u_i, &self.W_i, &self.U_i, &T, cmT,
|
||||
)?;
|
||||
|
||||
// folded instance output (public input, x)
|
||||
// u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1})
|
||||
let u_i1_x = U_i1.hash(
|
||||
&self.poseidon_config,
|
||||
self.i + C1::ScalarField::one(),
|
||||
self.z_0.clone(),
|
||||
z_i1.clone(),
|
||||
)?;
|
||||
|
||||
if self.i == C1::ScalarField::zero() {
|
||||
// base case
|
||||
augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
|
||||
_gc2: PhantomData,
|
||||
poseidon_config: self.poseidon_config.clone(),
|
||||
i: Some(C1::ScalarField::zero()), // = i=0
|
||||
z_0: Some(self.z_0.clone()), // = z_i
|
||||
z_i: Some(self.z_i.clone()),
|
||||
u_i: Some(self.u_i.clone()), // = dummy
|
||||
U_i: Some(self.U_i.clone()), // = dummy
|
||||
U_i1: Some(U_i1.clone()),
|
||||
cmT: Some(cmT),
|
||||
F: self.F,
|
||||
x: Some(u_i1_x),
|
||||
cf_u_i: None,
|
||||
cf_U_i: None,
|
||||
cf_U_i1: None,
|
||||
cf_cmT: None,
|
||||
cf_r_nonnat: None,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
NIFS::<C1, CP1>::verify_folded_instance(r_Fr, &self.u_i, &self.U_i, &U_i1, &cmT)?;
|
||||
} else {
|
||||
// CycleFold part:
|
||||
// get the vector used as public inputs 'x' in the CycleFold circuit
|
||||
let cf_u_i_x = [
|
||||
get_committed_instance_coordinates(&self.u_i),
|
||||
get_committed_instance_coordinates(&self.U_i),
|
||||
get_committed_instance_coordinates(&U_i1),
|
||||
]
|
||||
.concat();
|
||||
|
||||
cf_circuit = CycleFoldCircuit::<C1, GC1> {
|
||||
_gc: PhantomData,
|
||||
r_bits: Some(r_bits.clone()),
|
||||
cmT: Some(cmT),
|
||||
u_i: Some(self.u_i.clone()),
|
||||
U_i: Some(self.U_i.clone()),
|
||||
U_i1: Some(U_i1.clone()),
|
||||
x: Some(cf_u_i_x.clone()),
|
||||
};
|
||||
|
||||
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
|
||||
cf_circuit.generate_constraints(cs2.clone())?;
|
||||
|
||||
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let (cf_w_i, cf_x_i) = extract_w_x::<C1::BaseField>(&cs2);
|
||||
if cf_x_i != cf_u_i_x {
|
||||
return Err(Error::NotEqual);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
if cf_x_i.len() != CF_IO_LEN {
|
||||
return Err(Error::NotExpectedLength(cf_x_i.len(), CF_IO_LEN));
|
||||
}
|
||||
|
||||
// fold cyclefold instances
|
||||
let cf_w_i = Witness::<C2>::new(cf_w_i.clone(), self.cf_r1cs.A.n_rows);
|
||||
let cf_u_i: CommittedInstance<C2> =
|
||||
cf_w_i.commit::<CP2>(&self.cf_cm_params, cf_x_i.clone())?;
|
||||
|
||||
// compute T* and cmT* for CycleFoldCircuit
|
||||
let (cf_T, cf_cmT) = self.compute_cf_cmT(&cf_w_i, &cf_u_i)?;
|
||||
|
||||
let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_native(
|
||||
&self.poseidon_config,
|
||||
cf_u_i.clone(),
|
||||
self.cf_U_i.clone(),
|
||||
cf_cmT,
|
||||
)?;
|
||||
let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits))
|
||||
.ok_or(Error::OutOfBounds)?;
|
||||
|
||||
let (cf_W_i1, cf_U_i1) = NIFS::<C2, CP2>::fold_instances(
|
||||
cf_r_Fq,
|
||||
&self.cf_W_i,
|
||||
&self.cf_U_i,
|
||||
&cf_w_i,
|
||||
&cf_u_i,
|
||||
&cf_T,
|
||||
cf_cmT,
|
||||
)?;
|
||||
|
||||
augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
|
||||
_gc2: PhantomData,
|
||||
poseidon_config: self.poseidon_config.clone(),
|
||||
i: Some(self.i),
|
||||
z_0: Some(self.z_0.clone()),
|
||||
z_i: Some(self.z_i.clone()),
|
||||
u_i: Some(self.u_i.clone()),
|
||||
U_i: Some(self.U_i.clone()),
|
||||
U_i1: Some(U_i1.clone()),
|
||||
cmT: Some(cmT),
|
||||
F: self.F,
|
||||
x: Some(u_i1_x),
|
||||
// cyclefold values
|
||||
cf_u_i: Some(cf_u_i.clone()),
|
||||
cf_U_i: Some(self.cf_U_i.clone()),
|
||||
cf_U_i1: Some(cf_U_i1.clone()),
|
||||
cf_cmT: Some(cf_cmT),
|
||||
cf_r_nonnat: Some(cf_r_Fq),
|
||||
};
|
||||
|
||||
self.cf_W_i = cf_W_i1.clone();
|
||||
self.cf_U_i = cf_U_i1.clone();
|
||||
|
||||
#[cfg(test)]
|
||||
{
|
||||
self.cf_r1cs.check_instance_relation(&cf_w_i, &cf_u_i)?;
|
||||
self.cf_r1cs
|
||||
.check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?;
|
||||
}
|
||||
}
|
||||
|
||||
let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
|
||||
|
||||
augmented_F_circuit.generate_constraints(cs.clone())?;
|
||||
|
||||
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let (w_i1, x_i1) = extract_w_x::<C1::ScalarField>(&cs);
|
||||
if x_i1[0] != u_i1_x {
|
||||
return Err(Error::NotEqual);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
if x_i1.len() != 1 {
|
||||
return Err(Error::NotExpectedLength(x_i1.len(), 1));
|
||||
}
|
||||
|
||||
// set values for next iteration
|
||||
self.i += C1::ScalarField::one();
|
||||
self.z_i = z_i1.clone();
|
||||
self.w_i = Witness::<C1>::new(w_i1, self.r1cs.A.n_rows);
|
||||
self.u_i = self.w_i.commit::<CP1>(&self.cm_params, vec![u_i1_x])?;
|
||||
self.W_i = W_i1.clone();
|
||||
self.U_i = U_i1.clone();
|
||||
|
||||
#[cfg(test)]
|
||||
{
|
||||
self.r1cs.check_instance_relation(&self.w_i, &self.u_i)?;
|
||||
self.r1cs
|
||||
.check_relaxed_instance_relation(&self.W_i, &self.U_i)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state(&self) -> Vec<C1::ScalarField> {
|
||||
self.z_i.clone()
|
||||
}
|
||||
fn instances(
|
||||
&self,
|
||||
) -> (
|
||||
Self::CommittedInstanceWithWitness,
|
||||
Self::CommittedInstanceWithWitness,
|
||||
Self::CFCommittedInstanceWithWitness,
|
||||
) {
|
||||
(
|
||||
(self.U_i.clone(), self.W_i.clone()),
|
||||
(self.u_i.clone(), self.w_i.clone()),
|
||||
(self.cf_U_i.clone(), self.cf_W_i.clone()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Implements IVC.V of Nova+CycleFold
|
||||
fn verify(
|
||||
vp: Self::VerifierParam,
|
||||
z_0: Vec<C1::ScalarField>, // initial state
|
||||
z_i: Vec<C1::ScalarField>, // last state
|
||||
num_steps: C1::ScalarField,
|
||||
running_instance: Self::CommittedInstanceWithWitness,
|
||||
incomming_instance: Self::CommittedInstanceWithWitness,
|
||||
cyclefold_instance: Self::CFCommittedInstanceWithWitness,
|
||||
) -> Result<(), Error> {
|
||||
let (U_i, W_i) = running_instance;
|
||||
let (u_i, w_i) = incomming_instance;
|
||||
let (cf_U_i, cf_W_i) = cyclefold_instance;
|
||||
|
||||
if u_i.x.len() != 1 || U_i.x.len() != 1 {
|
||||
return Err(Error::IVCVerificationFail);
|
||||
}
|
||||
|
||||
// check that u_i's output points to the running instance
|
||||
// u_i.X == H(i, z_0, z_i, U_i)
|
||||
let expected_u_i_x = U_i.hash(&vp.poseidon_config, num_steps, z_0, z_i.clone())?;
|
||||
if expected_u_i_x != u_i.x[0] {
|
||||
return Err(Error::IVCVerificationFail);
|
||||
}
|
||||
|
||||
// check u_i.cmE==0, u_i.u==1 (=u_i is a un-relaxed instance)
|
||||
if !u_i.cmE.is_zero() || !u_i.u.is_one() {
|
||||
return Err(Error::IVCVerificationFail);
|
||||
}
|
||||
|
||||
// check R1CS satisfiability
|
||||
vp.r1cs.check_instance_relation(&w_i, &u_i)?;
|
||||
// check RelaxedR1CS satisfiability
|
||||
vp.r1cs.check_relaxed_instance_relation(&W_i, &U_i)?;
|
||||
|
||||
// check CycleFold RelaxedR1CS satisfiability
|
||||
vp.cf_r1cs
|
||||
.check_relaxed_instance_relation(&cf_W_i, &cf_U_i)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1, GC1, C2, GC2, FC, CP1, CP2> Nova<C1, GC1, C2, GC2, FC, CP1, CP2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
CP1: CommitmentProver<C1>,
|
||||
CP2: CommitmentProver<C2>,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
{
|
||||
// computes T and cmT for the AugmentedFCircuit
|
||||
fn compute_cmT(&self) -> Result<(Vec<C1::ScalarField>, C1), Error> {
|
||||
NIFS::<C1, CP1>::compute_cmT(
|
||||
&self.cm_params,
|
||||
&self.r1cs,
|
||||
&self.w_i,
|
||||
&self.u_i,
|
||||
&self.W_i,
|
||||
&self.U_i,
|
||||
)
|
||||
}
|
||||
// computes T* and cmT* for the CycleFoldCircuit
|
||||
fn compute_cf_cmT(
|
||||
&self,
|
||||
cf_w_i: &Witness<C2>,
|
||||
cf_u_i: &CommittedInstance<C2>,
|
||||
) -> Result<(Vec<C2::ScalarField>, C2), Error> {
|
||||
NIFS::<C2, CP2>::compute_cyclefold_cmT(
|
||||
&self.cf_cm_params,
|
||||
&self.cf_r1cs,
|
||||
cf_w_i,
|
||||
cf_u_i,
|
||||
&self.cf_W_i,
|
||||
&self.cf_U_i,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// helper method to get the r1cs from the ConstraintSynthesizer
|
||||
pub fn get_r1cs_from_cs<F: PrimeField>(
|
||||
circuit: impl ConstraintSynthesizer<F>,
|
||||
) -> Result<R1CS<F>, Error> {
|
||||
let cs = ConstraintSystem::<F>::new_ref();
|
||||
circuit.generate_constraints(cs.clone())?;
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let r1cs = extract_r1cs::<F>(&cs);
|
||||
Ok(r1cs)
|
||||
}
|
||||
|
||||
/// helper method to get the R1CS for both the AugmentedFCircuit and the CycleFold circuit
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn get_r1cs<C1, GC1, C2, GC2, FC>(
|
||||
poseidon_config: &PoseidonConfig<C1::ScalarField>,
|
||||
F_circuit: FC,
|
||||
) -> Result<(R1CS<C1::ScalarField>, R1CS<C2::ScalarField>), Error>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
let augmented_F_circuit =
|
||||
AugmentedFCircuit::<C1, C2, GC2, FC>::empty(poseidon_config, F_circuit);
|
||||
let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty();
|
||||
let r1cs = get_r1cs_from_cs::<C1::ScalarField>(augmented_F_circuit)?;
|
||||
let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
|
||||
Ok((r1cs, cf_r1cs))
|
||||
}
|
||||
|
||||
/// helper method to get the pedersen params length for both the AugmentedFCircuit and the
|
||||
/// CycleFold circuit
|
||||
pub fn get_pedersen_params_len<C1, GC1, C2, GC2, FC>(
|
||||
poseidon_config: &PoseidonConfig<C1::ScalarField>,
|
||||
F_circuit: FC,
|
||||
) -> Result<(usize, usize), Error>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
let (r1cs, cf_r1cs) = get_r1cs::<C1, GC1, C2, GC2, FC>(poseidon_config, F_circuit)?;
|
||||
Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows))
|
||||
}
|
||||
|
||||
pub(crate) fn get_committed_instance_coordinates<C: CurveGroup>(
|
||||
u: &CommittedInstance<C>,
|
||||
) -> Vec<C::BaseField> {
|
||||
let zero = (&C::BaseField::zero(), &C::BaseField::one());
|
||||
|
||||
let cmE = u.cmE.into_affine();
|
||||
let (cmE_x, cmE_y) = cmE.xy().unwrap_or(zero);
|
||||
|
||||
let cmW = u.cmW.into_affine();
|
||||
let (cmW_x, cmW_y) = cmW.xy().unwrap_or(zero);
|
||||
vec![*cmE_x, *cmE_y, *cmW_x, *cmW_y]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ark_pallas::{constraints::GVar, Fr, Projective};
|
||||
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
|
||||
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::frontend::tests::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_test_config;
|
||||
|
||||
#[test]
|
||||
fn test_ivc() {
|
||||
type NOVA = Nova<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
CubicFCircuit<Fr>,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
>;
|
||||
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
|
||||
let F_circuit = CubicFCircuit::<Fr>::new(());
|
||||
let z_0 = vec![Fr::from(3_u32)];
|
||||
|
||||
let (cm_len, cf_cm_len) =
|
||||
get_pedersen_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>(
|
||||
&poseidon_config,
|
||||
F_circuit,
|
||||
)
|
||||
.unwrap();
|
||||
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, cm_len);
|
||||
let cf_pedersen_params = Pedersen::<Projective2>::new_params(&mut rng, cf_cm_len);
|
||||
|
||||
let prover_params =
|
||||
ProverParams::<Projective, Projective2, Pedersen<Projective>, Pedersen<Projective2>> {
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
cm_params: pedersen_params,
|
||||
cf_cm_params: cf_pedersen_params,
|
||||
};
|
||||
|
||||
let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap();
|
||||
|
||||
let num_steps: usize = 3;
|
||||
for _ in 0..num_steps {
|
||||
nova.prove_step().unwrap();
|
||||
}
|
||||
assert_eq!(Fr::from(num_steps as u32), nova.i);
|
||||
|
||||
let verifier_params = VerifierParams::<Projective, Projective2> {
|
||||
poseidon_config,
|
||||
r1cs: nova.r1cs,
|
||||
cf_r1cs: nova.cf_r1cs,
|
||||
};
|
||||
NOVA::verify(
|
||||
verifier_params,
|
||||
z_0,
|
||||
nova.z_i,
|
||||
nova.i,
|
||||
(nova.U_i, nova.W_i),
|
||||
(nova.u_i, nova.w_i),
|
||||
(nova.cf_U_i, nova.cf_W_i),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
481
folding-schemes/src/folding/nova/nifs.rs
Normal file
481
folding-schemes/src/folding/nova/nifs.rs
Normal file
@@ -0,0 +1,481 @@
|
||||
use ark_crypto_primitives::sponge::Absorb;
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_std::One;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::{CommittedInstance, Witness};
|
||||
use crate::ccs::r1cs::R1CS;
|
||||
use crate::commitment::CommitmentProver;
|
||||
use crate::transcript::Transcript;
|
||||
use crate::utils::vec::*;
|
||||
use crate::Error;
|
||||
|
||||
/// Implements the Non-Interactive Folding Scheme described in section 4 of
|
||||
/// [Nova](https://eprint.iacr.org/2021/370.pdf)
|
||||
pub struct NIFS<C: CurveGroup, CP: CommitmentProver<C>> {
|
||||
_c: PhantomData<C>,
|
||||
_cp: PhantomData<CP>,
|
||||
}
|
||||
|
||||
impl<C: CurveGroup, CP: CommitmentProver<C>> NIFS<C, CP>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
{
|
||||
// compute_T: compute cross-terms T
|
||||
pub fn compute_T(
|
||||
r1cs: &R1CS<C::ScalarField>,
|
||||
u1: C::ScalarField,
|
||||
u2: C::ScalarField,
|
||||
z1: &[C::ScalarField],
|
||||
z2: &[C::ScalarField],
|
||||
) -> Result<Vec<C::ScalarField>, Error> {
|
||||
let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone());
|
||||
|
||||
// this is parallelizable (for the future)
|
||||
let Az1 = mat_vec_mul_sparse(&A, z1)?;
|
||||
let Bz1 = mat_vec_mul_sparse(&B, z1)?;
|
||||
let Cz1 = mat_vec_mul_sparse(&C, z1)?;
|
||||
let Az2 = mat_vec_mul_sparse(&A, z2)?;
|
||||
let Bz2 = mat_vec_mul_sparse(&B, z2)?;
|
||||
let Cz2 = mat_vec_mul_sparse(&C, z2)?;
|
||||
|
||||
let Az1_Bz2 = hadamard(&Az1, &Bz2)?;
|
||||
let Az2_Bz1 = hadamard(&Az2, &Bz1)?;
|
||||
let u1Cz2 = vec_scalar_mul(&Cz2, &u1);
|
||||
let u2Cz1 = vec_scalar_mul(&Cz1, &u2);
|
||||
|
||||
vec_sub(&vec_sub(&vec_add(&Az1_Bz2, &Az2_Bz1)?, &u1Cz2)?, &u2Cz1)
|
||||
}
|
||||
|
||||
pub fn fold_witness(
|
||||
r: C::ScalarField,
|
||||
w1: &Witness<C>,
|
||||
w2: &Witness<C>,
|
||||
T: &[C::ScalarField],
|
||||
rT: C::ScalarField,
|
||||
) -> Result<Witness<C>, Error> {
|
||||
let r2 = r * r;
|
||||
let E: Vec<C::ScalarField> = vec_add(
|
||||
&vec_add(&w1.E, &vec_scalar_mul(T, &r))?,
|
||||
&vec_scalar_mul(&w2.E, &r2),
|
||||
)?;
|
||||
let rE = w1.rE + r * rT + r2 * w2.rE;
|
||||
let W: Vec<C::ScalarField> = w1.W.iter().zip(&w2.W).map(|(a, b)| *a + (r * b)).collect();
|
||||
|
||||
let rW = w1.rW + r * w2.rW;
|
||||
Ok(Witness::<C> { E, rE, W, rW })
|
||||
}
|
||||
|
||||
pub fn fold_committed_instance(
|
||||
r: C::ScalarField,
|
||||
ci1: &CommittedInstance<C>,
|
||||
ci2: &CommittedInstance<C>,
|
||||
cmT: &C,
|
||||
) -> CommittedInstance<C> {
|
||||
let r2 = r * r;
|
||||
let cmE = ci1.cmE + cmT.mul(r) + ci2.cmE.mul(r2);
|
||||
let u = ci1.u + r * ci2.u;
|
||||
let cmW = ci1.cmW + ci2.cmW.mul(r);
|
||||
let x = ci1
|
||||
.x
|
||||
.iter()
|
||||
.zip(&ci2.x)
|
||||
.map(|(a, b)| *a + (r * b))
|
||||
.collect::<Vec<C::ScalarField>>();
|
||||
|
||||
CommittedInstance::<C> { cmE, u, cmW, x }
|
||||
}
|
||||
|
||||
/// NIFS.P is the consecutive combination of compute_cmT with fold_instances
|
||||
|
||||
/// compute_cmT is part of the NIFS.P logic
|
||||
pub fn compute_cmT(
|
||||
cm_prover_params: &CP::Params,
|
||||
r1cs: &R1CS<C::ScalarField>,
|
||||
w1: &Witness<C>,
|
||||
ci1: &CommittedInstance<C>,
|
||||
w2: &Witness<C>,
|
||||
ci2: &CommittedInstance<C>,
|
||||
) -> Result<(Vec<C::ScalarField>, C), Error> {
|
||||
let z1: Vec<C::ScalarField> = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat();
|
||||
let z2: Vec<C::ScalarField> = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat();
|
||||
|
||||
// compute cross terms
|
||||
let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?;
|
||||
// use r_T=1 since we don't need hiding property for cm(T)
|
||||
let cmT = CP::commit(cm_prover_params, &T, &C::ScalarField::one())?;
|
||||
Ok((T, cmT))
|
||||
}
|
||||
pub fn compute_cyclefold_cmT(
|
||||
cm_prover_params: &CP::Params,
|
||||
r1cs: &R1CS<C::ScalarField>, // R1CS over C2.Fr=C1.Fq (here C=C2)
|
||||
w1: &Witness<C>,
|
||||
ci1: &CommittedInstance<C>,
|
||||
w2: &Witness<C>,
|
||||
ci2: &CommittedInstance<C>,
|
||||
) -> Result<(Vec<C::ScalarField>, C), Error>
|
||||
where
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
let z1: Vec<C::ScalarField> = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat();
|
||||
let z2: Vec<C::ScalarField> = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat();
|
||||
|
||||
// compute cross terms
|
||||
let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?;
|
||||
// use r_T=1 since we don't need hiding property for cm(T)
|
||||
let cmT = CP::commit(cm_prover_params, &T, &C::ScalarField::one())?;
|
||||
Ok((T, cmT))
|
||||
}
|
||||
|
||||
/// fold_instances is part of the NIFS.P logic described in
|
||||
/// [Nova](https://eprint.iacr.org/2021/370.pdf)'s section 4. It returns the folded Committed
|
||||
/// Instances and the Witness.
|
||||
pub fn fold_instances(
|
||||
r: C::ScalarField,
|
||||
w1: &Witness<C>,
|
||||
ci1: &CommittedInstance<C>,
|
||||
w2: &Witness<C>,
|
||||
ci2: &CommittedInstance<C>,
|
||||
T: &[C::ScalarField],
|
||||
cmT: C,
|
||||
) -> Result<(Witness<C>, CommittedInstance<C>), Error> {
|
||||
// fold witness
|
||||
// use r_T=1 since we don't need hiding property for cm(T)
|
||||
let w3 = NIFS::<C, CP>::fold_witness(r, w1, w2, T, C::ScalarField::one())?;
|
||||
|
||||
// fold committed instancs
|
||||
let ci3 = NIFS::<C, CP>::fold_committed_instance(r, ci1, ci2, &cmT);
|
||||
|
||||
Ok((w3, ci3))
|
||||
}
|
||||
|
||||
/// verify implements NIFS.V logic described in [Nova](https://eprint.iacr.org/2021/370.pdf)'s
|
||||
/// section 4. It returns the folded Committed Instance
|
||||
pub fn verify(
|
||||
// r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
|
||||
r: C::ScalarField,
|
||||
ci1: &CommittedInstance<C>,
|
||||
ci2: &CommittedInstance<C>,
|
||||
cmT: &C,
|
||||
) -> CommittedInstance<C> {
|
||||
NIFS::<C, CP>::fold_committed_instance(r, ci1, ci2, cmT)
|
||||
}
|
||||
|
||||
/// Verify commited folded instance (ci) relations. Notice that this method does not open the
|
||||
/// commitments, but just checks that the given committed instances (ci1, ci2) when folded
|
||||
/// result in the folded committed instance (ci3) values.
|
||||
pub fn verify_folded_instance(
|
||||
r: C::ScalarField,
|
||||
ci1: &CommittedInstance<C>,
|
||||
ci2: &CommittedInstance<C>,
|
||||
ci3: &CommittedInstance<C>,
|
||||
cmT: &C,
|
||||
) -> Result<(), Error> {
|
||||
let expected = Self::fold_committed_instance(r, ci1, ci2, cmT);
|
||||
if ci3.cmE != expected.cmE
|
||||
|| ci3.u != expected.u
|
||||
|| ci3.cmW != expected.cmW
|
||||
|| ci3.x != expected.x
|
||||
{
|
||||
return Err(Error::NotSatisfied);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn prove_commitments(
|
||||
tr: &mut impl Transcript<C>,
|
||||
cm_prover_params: &CP::Params,
|
||||
w: &Witness<C>,
|
||||
ci: &CommittedInstance<C>,
|
||||
T: Vec<C::ScalarField>,
|
||||
cmT: &C,
|
||||
) -> Result<[CP::Proof; 3], Error> {
|
||||
let cmE_proof = CP::prove(cm_prover_params, tr, &ci.cmE, &w.E, &w.rE)?;
|
||||
let cmW_proof = CP::prove(cm_prover_params, tr, &ci.cmW, &w.W, &w.rW)?;
|
||||
let cmT_proof = CP::prove(cm_prover_params, tr, cmT, &T, &C::ScalarField::one())?; // cm(T) is committed with rT=1
|
||||
Ok([cmE_proof, cmW_proof, cmT_proof])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ark_crypto_primitives::sponge::poseidon::PoseidonConfig;
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_pallas::{Fr, Projective};
|
||||
use ark_std::{ops::Mul, UniformRand, Zero};
|
||||
|
||||
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
|
||||
use crate::commitment::pedersen::{Params as PedersenParams, Pedersen};
|
||||
use crate::folding::nova::circuits::ChallengeGadget;
|
||||
use crate::folding::nova::traits::NovaR1CS;
|
||||
use crate::transcript::poseidon::{poseidon_test_config, PoseidonTranscript};
|
||||
use crate::utils::vec::vec_scalar_mul;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn prepare_simple_fold_inputs() -> (
|
||||
PedersenParams<Projective>,
|
||||
PoseidonConfig<Fr>,
|
||||
R1CS<Fr>,
|
||||
Witness<Projective>, // w1
|
||||
CommittedInstance<Projective>, // ci1
|
||||
Witness<Projective>, // w2
|
||||
CommittedInstance<Projective>, // ci2
|
||||
Witness<Projective>, // w3
|
||||
CommittedInstance<Projective>, // ci3
|
||||
Vec<Fr>, // T
|
||||
Projective, // cmT
|
||||
Vec<bool>, // r_bits
|
||||
Fr, // r_Fr
|
||||
) {
|
||||
let r1cs = get_test_r1cs();
|
||||
let z1 = get_test_z(3);
|
||||
let z2 = get_test_z(4);
|
||||
let (w1, x1) = r1cs.split_z(&z1);
|
||||
let (w2, x2) = r1cs.split_z(&z2);
|
||||
|
||||
let w1 = Witness::<Projective>::new(w1.clone(), r1cs.A.n_rows);
|
||||
let w2 = Witness::<Projective>::new(w2.clone(), r1cs.A.n_rows);
|
||||
|
||||
let mut rng = ark_std::test_rng();
|
||||
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_cols);
|
||||
|
||||
// compute committed instances
|
||||
let ci1 = w1
|
||||
.commit::<Pedersen<Projective>>(&pedersen_params, x1.clone())
|
||||
.unwrap();
|
||||
let ci2 = w2
|
||||
.commit::<Pedersen<Projective>>(&pedersen_params, x2.clone())
|
||||
.unwrap();
|
||||
|
||||
// NIFS.P
|
||||
let (T, cmT) = NIFS::<Projective, Pedersen<Projective>>::compute_cmT(
|
||||
&pedersen_params,
|
||||
&r1cs,
|
||||
&w1,
|
||||
&ci1,
|
||||
&w2,
|
||||
&ci2,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
|
||||
let r_bits = ChallengeGadget::<Projective>::get_challenge_native(
|
||||
&poseidon_config,
|
||||
ci1.clone(),
|
||||
ci2.clone(),
|
||||
cmT,
|
||||
)
|
||||
.unwrap();
|
||||
let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
|
||||
|
||||
let (w3, ci3) = NIFS::<Projective, Pedersen<Projective>>::fold_instances(
|
||||
r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
(
|
||||
pedersen_params,
|
||||
poseidon_config,
|
||||
r1cs,
|
||||
w1,
|
||||
ci1,
|
||||
w2,
|
||||
ci2,
|
||||
w3,
|
||||
ci3,
|
||||
T,
|
||||
cmT,
|
||||
r_bits,
|
||||
r_Fr,
|
||||
)
|
||||
}
|
||||
|
||||
// fold 2 dummy instances and check that the folded instance holds the relaxed R1CS relation
|
||||
#[test]
|
||||
fn test_nifs_fold_dummy() {
|
||||
let r1cs = get_test_r1cs::<Fr>();
|
||||
let z1 = get_test_z(3);
|
||||
let (w1, x1) = r1cs.split_z(&z1);
|
||||
|
||||
let mut rng = ark_std::test_rng();
|
||||
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_cols);
|
||||
|
||||
// dummy instance, witness and public inputs zeroes
|
||||
let w_dummy = Witness::<Projective>::new(vec![Fr::zero(); w1.len()], r1cs.A.n_rows);
|
||||
let mut u_dummy = w_dummy
|
||||
.commit::<Pedersen<Projective>>(&pedersen_params, vec![Fr::zero(); x1.len()])
|
||||
.unwrap();
|
||||
u_dummy.u = Fr::zero();
|
||||
|
||||
let w_i = w_dummy.clone();
|
||||
let u_i = u_dummy.clone();
|
||||
let W_i = w_dummy.clone();
|
||||
let U_i = u_dummy.clone();
|
||||
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
|
||||
|
||||
let r_Fr = Fr::from(3_u32);
|
||||
|
||||
let (T, cmT) = NIFS::<Projective, Pedersen<Projective>>::compute_cmT(
|
||||
&pedersen_params,
|
||||
&r1cs,
|
||||
&w_i,
|
||||
&u_i,
|
||||
&W_i,
|
||||
&U_i,
|
||||
)
|
||||
.unwrap();
|
||||
let (W_i1, U_i1) = NIFS::<Projective, Pedersen<Projective>>::fold_instances(
|
||||
r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT,
|
||||
)
|
||||
.unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
|
||||
}
|
||||
|
||||
// fold 2 instances into one
|
||||
#[test]
|
||||
fn test_nifs_one_fold() {
|
||||
let (pedersen_params, poseidon_config, r1cs, w1, ci1, w2, ci2, w3, ci3, T, cmT, _, r) =
|
||||
prepare_simple_fold_inputs();
|
||||
|
||||
// NIFS.V
|
||||
let ci3_v = NIFS::<Projective, Pedersen<Projective>>::verify(r, &ci1, &ci2, &cmT);
|
||||
assert_eq!(ci3_v, ci3);
|
||||
|
||||
// check that relations hold for the 2 inputted instances and the folded one
|
||||
r1cs.check_relaxed_instance_relation(&w1, &ci1).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&w2, &ci2).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&w3, &ci3).unwrap();
|
||||
|
||||
// check that folded commitments from folded instance (ci) are equal to folding the
|
||||
// use folded rE, rW to commit w3
|
||||
let ci3_expected = w3
|
||||
.commit::<Pedersen<Projective>>(&pedersen_params, ci3.x.clone())
|
||||
.unwrap();
|
||||
assert_eq!(ci3_expected.cmE, ci3.cmE);
|
||||
assert_eq!(ci3_expected.cmW, ci3.cmW);
|
||||
|
||||
// next equalities should hold since we started from two cmE of zero-vector E's
|
||||
assert_eq!(ci3.cmE, cmT.mul(r));
|
||||
assert_eq!(w3.E, vec_scalar_mul(&T, &r));
|
||||
|
||||
// NIFS.Verify_Folded_Instance:
|
||||
NIFS::<Projective, Pedersen<Projective>>::verify_folded_instance(r, &ci1, &ci2, &ci3, &cmT)
|
||||
.unwrap();
|
||||
|
||||
// init Prover's transcript
|
||||
let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config);
|
||||
// init Verifier's transcript
|
||||
let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config);
|
||||
|
||||
// prove the ci3.cmE, ci3.cmW, cmT commitments
|
||||
let cm_proofs = NIFS::<Projective, Pedersen<Projective>>::prove_commitments(
|
||||
&mut transcript_p,
|
||||
&pedersen_params,
|
||||
&w3,
|
||||
&ci3,
|
||||
T,
|
||||
&cmT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// verify the ci3.cmE, ci3.cmW, cmT commitments
|
||||
assert_eq!(cm_proofs.len(), 3);
|
||||
Pedersen::<Projective>::verify(
|
||||
&pedersen_params,
|
||||
&mut transcript_v,
|
||||
ci3.cmE,
|
||||
cm_proofs[0].clone(),
|
||||
)
|
||||
.unwrap();
|
||||
Pedersen::<Projective>::verify(
|
||||
&pedersen_params,
|
||||
&mut transcript_v,
|
||||
ci3.cmW,
|
||||
cm_proofs[1].clone(),
|
||||
)
|
||||
.unwrap();
|
||||
Pedersen::<Projective>::verify(
|
||||
&pedersen_params,
|
||||
&mut transcript_v,
|
||||
cmT,
|
||||
cm_proofs[2].clone(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nifs_fold_loop() {
|
||||
let r1cs = get_test_r1cs();
|
||||
let z = get_test_z(3);
|
||||
let (w, x) = r1cs.split_z(&z);
|
||||
|
||||
let mut rng = ark_std::test_rng();
|
||||
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_cols);
|
||||
|
||||
// prepare the running instance
|
||||
let mut running_instance_w = Witness::<Projective>::new(w.clone(), r1cs.A.n_rows);
|
||||
let mut running_committed_instance = running_instance_w
|
||||
.commit::<Pedersen<Projective>>(&pedersen_params, x)
|
||||
.unwrap();
|
||||
|
||||
r1cs.check_relaxed_instance_relation(&running_instance_w, &running_committed_instance)
|
||||
.unwrap();
|
||||
|
||||
let num_iters = 10;
|
||||
for i in 0..num_iters {
|
||||
// prepare the incomming instance
|
||||
let incomming_instance_z = get_test_z(i + 4);
|
||||
let (w, x) = r1cs.split_z(&incomming_instance_z);
|
||||
let incomming_instance_w = Witness::<Projective>::new(w.clone(), r1cs.A.n_rows);
|
||||
let incomming_committed_instance = incomming_instance_w
|
||||
.commit::<Pedersen<Projective>>(&pedersen_params, x)
|
||||
.unwrap();
|
||||
r1cs.check_relaxed_instance_relation(
|
||||
&incomming_instance_w,
|
||||
&incomming_committed_instance,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let r = Fr::rand(&mut rng); // folding challenge would come from the RO
|
||||
|
||||
// NIFS.P
|
||||
let (T, cmT) = NIFS::<Projective, Pedersen<Projective>>::compute_cmT(
|
||||
&pedersen_params,
|
||||
&r1cs,
|
||||
&running_instance_w,
|
||||
&running_committed_instance,
|
||||
&incomming_instance_w,
|
||||
&incomming_committed_instance,
|
||||
)
|
||||
.unwrap();
|
||||
let (folded_w, _) = NIFS::<Projective, Pedersen<Projective>>::fold_instances(
|
||||
r,
|
||||
&running_instance_w,
|
||||
&running_committed_instance,
|
||||
&incomming_instance_w,
|
||||
&incomming_committed_instance,
|
||||
&T,
|
||||
cmT,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// NIFS.V
|
||||
let folded_committed_instance = NIFS::<Projective, Pedersen<Projective>>::verify(
|
||||
r,
|
||||
&running_committed_instance,
|
||||
&incomming_committed_instance,
|
||||
&cmT,
|
||||
);
|
||||
|
||||
r1cs.check_relaxed_instance_relation(&folded_w, &folded_committed_instance)
|
||||
.unwrap();
|
||||
|
||||
// set running_instance for next loop iteration
|
||||
running_instance_w = folded_w;
|
||||
running_committed_instance = folded_committed_instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
folding-schemes/src/folding/nova/traits.rs
Normal file
67
folding-schemes/src/folding/nova/traits.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use ark_crypto_primitives::sponge::Absorb;
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_std::{One, Zero};
|
||||
|
||||
use super::{CommittedInstance, Witness};
|
||||
use crate::ccs::r1cs::R1CS;
|
||||
use crate::Error;
|
||||
|
||||
/// NovaR1CS extends R1CS methods with Nova specific methods
|
||||
pub trait NovaR1CS<C: CurveGroup> {
|
||||
/// returns a dummy instance (Witness and CommittedInstance) for the current R1CS structure
|
||||
fn dummy_instance(&self) -> (Witness<C>, CommittedInstance<C>);
|
||||
|
||||
/// checks the R1CS relation (un-relaxed) for the given Witness and CommittedInstance.
|
||||
fn check_instance_relation(
|
||||
&self,
|
||||
W: &Witness<C>,
|
||||
U: &CommittedInstance<C>,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
/// checks the Relaxed R1CS relation (corresponding to the current R1CS) for the given Witness
|
||||
/// and CommittedInstance.
|
||||
fn check_relaxed_instance_relation(
|
||||
&self,
|
||||
W: &Witness<C>,
|
||||
U: &CommittedInstance<C>,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> NovaR1CS<C> for R1CS<C::ScalarField>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
fn dummy_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
|
||||
let w_len = self.A.n_cols - 1 - self.l;
|
||||
let w_dummy = Witness::<C>::new(vec![C::ScalarField::zero(); w_len], self.A.n_rows);
|
||||
let u_dummy = CommittedInstance::<C>::dummy(self.l);
|
||||
(w_dummy, u_dummy)
|
||||
}
|
||||
|
||||
fn check_instance_relation(
|
||||
&self,
|
||||
W: &Witness<C>,
|
||||
U: &CommittedInstance<C>,
|
||||
) -> Result<(), Error> {
|
||||
if U.cmE != C::zero() || U.u != C::ScalarField::one() {
|
||||
return Err(Error::R1CSUnrelaxedFail);
|
||||
}
|
||||
|
||||
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
|
||||
self.check_relation(&Z)
|
||||
}
|
||||
|
||||
fn check_relaxed_instance_relation(
|
||||
&self,
|
||||
W: &Witness<C>,
|
||||
U: &CommittedInstance<C>,
|
||||
) -> Result<(), Error> {
|
||||
let mut rel_r1cs = self.clone().relax();
|
||||
rel_r1cs.u = U.u;
|
||||
rel_r1cs.E = W.E.clone();
|
||||
|
||||
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
|
||||
rel_r1cs.check_relation(&Z)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user