mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-14 01:45:31 +01:00
* Reduce the number of constraints in `AugmentedFCircuit` For the test `folding::nova::tests::test_ivc` Before: 138240 After: 86756 (1.6x improvement) Two notable optimization techniques: 1. Instead of allocating two witness variables `a, b` and enforce their equality by calling `a.conditional_enforce_equal(&b, &cond)`, we can avoid the allocation of `b` and directly set `b = a`. The former might be costly due to the checks in allocation and `conditional_enforce_equal`. See `nova/circuits.rs` for details. 2. Before this commit, `NonNativeFieldVar::to_constraint_field` was majorly called for generating the inputs (preimage) to hash functions. However, it turns out that the underlying conversion strategy (optimized for weight) is not optimal for reducing the length of hash preimage. We can go further by maximizing the number of bits per limb, thereby minimizing the preimage length. See `circuits/nonnative.rs` for details. * Format * Fix clippy warnings * Move the comments to the right position * Cleanup unnecessary code
881 lines
31 KiB
Rust
881 lines
31 KiB
Rust
/// 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, Field, PrimeField, ToConstraintField};
|
|
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
|
|
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::CommitmentScheme;
|
|
use crate::folding::circuits::nonnative::{
|
|
nonnative_affine_to_field_elements, nonnative_field_to_field_elements,
|
|
};
|
|
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) = nonnative_affine_to_field_elements::<C>(self.cmE)?;
|
|
let (cmW_x, cmW_y) = nonnative_affine_to_field_elements::<C>(self.cmW)?;
|
|
|
|
CRH::<C::ScalarField>::evaluate(
|
|
poseidon_config,
|
|
vec![
|
|
vec![i],
|
|
z_0,
|
|
z_i,
|
|
vec![self.u],
|
|
self.x.clone(),
|
|
cmE_x,
|
|
cmE_y,
|
|
cmW_x,
|
|
cmW_y,
|
|
]
|
|
.concat(),
|
|
)
|
|
.map_err(|e| Error::Other(e.to_string()))
|
|
}
|
|
}
|
|
|
|
impl<C: CurveGroup> ToConstraintField<C::BaseField> for CommittedInstance<C>
|
|
where
|
|
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
|
|
{
|
|
fn to_field_elements(&self) -> Option<Vec<C::BaseField>> {
|
|
let u = nonnative_field_to_field_elements(&self.u);
|
|
let x = self
|
|
.x
|
|
.iter()
|
|
.flat_map(nonnative_field_to_field_elements)
|
|
.collect::<Vec<_>>();
|
|
let (cmE_x, cmE_y, cmE_is_inf) = match self.cmE.into_affine().xy() {
|
|
Some((&x, &y)) => (x, y, C::BaseField::zero()),
|
|
None => (
|
|
C::BaseField::zero(),
|
|
C::BaseField::zero(),
|
|
C::BaseField::one(),
|
|
),
|
|
};
|
|
let (cmW_x, cmW_y, cmW_is_inf) = match self.cmW.into_affine().xy() {
|
|
Some((&x, &y)) => (x, y, C::BaseField::zero()),
|
|
None => (
|
|
C::BaseField::zero(),
|
|
C::BaseField::zero(),
|
|
C::BaseField::one(),
|
|
),
|
|
};
|
|
// Concatenate `cmE_is_inf` and `cmW_is_inf` to save constraints for CRHGadget::evaluate in the corresponding circuit
|
|
let is_inf = cmE_is_inf.double() + cmW_is_inf;
|
|
|
|
Some([u, x, vec![cmE_x, cmE_y, cmW_x, cmW_y, is_inf]].concat())
|
|
}
|
|
}
|
|
|
|
impl<C: CurveGroup> CommittedInstance<C>
|
|
where
|
|
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
|
|
{
|
|
/// hash_cyclefold implements the committed instance hash compatible with the gadget implemented in
|
|
/// nova/cyclefold.rs::CycleFoldCommittedInstanceVar.hash.
|
|
/// Returns `H(U_i)`, where `U_i` is the `CommittedInstance` for CycleFold.
|
|
pub fn hash_cyclefold(
|
|
&self,
|
|
poseidon_config: &PoseidonConfig<C::BaseField>,
|
|
) -> Result<C::BaseField, Error> {
|
|
CRH::<C::BaseField>::evaluate(poseidon_config, self.to_field_elements().unwrap())
|
|
.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<CS: CommitmentScheme<C>>(
|
|
&self,
|
|
params: &CS::ProverParams,
|
|
x: Vec<C::ScalarField>,
|
|
) -> Result<CommittedInstance<C>, Error> {
|
|
let mut cmE = C::zero();
|
|
if !is_zero_vec::<C::ScalarField>(&self.E) {
|
|
cmE = CS::commit(params, &self.E, &self.rE)?;
|
|
}
|
|
let cmW = CS::commit(params, &self.W, &self.rW)?;
|
|
Ok(CommittedInstance {
|
|
cmE,
|
|
u: C::ScalarField::one(),
|
|
cmW,
|
|
x,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ProverParams<C1, C2, CS1, CS2>
|
|
where
|
|
C1: CurveGroup,
|
|
C2: CurveGroup,
|
|
CS1: CommitmentScheme<C1>,
|
|
CS2: CommitmentScheme<C2>,
|
|
{
|
|
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
|
|
pub cs_params: CS1::ProverParams,
|
|
pub cf_cs_params: CS2::ProverParams,
|
|
}
|
|
|
|
#[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, CS1, CS2>
|
|
where
|
|
C1: CurveGroup,
|
|
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
|
C2: CurveGroup,
|
|
GC2: CurveVar<C2, CF2<C2>>,
|
|
FC: FCircuit<C1::ScalarField>,
|
|
CS1: CommitmentScheme<C1>,
|
|
CS2: CommitmentScheme<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>,
|
|
/// CommitmentScheme::ProverParams over C1
|
|
pub cs_params: CS1::ProverParams,
|
|
/// CycleFold CommitmentScheme::ProverParams, over C2
|
|
pub cf_cs_params: CS2::ProverParams,
|
|
/// 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, CS1, CS2> FoldingScheme<C1, C2, FC>
|
|
for Nova<C1, GC1, C2, GC2, FC, CS1, CS2>
|
|
where
|
|
C1: CurveGroup,
|
|
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
|
C2: CurveGroup,
|
|
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
|
FC: FCircuit<C1::ScalarField>,
|
|
CS1: CommitmentScheme<C1>,
|
|
CS2: CommitmentScheme<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, CS1, CS2>;
|
|
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.clone())?;
|
|
|
|
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.clone());
|
|
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(),
|
|
cs_params: pp.cs_params.clone(),
|
|
cf_cs_params: pp.cf_cs_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>;
|
|
|
|
if self.i > C1::ScalarField::from_le_bytes_mod_order(&usize::MAX.to_le_bytes()) {
|
|
return Err(Error::MaxStep);
|
|
}
|
|
let mut i_bytes: [u8; 8] = [0; 8];
|
|
i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..8]);
|
|
let i_usize: usize = usize::from_le_bytes(i_bytes);
|
|
|
|
let z_i1 = self.F.step_native(i_usize, self.z_i.clone())?;
|
|
|
|
// compute T and cmT for AugmentedFCircuit
|
|
let (T, cmT) = self.compute_cmT()?;
|
|
|
|
// r_bits is the r used to the RLC of the F' instances
|
|
let r_bits = ChallengeGadget::<C1>::get_challenge_native(
|
|
&self.poseidon_config,
|
|
self.U_i.clone(),
|
|
self.u_i.clone(),
|
|
cmT,
|
|
)?;
|
|
let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
|
|
.ok_or(Error::OutOfBounds)?;
|
|
let r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&r_bits))
|
|
.ok_or(Error::OutOfBounds)?;
|
|
|
|
// fold Nova instances
|
|
let (W_i1, U_i1): (Witness<C1>, CommittedInstance<C1>) = NIFS::<C1, CS1>::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[0] = 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(),
|
|
)?;
|
|
// u_{i+1}.x[1] = H(cf_U_{i+1})
|
|
let cf_u_i1_x: C1::ScalarField;
|
|
|
|
if self.i == C1::ScalarField::zero() {
|
|
cf_u_i1_x = self.cf_U_i.hash_cyclefold(&self.poseidon_config)?;
|
|
// 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
|
|
i_usize: Some(0),
|
|
z_0: Some(self.z_0.clone()), // = z_i
|
|
z_i: Some(self.z_i.clone()),
|
|
u_i_cmW: Some(self.u_i.cmW), // = dummy
|
|
U_i: Some(self.U_i.clone()), // = dummy
|
|
U_i1_cmE: Some(U_i1.cmE),
|
|
U_i1_cmW: Some(U_i1.cmW),
|
|
cmT: Some(cmT),
|
|
F: self.F.clone(),
|
|
x: Some(u_i1_x),
|
|
cf1_u_i_cmW: None,
|
|
cf2_u_i_cmW: None,
|
|
cf_U_i: None,
|
|
cf1_cmT: None,
|
|
cf2_cmT: None,
|
|
cf_x: Some(cf_u_i1_x),
|
|
};
|
|
|
|
#[cfg(test)]
|
|
NIFS::<C1, CS1>::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
|
|
// cyclefold circuit for cmW
|
|
let cfW_u_i_x = [
|
|
vec![r_Fq],
|
|
get_cm_coordinates(&self.U_i.cmW),
|
|
get_cm_coordinates(&self.u_i.cmW),
|
|
get_cm_coordinates(&U_i1.cmW),
|
|
]
|
|
.concat();
|
|
// cyclefold circuit for cmE
|
|
let cfE_u_i_x = [
|
|
vec![r_Fq],
|
|
get_cm_coordinates(&self.U_i.cmE),
|
|
get_cm_coordinates(&cmT),
|
|
get_cm_coordinates(&U_i1.cmE),
|
|
]
|
|
.concat();
|
|
|
|
let cfW_circuit = CycleFoldCircuit::<C1, GC1> {
|
|
_gc: PhantomData,
|
|
r_bits: Some(r_bits.clone()),
|
|
p1: Some(self.U_i.clone().cmW),
|
|
p2: Some(self.u_i.clone().cmW),
|
|
p3: Some(U_i1.clone().cmW),
|
|
x: Some(cfW_u_i_x.clone()),
|
|
};
|
|
let cfE_circuit = CycleFoldCircuit::<C1, GC1> {
|
|
_gc: PhantomData,
|
|
r_bits: Some(r_bits.clone()),
|
|
p1: Some(self.U_i.clone().cmE),
|
|
p2: Some(cmT),
|
|
p3: Some(U_i1.clone().cmE),
|
|
x: Some(cfE_u_i_x.clone()),
|
|
};
|
|
|
|
// fold self.cf_U_i + cfW_U -> folded running with cfW
|
|
let (_cfW_w_i, cfW_u_i, cfW_W_i1, cfW_U_i1, cfW_cmT, _) = self.fold_cyclefold_circuit(
|
|
self.cf_W_i.clone(), // CycleFold running instance witness
|
|
self.cf_U_i.clone(), // CycleFold running instance
|
|
cfW_u_i_x,
|
|
cfW_circuit,
|
|
)?;
|
|
// fold [the output from folding self.cf_U_i + cfW_U] + cfE_U = folded_running_with_cfW + cfE
|
|
let (_cfE_w_i, cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) =
|
|
self.fold_cyclefold_circuit(cfW_W_i1, cfW_U_i1.clone(), cfE_u_i_x, cfE_circuit)?;
|
|
|
|
cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config)?;
|
|
|
|
augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
|
|
_gc2: PhantomData,
|
|
poseidon_config: self.poseidon_config.clone(),
|
|
i: Some(self.i),
|
|
i_usize: Some(i_usize),
|
|
z_0: Some(self.z_0.clone()),
|
|
z_i: Some(self.z_i.clone()),
|
|
u_i_cmW: Some(self.u_i.cmW),
|
|
U_i: Some(self.U_i.clone()),
|
|
U_i1_cmE: Some(U_i1.cmE),
|
|
U_i1_cmW: Some(U_i1.cmW),
|
|
cmT: Some(cmT),
|
|
F: self.F.clone(),
|
|
x: Some(u_i1_x),
|
|
// cyclefold values
|
|
cf1_u_i_cmW: Some(cfW_u_i.cmW),
|
|
cf2_u_i_cmW: Some(cfE_u_i.cmW),
|
|
cf_U_i: Some(self.cf_U_i.clone()),
|
|
cf1_cmT: Some(cfW_cmT),
|
|
cf2_cmT: Some(cf_cmT),
|
|
cf_x: Some(cf_u_i1_x),
|
|
};
|
|
|
|
self.cf_W_i = cf_W_i1.clone();
|
|
self.cf_U_i = cf_U_i1.clone();
|
|
|
|
#[cfg(test)]
|
|
{
|
|
self.cf_r1cs.check_instance_relation(&_cfW_w_i, &cfW_u_i)?;
|
|
self.cf_r1cs.check_instance_relation(&_cfE_w_i, &cfE_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())?;
|
|
|
|
#[cfg(test)]
|
|
assert!(cs.is_satisfied().unwrap());
|
|
|
|
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 || x_i1[1] != cf_u_i1_x {
|
|
return Err(Error::NotEqual);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
if x_i1.len() != 2 {
|
|
return Err(Error::NotExpectedLength(x_i1.len(), 2));
|
|
}
|
|
|
|
// set values for next iteration
|
|
self.i += C1::ScalarField::one();
|
|
self.z_i = z_i1;
|
|
self.w_i = Witness::<C1>::new(w_i1, self.r1cs.A.n_rows);
|
|
self.u_i = self.w_i.commit::<CS1>(&self.cs_params, x_i1)?;
|
|
self.W_i = W_i1;
|
|
self.U_i = U_i1;
|
|
|
|
#[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,
|
|
incoming_instance: Self::CommittedInstanceWithWitness,
|
|
cyclefold_instance: Self::CFCommittedInstanceWithWitness,
|
|
) -> Result<(), Error> {
|
|
let (U_i, W_i) = running_instance;
|
|
let (u_i, w_i) = incoming_instance;
|
|
let (cf_U_i, cf_W_i) = cyclefold_instance;
|
|
|
|
if u_i.x.len() != 2 || U_i.x.len() != 2 {
|
|
return Err(Error::IVCVerificationFail);
|
|
}
|
|
|
|
// check that u_i's output points to the running instance
|
|
// u_i.X[0] == H(i, z_0, z_i, U_i)
|
|
let expected_u_i_x = U_i.hash(&vp.poseidon_config, num_steps, z_0, z_i.clone())?;
|
|
if expected_u_i_x != u_i.x[0] {
|
|
return Err(Error::IVCVerificationFail);
|
|
}
|
|
// u_i.X[1] == H(cf_U_i)
|
|
let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&vp.poseidon_config)?;
|
|
if expected_cf_u_i_x != u_i.x[1] {
|
|
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, CS1, CS2> Nova<C1, GC1, C2, GC2, FC, CS1, CS2>
|
|
where
|
|
C1: CurveGroup,
|
|
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
|
C2: CurveGroup,
|
|
GC2: CurveVar<C2, CF2<C2>>,
|
|
FC: FCircuit<C1::ScalarField>,
|
|
CS1: CommitmentScheme<C1>,
|
|
CS2: CommitmentScheme<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, CS1>::compute_cmT(
|
|
&self.cs_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>,
|
|
cf_W_i: &Witness<C2>,
|
|
cf_U_i: &CommittedInstance<C2>,
|
|
) -> Result<(Vec<C2::ScalarField>, C2), Error> {
|
|
NIFS::<C2, CS2>::compute_cyclefold_cmT(
|
|
&self.cf_cs_params,
|
|
&self.cf_r1cs,
|
|
cf_w_i,
|
|
cf_u_i,
|
|
cf_W_i,
|
|
cf_U_i,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<C1, GC1, C2, GC2, FC, CS1, CS2> Nova<C1, GC1, C2, GC2, FC, CS1, CS2>
|
|
where
|
|
C1: CurveGroup,
|
|
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
|
C2: CurveGroup,
|
|
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
|
FC: FCircuit<C1::ScalarField>,
|
|
CS1: CommitmentScheme<C1>,
|
|
CS2: CommitmentScheme<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>,
|
|
{
|
|
// folds the given cyclefold circuit and its instances
|
|
#[allow(clippy::type_complexity)]
|
|
fn fold_cyclefold_circuit(
|
|
&self,
|
|
cf_W_i: Witness<C2>, // witness of the running instance
|
|
cf_U_i: CommittedInstance<C2>, // running instance
|
|
cf_u_i_x: Vec<C2::ScalarField>,
|
|
cf_circuit: CycleFoldCircuit<C1, GC1>,
|
|
) -> Result<
|
|
(
|
|
Witness<C2>,
|
|
CommittedInstance<C2>, // u_i
|
|
Witness<C2>, // W_i1
|
|
CommittedInstance<C2>, // U_i1
|
|
C2, // cmT
|
|
C2::ScalarField, // r_Fq
|
|
),
|
|
Error,
|
|
> {
|
|
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::<CS2>(&self.cf_cs_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, &cf_W_i, &cf_U_i)?;
|
|
|
|
let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_native(
|
|
&self.poseidon_config,
|
|
cf_U_i.clone(),
|
|
cf_u_i.clone(),
|
|
cf_cmT,
|
|
)?;
|
|
let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits))
|
|
.ok_or(Error::OutOfBounds)?;
|
|
|
|
let (cf_W_i1, cf_U_i1) = NIFS::<C2, CS2>::fold_instances(
|
|
cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT,
|
|
)?;
|
|
Ok((cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r_Fq))
|
|
}
|
|
}
|
|
|
|
/// 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>> + ToConstraintFieldGadget<CF2<C1>>,
|
|
C2: CurveGroup,
|
|
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<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_cs_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>> + ToConstraintFieldGadget<CF2<C1>>,
|
|
C2: CurveGroup,
|
|
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<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))
|
|
}
|
|
|
|
/// returns the coordinates of a commitment point. This is compatible with the arkworks
|
|
/// GC.to_constraint_field()[..2]
|
|
pub(crate) fn get_cm_coordinates<C: CurveGroup>(cm: &C) -> Vec<C::BaseField> {
|
|
let zero = (&C::BaseField::zero(), &C::BaseField::zero());
|
|
let cm = cm.into_affine();
|
|
let (cm_x, cm_y) = cm.xy().unwrap_or(zero);
|
|
vec![*cm_x, *cm_y]
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod tests {
|
|
use super::*;
|
|
use crate::commitment::kzg::{ProverKey as KZGProverKey, KZG};
|
|
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
|
|
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
|
|
use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey;
|
|
|
|
use crate::commitment::pedersen::Pedersen;
|
|
use crate::frontend::tests::CubicFCircuit;
|
|
use crate::transcript::poseidon::poseidon_test_config;
|
|
|
|
/// This test tests the Nova+CycleFold IVC, and by consequence it is also testing the
|
|
/// AugmentedFCircuit
|
|
#[test]
|
|
fn test_ivc() {
|
|
let mut rng = ark_std::test_rng();
|
|
let poseidon_config = poseidon_test_config::<Fr>();
|
|
|
|
let F_circuit = CubicFCircuit::<Fr>::new(());
|
|
|
|
let (cs_len, cf_cs_len) =
|
|
get_cs_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>(
|
|
&poseidon_config,
|
|
F_circuit,
|
|
)
|
|
.unwrap();
|
|
let (kzg_pk, _): (KZGProverKey<Projective>, KZGVerifierKey<Bn254>) =
|
|
KZG::<Bn254>::setup(&mut rng, cs_len).unwrap();
|
|
let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, cs_len).unwrap();
|
|
let (cf_pedersen_params, _) = Pedersen::<Projective2>::setup(&mut rng, cf_cs_len).unwrap();
|
|
|
|
// run the test using Pedersen commitments on both sides of the curve cycle
|
|
test_ivc_opt::<Pedersen<Projective>, Pedersen<Projective2>>(
|
|
poseidon_config.clone(),
|
|
pedersen_params,
|
|
cf_pedersen_params.clone(),
|
|
F_circuit,
|
|
);
|
|
// run the test using KZG for the commitments on the main curve, and Pedersen for the
|
|
// commitments on the secondary curve
|
|
test_ivc_opt::<KZG<Bn254>, Pedersen<Projective2>>(
|
|
poseidon_config,
|
|
kzg_pk,
|
|
cf_pedersen_params,
|
|
F_circuit,
|
|
);
|
|
}
|
|
|
|
// test_ivc allowing to choose the CommitmentSchemes
|
|
fn test_ivc_opt<CS1: CommitmentScheme<Projective>, CS2: CommitmentScheme<Projective2>>(
|
|
poseidon_config: PoseidonConfig<Fr>,
|
|
cs_params: CS1::ProverParams,
|
|
cf_cs_params: CS2::ProverParams,
|
|
F_circuit: CubicFCircuit<Fr>,
|
|
) {
|
|
type NOVA<CS1, CS2> =
|
|
Nova<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2>;
|
|
|
|
let prover_params = ProverParams::<Projective, Projective2, CS1, CS2> {
|
|
poseidon_config: poseidon_config.clone(),
|
|
cs_params,
|
|
cf_cs_params,
|
|
};
|
|
|
|
let z_0 = vec![Fr::from(3_u32)];
|
|
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.clone().r1cs,
|
|
cf_r1cs: nova.clone().cf_r1cs,
|
|
};
|
|
let (running_instance, incoming_instance, cyclefold_instance) = nova.instances();
|
|
NOVA::<CS1, CS2>::verify(
|
|
verifier_params,
|
|
z_0,
|
|
nova.z_i,
|
|
nova.i,
|
|
running_instance,
|
|
incoming_instance,
|
|
cyclefold_instance,
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|