mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-09 23:41:30 +01:00
Feature/nova ivc (#36)
* Implement Nova IVC's new & prove_step methods Implement Nova IVC's new & prove_step methods (without CycleFold part yet) * transcript.absorb_point err handling, and update C.xy() usage * add transcript usage to IVC prove, add NovaTranscript trait extending Transcript trait, refactor NIFS.P to allow absorbing in transcript inbetween * Implement Nova's IVC.V method (without CycleFold part yet) * clippy lints * move challenge r computation in-circuit * reuse computed points with coordinates over CF (non-native) to save constraints in AugmentedFCircuit (constraint count went down ~6k) * rm 128 bit constant * add params to Errors * Updates from review suggestions. Additionally refactored nova/nifs fold, and rm transcript from nova/IVC. - Updates from PR suggestions - Additionally updated: - in nova/nifs.rs: reuse folded_committed_instance for verify_folded_instance, computationally is the same, but reusing the same code so avoiding duplication and having an error on one of the two versions. - in nova/ivc.rs: remove transcript from IVC (not needed, it uses the RO)
This commit is contained in:
@@ -1 +1 @@
|
|||||||
1.71.1
|
1.73.0
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
pub const N_BITS_CHALLENGE: usize = 250;
|
// used for committed instances hash, so when going to the other curve of the cycle it does not
|
||||||
|
// overflow the scalar field
|
||||||
|
pub const N_BITS_HASH: usize = 250;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub fn vec_add<F: PrimeField>(
|
|||||||
b: &Vec<FpVar<F>>,
|
b: &Vec<FpVar<F>>,
|
||||||
) -> Result<Vec<FpVar<F>>, Error> {
|
) -> Result<Vec<FpVar<F>>, Error> {
|
||||||
if a.len() != b.len() {
|
if a.len() != b.len() {
|
||||||
return Err(Error::NotSameLength);
|
return Err(Error::NotSameLength(a.len(), b.len()));
|
||||||
}
|
}
|
||||||
let mut r: Vec<FpVar<F>> = vec![FpVar::<F>::zero(); a.len()];
|
let mut r: Vec<FpVar<F>> = vec![FpVar::<F>::zero(); a.len()];
|
||||||
for i in 0..a.len() {
|
for i in 0..a.len() {
|
||||||
@@ -68,7 +68,7 @@ pub fn hadamard<F: PrimeField>(
|
|||||||
b: &Vec<FpVar<F>>,
|
b: &Vec<FpVar<F>>,
|
||||||
) -> Result<Vec<FpVar<F>>, Error> {
|
) -> Result<Vec<FpVar<F>>, Error> {
|
||||||
if a.len() != b.len() {
|
if a.len() != b.len() {
|
||||||
return Err(Error::NotSameLength);
|
return Err(Error::NotSameLength(a.len(), b.len()));
|
||||||
}
|
}
|
||||||
let mut r: Vec<FpVar<F>> = vec![FpVar::<F>::zero(); a.len()];
|
let mut r: Vec<FpVar<F>> = vec![FpVar::<F>::zero(); a.len()];
|
||||||
for i in 0..a.len() {
|
for i in 0..a.len() {
|
||||||
|
|||||||
@@ -3,22 +3,23 @@ use ark_ff::PrimeField;
|
|||||||
use ark_r1cs_std::fields::nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar};
|
use ark_r1cs_std::fields::nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar};
|
||||||
use ark_r1cs_std::{
|
use ark_r1cs_std::{
|
||||||
alloc::{AllocVar, AllocationMode},
|
alloc::{AllocVar, AllocationMode},
|
||||||
fields::nonnative::NonNativeFieldVar,
|
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
|
||||||
|
ToConstraintFieldGadget,
|
||||||
};
|
};
|
||||||
use ark_relations::r1cs::{Namespace, SynthesisError};
|
use ark_relations::r1cs::{Namespace, SynthesisError};
|
||||||
use ark_std::{One, Zero};
|
use ark_std::{One, Zero};
|
||||||
use core::borrow::Borrow;
|
use core::borrow::Borrow;
|
||||||
|
|
||||||
/// NonNativeAffineVar represents an elliptic curve point in Affine represenation in the non-native
|
/// NonNativeAffineVar represents an elliptic curve point in Affine represenation in the non-native
|
||||||
/// field. It is not intended to perform operations, but just to contain the affine coordinates in
|
/// field, over the constraint field. It is not intended to perform operations, but just to contain
|
||||||
/// order to perform hash operations of the point.
|
/// the affine coordinates in order to perform hash operations of the point.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NonNativeAffineVar<F: PrimeField, CF: PrimeField> {
|
pub struct NonNativeAffineVar<F: PrimeField> {
|
||||||
pub x: NonNativeFieldVar<F, CF>,
|
pub x: Vec<FpVar<F>>,
|
||||||
pub y: NonNativeFieldVar<F, CF>,
|
pub y: Vec<FpVar<F>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> AllocVar<C, C::ScalarField> for NonNativeAffineVar<C::BaseField, C::ScalarField>
|
impl<C> AllocVar<C, C::ScalarField> for NonNativeAffineVar<C::ScalarField>
|
||||||
where
|
where
|
||||||
C: CurveGroup,
|
C: CurveGroup,
|
||||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||||
@@ -32,30 +33,23 @@ where
|
|||||||
let cs = cs.into();
|
let cs = cs.into();
|
||||||
|
|
||||||
let affine = val.borrow().into_affine();
|
let affine = val.borrow().into_affine();
|
||||||
if affine.is_zero() {
|
let xy_obj = &affine.xy();
|
||||||
let x = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
|
let mut xy = (&C::BaseField::zero(), &C::BaseField::one());
|
||||||
cs.clone(),
|
if xy_obj.is_some() {
|
||||||
|| Ok(C::BaseField::zero()),
|
xy = xy_obj.unwrap();
|
||||||
mode,
|
|
||||||
)?;
|
|
||||||
let y = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
|
|
||||||
cs.clone(),
|
|
||||||
|| Ok(C::BaseField::one()),
|
|
||||||
mode,
|
|
||||||
)?;
|
|
||||||
return Ok(Self { x, y });
|
|
||||||
}
|
}
|
||||||
let xy = affine.xy().unwrap();
|
|
||||||
let x = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
|
let x = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
|
||||||
cs.clone(),
|
cs.clone(),
|
||||||
|| Ok(xy.0),
|
|| Ok(xy.0),
|
||||||
mode,
|
mode,
|
||||||
)?;
|
)?
|
||||||
|
.to_constraint_field()?;
|
||||||
let y = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
|
let y = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
|
||||||
cs.clone(),
|
cs.clone(),
|
||||||
|| Ok(xy.1),
|
|| Ok(xy.1),
|
||||||
mode,
|
mode,
|
||||||
)?;
|
)?
|
||||||
|
.to_constraint_field()?;
|
||||||
|
|
||||||
Ok(Self { x, y })
|
Ok(Self { x, y })
|
||||||
})
|
})
|
||||||
@@ -101,7 +95,7 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ark_pallas::{Fq, Fr, Projective};
|
use ark_pallas::{Fr, Projective};
|
||||||
use ark_r1cs_std::alloc::AllocVar;
|
use ark_r1cs_std::alloc::AllocVar;
|
||||||
use ark_relations::r1cs::ConstraintSystem;
|
use ark_relations::r1cs::ConstraintSystem;
|
||||||
use ark_std::Zero;
|
use ark_std::Zero;
|
||||||
@@ -112,6 +106,6 @@ mod tests {
|
|||||||
|
|
||||||
// dealing with the 'zero' point should not panic when doing the unwrap
|
// dealing with the 'zero' point should not panic when doing the unwrap
|
||||||
let p = Projective::zero();
|
let p = Projective::zero();
|
||||||
NonNativeAffineVar::<Fq, Fr>::new_witness(cs.clone(), || Ok(p)).unwrap();
|
NonNativeAffineVar::<Fr>::new_witness(cs.clone(), || Ok(p)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use ark_crypto_primitives::crh::{
|
use ark_crypto_primitives::crh::{
|
||||||
poseidon::constraints::{CRHGadget, CRHParametersVar},
|
poseidon::constraints::{CRHGadget, CRHParametersVar},
|
||||||
CRHSchemeGadget,
|
poseidon::CRH,
|
||||||
|
CRHScheme, CRHSchemeGadget,
|
||||||
};
|
};
|
||||||
use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
|
use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
|
||||||
use ark_ec::{AffineRepr, CurveGroup, Group};
|
use ark_ec::{AffineRepr, CurveGroup, Group};
|
||||||
@@ -12,7 +13,6 @@ use ark_r1cs_std::{
|
|||||||
fields::{fp::FpVar, FieldVar},
|
fields::{fp::FpVar, FieldVar},
|
||||||
groups::GroupOpsBounds,
|
groups::GroupOpsBounds,
|
||||||
prelude::CurveVar,
|
prelude::CurveVar,
|
||||||
ToConstraintFieldGadget,
|
|
||||||
};
|
};
|
||||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
|
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
|
||||||
use ark_std::fmt::Debug;
|
use ark_std::fmt::Debug;
|
||||||
@@ -20,7 +20,10 @@ use ark_std::Zero;
|
|||||||
use core::{borrow::Borrow, marker::PhantomData};
|
use core::{borrow::Borrow, marker::PhantomData};
|
||||||
|
|
||||||
use super::CommittedInstance;
|
use super::CommittedInstance;
|
||||||
use crate::folding::circuits::{cyclefold::ECRLC, nonnative::NonNativeAffineVar};
|
use crate::folding::circuits::{
|
||||||
|
cyclefold::ECRLC,
|
||||||
|
nonnative::{point_to_nonnative_limbs, NonNativeAffineVar},
|
||||||
|
};
|
||||||
|
|
||||||
/// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr, where
|
/// 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.
|
/// E1 is the main curve where we do the folding.
|
||||||
@@ -36,8 +39,8 @@ pub type CF2<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
|
|||||||
pub struct CommittedInstanceVar<C: CurveGroup> {
|
pub struct CommittedInstanceVar<C: CurveGroup> {
|
||||||
u: FpVar<C::ScalarField>,
|
u: FpVar<C::ScalarField>,
|
||||||
x: Vec<FpVar<C::ScalarField>>,
|
x: Vec<FpVar<C::ScalarField>>,
|
||||||
cmE: NonNativeAffineVar<CF2<C>, C::ScalarField>,
|
cmE: NonNativeAffineVar<C::ScalarField>,
|
||||||
cmW: NonNativeAffineVar<CF2<C>, C::ScalarField>,
|
cmW: NonNativeAffineVar<C::ScalarField>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceVar<C>
|
impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceVar<C>
|
||||||
@@ -57,12 +60,12 @@ where
|
|||||||
let x: Vec<FpVar<C::ScalarField>> =
|
let x: Vec<FpVar<C::ScalarField>> =
|
||||||
Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?;
|
Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?;
|
||||||
|
|
||||||
let cmE = NonNativeAffineVar::<CF2<C>, C::ScalarField>::new_variable(
|
let cmE = NonNativeAffineVar::<C::ScalarField>::new_variable(
|
||||||
cs.clone(),
|
cs.clone(),
|
||||||
|| Ok(val.borrow().cmE),
|
|| Ok(val.borrow().cmE),
|
||||||
mode,
|
mode,
|
||||||
)?;
|
)?;
|
||||||
let cmW = NonNativeAffineVar::<CF2<C>, C::ScalarField>::new_variable(
|
let cmW = NonNativeAffineVar::<C::ScalarField>::new_variable(
|
||||||
cs.clone(),
|
cs.clone(),
|
||||||
|| Ok(val.borrow().cmW),
|
|| Ok(val.borrow().cmW),
|
||||||
mode,
|
mode,
|
||||||
@@ -95,10 +98,10 @@ where
|
|||||||
z_i,
|
z_i,
|
||||||
vec![self.u],
|
vec![self.u],
|
||||||
self.x,
|
self.x,
|
||||||
self.cmE.x.to_constraint_field()?,
|
self.cmE.x,
|
||||||
self.cmE.y.to_constraint_field()?,
|
self.cmE.y,
|
||||||
self.cmW.x.to_constraint_field()?,
|
self.cmW.x,
|
||||||
self.cmW.y.to_constraint_field()?,
|
self.cmW.y,
|
||||||
]
|
]
|
||||||
.concat();
|
.concat();
|
||||||
CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)
|
CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)
|
||||||
@@ -211,6 +214,9 @@ where
|
|||||||
/// FCircuit defines the trait of the circuit of the F function, which is the one being executed
|
/// FCircuit defines the trait of the circuit of the F function, which is the one being executed
|
||||||
/// inside the agmented F' function.
|
/// inside the agmented F' function.
|
||||||
pub trait FCircuit<F: PrimeField>: Clone + Copy + Debug {
|
pub trait FCircuit<F: PrimeField>: Clone + Copy + Debug {
|
||||||
|
/// returns a new FCircuit instance
|
||||||
|
fn new() -> Self;
|
||||||
|
|
||||||
/// computes the next state values in place, assigning z_{i+1} into z_i, and
|
/// computes the next state values in place, assigning z_{i+1} into z_i, and
|
||||||
/// computing the new z_i
|
/// computing the new z_i
|
||||||
fn step_native(
|
fn step_native(
|
||||||
@@ -242,14 +248,12 @@ pub struct AugmentedFCircuit<C: CurveGroup, FC: FCircuit<CF1<C>>> {
|
|||||||
pub U_i: Option<CommittedInstance<C>>,
|
pub U_i: Option<CommittedInstance<C>>,
|
||||||
pub U_i1: Option<CommittedInstance<C>>,
|
pub U_i1: Option<CommittedInstance<C>>,
|
||||||
pub cmT: Option<C>,
|
pub cmT: Option<C>,
|
||||||
pub r: Option<C::ScalarField>, // This will not be an input and derived from a hash internally in the circuit (poseidon transcript)
|
|
||||||
pub F: FC, // F circuit
|
pub F: FC, // F circuit
|
||||||
pub x: Option<CF1<C>>, // public inputs (u_{i+1}.x)
|
pub x: Option<CF1<C>>, // public inputs (u_{i+1}.x)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> AugmentedFCircuit<C, FC> {
|
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> AugmentedFCircuit<C, FC> {
|
||||||
#[allow(dead_code)] // TMP while IVC does not use this method
|
pub fn empty(poseidon_config: &PoseidonConfig<CF1<C>>, F_circuit: FC) -> Self {
|
||||||
fn empty(poseidon_config: &PoseidonConfig<CF1<C>>, F_circuit: FC) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
poseidon_config: poseidon_config.clone(),
|
poseidon_config: poseidon_config.clone(),
|
||||||
i: None,
|
i: None,
|
||||||
@@ -259,13 +263,77 @@ impl<C: CurveGroup, FC: FCircuit<CF1<C>>> AugmentedFCircuit<C, FC> {
|
|||||||
U_i: None,
|
U_i: None,
|
||||||
U_i1: None,
|
U_i1: None,
|
||||||
cmT: None,
|
cmT: None,
|
||||||
r: None,
|
|
||||||
F: F_circuit,
|
F: F_circuit,
|
||||||
x: None,
|
x: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> AugmentedFCircuit<C, FC>
|
||||||
|
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<C::ScalarField, 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 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();
|
||||||
|
Ok(CRH::<C::ScalarField>::evaluate(poseidon_config, input).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_challenge(
|
||||||
|
crh_params: &CRHParametersVar<C::ScalarField>,
|
||||||
|
u_i: CommittedInstanceVar<C>,
|
||||||
|
U_i: CommittedInstanceVar<C>,
|
||||||
|
cmT: NonNativeAffineVar<C::ScalarField>,
|
||||||
|
) -> Result<FpVar<C::ScalarField>, SynthesisError> {
|
||||||
|
let input = 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();
|
||||||
|
CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> ConstraintSynthesizer<CF1<C>> for AugmentedFCircuit<C, FC>
|
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> ConstraintSynthesizer<CF1<C>> for AugmentedFCircuit<C, FC>
|
||||||
where
|
where
|
||||||
C: CurveGroup,
|
C: CurveGroup,
|
||||||
@@ -282,15 +350,7 @@ where
|
|||||||
Ok(self.z_i.unwrap_or_else(|| vec![CF1::<C>::zero()]))
|
Ok(self.z_i.unwrap_or_else(|| vec![CF1::<C>::zero()]))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// get z_{i+1} from the F circuit
|
let u_dummy_native = CommittedInstance::<C>::dummy(1);
|
||||||
let z_i1 = self.F.generate_step_constraints(cs.clone(), z_i.clone())?;
|
|
||||||
|
|
||||||
let u_dummy_native = CommittedInstance {
|
|
||||||
cmE: C::zero(),
|
|
||||||
u: C::ScalarField::zero(),
|
|
||||||
cmW: C::zero(),
|
|
||||||
x: vec![CF1::<C>::zero()],
|
|
||||||
};
|
|
||||||
|
|
||||||
let u_dummy =
|
let u_dummy =
|
||||||
CommittedInstanceVar::<C>::new_witness(cs.clone(), || Ok(u_dummy_native.clone()))?;
|
CommittedInstanceVar::<C>::new_witness(cs.clone(), || Ok(u_dummy_native.clone()))?;
|
||||||
@@ -303,16 +363,17 @@ where
|
|||||||
let U_i1 = CommittedInstanceVar::<C>::new_witness(cs.clone(), || {
|
let U_i1 = CommittedInstanceVar::<C>::new_witness(cs.clone(), || {
|
||||||
Ok(self.U_i1.unwrap_or_else(|| u_dummy_native.clone()))
|
Ok(self.U_i1.unwrap_or_else(|| u_dummy_native.clone()))
|
||||||
})?;
|
})?;
|
||||||
let _cmT =
|
let cmT =
|
||||||
NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C::zero)))?;
|
NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C::zero)))?;
|
||||||
let r =
|
|
||||||
FpVar::<CF1<C>>::new_witness(cs.clone(), || Ok(self.r.unwrap_or_else(CF1::<C>::zero)))?; // r will come from higher level transcript
|
|
||||||
let x =
|
let x =
|
||||||
FpVar::<CF1<C>>::new_input(cs.clone(), || Ok(self.x.unwrap_or_else(CF1::<C>::zero)))?;
|
FpVar::<CF1<C>>::new_input(cs.clone(), || Ok(self.x.unwrap_or_else(CF1::<C>::zero)))?;
|
||||||
|
|
||||||
let crh_params =
|
let crh_params =
|
||||||
CRHParametersVar::<C::ScalarField>::new_constant(cs.clone(), self.poseidon_config)?;
|
CRHParametersVar::<C::ScalarField>::new_constant(cs.clone(), self.poseidon_config)?;
|
||||||
|
|
||||||
|
// 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<C>>::new_constant(cs.clone(), CF1::<C>::zero())?;
|
let zero = FpVar::<CF1<C>>::new_constant(cs.clone(), CF1::<C>::zero())?;
|
||||||
let is_basecase = i.is_eq(&zero)?;
|
let is_basecase = i.is_eq(&zero)?;
|
||||||
let is_not_basecase = i.is_neq(&zero)?;
|
let is_not_basecase = i.is_neq(&zero)?;
|
||||||
@@ -333,6 +394,9 @@ where
|
|||||||
(u_i.u.is_one()?).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}.
|
// 3. nifs.verify, checks that folding u_i & U_i obtains U_{i+1}.
|
||||||
|
// compute r = H(u_i, U_i, cmT)
|
||||||
|
let r = Self::get_challenge(&crh_params, u_i.clone(), U_i.clone(), cmT)?;
|
||||||
|
|
||||||
// Notice that NIFSGadget::verify is not checking the folding of cmE & cmW, since it will
|
// Notice that NIFSGadget::verify is not checking the folding of cmE & cmW, since it will
|
||||||
// be done on the other curve.
|
// be done on the other curve.
|
||||||
let nifs_check = NIFSGadget::<C>::verify(r, u_i, U_i.clone(), U_i1.clone())?;
|
let nifs_check = NIFSGadget::<C>::verify(r, u_i, U_i.clone(), U_i1.clone())?;
|
||||||
@@ -364,7 +428,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ark_ff::BigInteger;
|
use ark_ff::BigInteger;
|
||||||
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
|
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
|
||||||
@@ -375,8 +439,7 @@ mod tests {
|
|||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
|
||||||
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
|
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
|
||||||
use crate::constants::N_BITS_CHALLENGE;
|
use crate::folding::nova::{nifs::NIFS, traits::NovaR1CS, Witness};
|
||||||
use crate::folding::nova::{check_instance_relation, nifs::NIFS, Witness};
|
|
||||||
use crate::frontend::arkworks::{extract_r1cs, extract_z};
|
use crate::frontend::arkworks::{extract_r1cs, extract_z};
|
||||||
use crate::pedersen::Pedersen;
|
use crate::pedersen::Pedersen;
|
||||||
use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript};
|
use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript};
|
||||||
@@ -391,6 +454,9 @@ mod tests {
|
|||||||
_f: PhantomData<F>,
|
_f: PhantomData<F>,
|
||||||
}
|
}
|
||||||
impl<F: PrimeField> FCircuit<F> for TestFCircuit<F> {
|
impl<F: PrimeField> FCircuit<F> for TestFCircuit<F> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { _f: PhantomData }
|
||||||
|
}
|
||||||
fn step_native(self, z_i: Vec<F>) -> Vec<F> {
|
fn step_native(self, z_i: Vec<F>) -> Vec<F> {
|
||||||
vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)]
|
vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)]
|
||||||
}
|
}
|
||||||
@@ -452,14 +518,19 @@ mod tests {
|
|||||||
let ci1 = w1.commit(&pedersen_params, x1.clone()).unwrap();
|
let ci1 = w1.commit(&pedersen_params, x1.clone()).unwrap();
|
||||||
let ci2 = w2.commit(&pedersen_params, x2.clone()).unwrap();
|
let ci2 = w2.commit(&pedersen_params, x2.clone()).unwrap();
|
||||||
|
|
||||||
// get challenge from transcript
|
// NIFS.P
|
||||||
|
let (T, cmT) =
|
||||||
|
NIFS::<Projective>::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2).unwrap();
|
||||||
|
|
||||||
|
// get challenge from transcript, since we're in a test skip absorbing values into
|
||||||
|
// transcript
|
||||||
let poseidon_config = poseidon_test_config::<Fr>();
|
let poseidon_config = poseidon_test_config::<Fr>();
|
||||||
let mut tr = PoseidonTranscript::<Projective>::new(&poseidon_config);
|
let mut tr = PoseidonTranscript::<Projective>::new(&poseidon_config);
|
||||||
let r_bits = tr.get_challenge_nbits(N_BITS_CHALLENGE);
|
let r_bits = tr.get_challenge_nbits(128);
|
||||||
let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
|
let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
|
||||||
|
|
||||||
let (_w3, ci3, _T, cmT) =
|
let (_, ci3) =
|
||||||
NIFS::<Projective>::prove(&pedersen_params, r_Fr, &r1cs, &w1, &ci1, &w2, &ci2).unwrap();
|
NIFS::<Projective>::fold_instances(r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap();
|
||||||
|
|
||||||
let ci3_verifier = NIFS::<Projective>::verify(r_Fr, &ci1, &ci2, &cmT);
|
let ci3_verifier = NIFS::<Projective>::verify(r_Fr, &ci1, &ci2, &cmT);
|
||||||
assert_eq!(ci3_verifier, ci3);
|
assert_eq!(ci3_verifier, ci3);
|
||||||
@@ -556,6 +627,60 @@ mod tests {
|
|||||||
assert_eq!(hVar.value().unwrap(), h);
|
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 = AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::get_challenge_native(
|
||||||
|
&poseidon_config,
|
||||||
|
u_i.clone(),
|
||||||
|
U_i.clone(),
|
||||||
|
cmT,
|
||||||
|
)
|
||||||
|
.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();
|
||||||
|
|
||||||
|
let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap();
|
||||||
|
|
||||||
|
// compute the challenge in-circuit
|
||||||
|
let rVar = AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::get_challenge(
|
||||||
|
&crh_params,
|
||||||
|
u_iVar,
|
||||||
|
U_iVar,
|
||||||
|
cmTVar,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(cs.is_satisfied().unwrap());
|
||||||
|
|
||||||
|
// check that the natively computed and in-circuit computed hashes match
|
||||||
|
assert_eq!(rVar.value().unwrap(), r);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// test_augmented_f_circuit folds the TestFCircuit circuit in multiple iterations, feeding the
|
/// test_augmented_f_circuit folds the TestFCircuit circuit in multiple iterations, feeding the
|
||||||
/// values into the AugmentedFCircuit.
|
/// values into the AugmentedFCircuit.
|
||||||
@@ -572,20 +697,21 @@ mod tests {
|
|||||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||||
|
|
||||||
// prepare the circuit to obtain its R1CS
|
// prepare the circuit to obtain its R1CS
|
||||||
let F_circuit = TestFCircuit::<Fr> { _f: PhantomData };
|
let F_circuit = TestFCircuit::<Fr>::new();
|
||||||
let mut augmented_F_circuit =
|
let mut augmented_F_circuit =
|
||||||
AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::empty(&poseidon_config, F_circuit);
|
AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::empty(&poseidon_config, F_circuit);
|
||||||
augmented_F_circuit
|
augmented_F_circuit
|
||||||
.generate_constraints(cs.clone())
|
.generate_constraints(cs.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cs.finalize();
|
cs.finalize();
|
||||||
|
println!("num_constraints={:?}", cs.num_constraints());
|
||||||
let cs = cs.into_inner().unwrap();
|
let cs = cs.into_inner().unwrap();
|
||||||
let r1cs = extract_r1cs::<Fr>(&cs);
|
let r1cs = extract_r1cs::<Fr>(&cs);
|
||||||
let z = extract_z::<Fr>(&cs); // includes 1 and public inputs
|
let z = extract_z::<Fr>(&cs); // includes 1 and public inputs
|
||||||
let (w, x) = r1cs.split_z(&z);
|
let (w, x) = r1cs.split_z(&z);
|
||||||
let F_witness_len = w.len();
|
assert_eq!(z.len(), r1cs.A.n_cols);
|
||||||
|
assert_eq!(1 + x.len() + w.len(), r1cs.A.n_cols);
|
||||||
let mut tr = PoseidonTranscript::<Projective>::new(&poseidon_config);
|
assert_eq!(r1cs.l, x.len());
|
||||||
|
|
||||||
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_rows);
|
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_rows);
|
||||||
|
|
||||||
@@ -594,31 +720,29 @@ mod tests {
|
|||||||
let mut z_i = z_0.clone();
|
let mut z_i = z_0.clone();
|
||||||
let mut z_i1 = vec![Fr::from(35_u32)];
|
let mut z_i1 = vec![Fr::from(35_u32)];
|
||||||
|
|
||||||
let w_dummy = Witness::<Projective>::new(vec![Fr::zero(); F_witness_len], r1cs.A.n_rows);
|
let w_dummy = Witness::<Projective>::new(vec![Fr::zero(); w.len()], r1cs.A.n_rows);
|
||||||
let u_dummy = CommittedInstance::<Projective>::dummy(x.len());
|
let u_dummy = CommittedInstance::<Projective>::dummy(x.len());
|
||||||
|
|
||||||
// Wi is a 'dummy witness', all zeroes, but with the size corresponding to the R1CS that
|
// W_i is a 'dummy witness', all zeroes, but with the size corresponding to the R1CS that
|
||||||
// we're working with.
|
// we're working with.
|
||||||
// set U_i <-- dummay instance
|
// set U_i <-- dummy instance
|
||||||
let mut W_i = w_dummy.clone();
|
let mut W_i = w_dummy.clone();
|
||||||
let mut U_i = u_dummy.clone();
|
let mut U_i = u_dummy.clone();
|
||||||
check_instance_relation(&r1cs, &W_i, &U_i).unwrap();
|
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
|
||||||
|
|
||||||
let mut w_i = w_dummy.clone();
|
let mut w_i = w_dummy.clone();
|
||||||
let mut u_i = u_dummy.clone();
|
let mut u_i = u_dummy.clone();
|
||||||
let (mut W_i1, mut U_i1, mut _T, mut cmT) = (
|
let (mut W_i1, mut U_i1, mut cmT): (
|
||||||
w_dummy.clone(),
|
Witness<Projective>,
|
||||||
u_dummy.clone(),
|
CommittedInstance<Projective>,
|
||||||
vec![],
|
Projective,
|
||||||
Projective::generator(),
|
) = (w_dummy.clone(), u_dummy.clone(), Projective::generator());
|
||||||
);
|
|
||||||
// as expected, dummy instances pass the relaxed_r1cs check
|
// as expected, dummy instances pass the relaxed_r1cs check
|
||||||
check_instance_relation(&r1cs, &W_i1, &U_i1).unwrap();
|
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
|
||||||
|
|
||||||
let mut i = Fr::zero();
|
let mut i = Fr::zero();
|
||||||
let mut u_i1_x: Fr;
|
let mut u_i1_x: Fr;
|
||||||
let n_steps: usize = 4;
|
for _ in 0..4 {
|
||||||
for _ in 0..n_steps {
|
|
||||||
if i == Fr::zero() {
|
if i == Fr::zero() {
|
||||||
// base case: i=0, z_i=z_0, U_i = U_d := dummy instance
|
// 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_1.x = H(1, z_0, z_i, U_i)
|
||||||
@@ -636,23 +760,17 @@ mod tests {
|
|||||||
U_i: Some(U_i.clone()), // = dummy
|
U_i: Some(U_i.clone()), // = dummy
|
||||||
U_i1: Some(U_i1.clone()), // = dummy
|
U_i1: Some(U_i1.clone()), // = dummy
|
||||||
cmT: Some(cmT),
|
cmT: Some(cmT),
|
||||||
r: Some(Fr::one()),
|
|
||||||
F: F_circuit,
|
F: F_circuit,
|
||||||
x: Some(u_i1_x),
|
x: Some(u_i1_x),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// get challenge from transcript (since this is a test, we skip absorbing values to
|
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
|
||||||
// the transcript for simplicity)
|
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
|
||||||
let r_bits = tr.get_challenge_nbits(N_BITS_CHALLENGE);
|
|
||||||
let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
|
|
||||||
|
|
||||||
check_instance_relation(&r1cs, &w_i, &u_i).unwrap();
|
|
||||||
check_instance_relation(&r1cs, &W_i, &U_i).unwrap();
|
|
||||||
|
|
||||||
// U_{i+1}
|
// U_{i+1}
|
||||||
(W_i1, U_i1, _T, cmT) = NIFS::<Projective>::prove(
|
let T: Vec<Fr>;
|
||||||
|
(T, cmT) = NIFS::<Projective>::compute_cmT(
|
||||||
&pedersen_params,
|
&pedersen_params,
|
||||||
r_Fr,
|
|
||||||
&r1cs,
|
&r1cs,
|
||||||
&w_i,
|
&w_i,
|
||||||
&u_i,
|
&u_i,
|
||||||
@@ -661,7 +779,20 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
check_instance_relation(&r1cs, &W_i1, &U_i1).unwrap();
|
// get challenge r
|
||||||
|
let r_Fr = AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::get_challenge_native(
|
||||||
|
&poseidon_config,
|
||||||
|
u_i.clone(),
|
||||||
|
U_i.clone(),
|
||||||
|
cmT,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
(W_i1, U_i1) =
|
||||||
|
NIFS::<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)
|
// folded instance output (public input, x)
|
||||||
// u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1})
|
// u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1})
|
||||||
@@ -678,7 +809,6 @@ mod tests {
|
|||||||
U_i: Some(U_i.clone()),
|
U_i: Some(U_i.clone()),
|
||||||
U_i1: Some(U_i1.clone()),
|
U_i1: Some(U_i1.clone()),
|
||||||
cmT: Some(cmT),
|
cmT: Some(cmT),
|
||||||
r: Some(r_Fr),
|
|
||||||
F: F_circuit,
|
F: F_circuit,
|
||||||
x: Some(u_i1_x),
|
x: Some(u_i1_x),
|
||||||
};
|
};
|
||||||
@@ -698,7 +828,7 @@ mod tests {
|
|||||||
cs.finalize();
|
cs.finalize();
|
||||||
let cs = cs.into_inner().unwrap();
|
let cs = cs.into_inner().unwrap();
|
||||||
// notice that here we use 'Z' (uppercase) to denote the 'z-vector' as in the paper,
|
// notice that here we use 'Z' (uppercase) to denote the 'z-vector' as in the paper,
|
||||||
// not the value 'z_i' (lowercase) which is the next state
|
// not the value 'z' (lowercase) which is the state
|
||||||
let Z_i1 = extract_z::<Fr>(&cs);
|
let Z_i1 = extract_z::<Fr>(&cs);
|
||||||
let (w_i1, x_i1) = r1cs.split_z(&Z_i1);
|
let (w_i1, x_i1) = r1cs.split_z(&Z_i1);
|
||||||
assert_eq!(x_i1.len(), 1);
|
assert_eq!(x_i1.len(), 1);
|
||||||
@@ -709,8 +839,8 @@ mod tests {
|
|||||||
w_i = Witness::<Projective>::new(w_i1.clone(), r1cs.A.n_rows);
|
w_i = Witness::<Projective>::new(w_i1.clone(), r1cs.A.n_rows);
|
||||||
u_i = w_i.commit(&pedersen_params, vec![u_i1_x]).unwrap();
|
u_i = w_i.commit(&pedersen_params, vec![u_i1_x]).unwrap();
|
||||||
|
|
||||||
check_instance_relation(&r1cs, &w_i, &u_i).unwrap();
|
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
|
||||||
check_instance_relation(&r1cs, &W_i1, &U_i1).unwrap();
|
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
|
||||||
|
|
||||||
// set values for next iteration
|
// set values for next iteration
|
||||||
i += Fr::one();
|
i += Fr::one();
|
||||||
|
|||||||
261
src/folding/nova/ivc.rs
Normal file
261
src/folding/nova/ivc.rs
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
|
||||||
|
use ark_ec::{CurveGroup, Group};
|
||||||
|
use ark_ff::PrimeField;
|
||||||
|
use ark_relations::r1cs::ConstraintSynthesizer;
|
||||||
|
use ark_relations::r1cs::ConstraintSystem;
|
||||||
|
use ark_std::rand::Rng;
|
||||||
|
use ark_std::{One, Zero};
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use super::circuits::{AugmentedFCircuit, FCircuit};
|
||||||
|
use super::{nifs::NIFS, traits::NovaR1CS, CommittedInstance, Witness};
|
||||||
|
use crate::ccs::r1cs::R1CS;
|
||||||
|
use crate::frontend::arkworks::{extract_r1cs, extract_z}; // TODO once Frontend trait is ready, use that
|
||||||
|
use crate::pedersen::{Params as PedersenParams, Pedersen};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
/// Implements the Incremental Verifiable Computation described in sections 1.2 and 5 of
|
||||||
|
/// [Nova](https://eprint.iacr.org/2021/370.pdf)
|
||||||
|
pub struct IVC<C1, C2, FC>
|
||||||
|
where
|
||||||
|
C1: CurveGroup,
|
||||||
|
C2: CurveGroup,
|
||||||
|
FC: FCircuit<C1::ScalarField>,
|
||||||
|
{
|
||||||
|
_c2: PhantomData<C2>,
|
||||||
|
r1cs: R1CS<C1::ScalarField>,
|
||||||
|
poseidon_config: PoseidonConfig<C1::ScalarField>,
|
||||||
|
pedersen_params: PedersenParams<C1>,
|
||||||
|
F: FC, // F circuit
|
||||||
|
i: C1::ScalarField,
|
||||||
|
z_0: Vec<C1::ScalarField>,
|
||||||
|
z_i: Vec<C1::ScalarField>,
|
||||||
|
w_i: Witness<C1>,
|
||||||
|
u_i: CommittedInstance<C1>,
|
||||||
|
W_i: Witness<C1>,
|
||||||
|
U_i: CommittedInstance<C1>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C1, C2, FC> IVC<C1, C2, FC>
|
||||||
|
where
|
||||||
|
C1: CurveGroup,
|
||||||
|
C2: CurveGroup,
|
||||||
|
FC: FCircuit<C1::ScalarField>,
|
||||||
|
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||||
|
<C1 as Group>::ScalarField: Absorb,
|
||||||
|
{
|
||||||
|
/// Initializes the IVC for the given parameters and initial state `z_0`.
|
||||||
|
pub fn new<R: Rng>(
|
||||||
|
rng: &mut R,
|
||||||
|
poseidon_config: PoseidonConfig<C1::ScalarField>,
|
||||||
|
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 augmented_F_circuit = AugmentedFCircuit::<C1, FC>::empty(&poseidon_config, F);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
let pedersen_params = Pedersen::<C1>::new_params(rng, r1cs.A.n_rows);
|
||||||
|
|
||||||
|
// setup the dummy instances
|
||||||
|
let (w_dummy, u_dummy) = r1cs.dummy_instance();
|
||||||
|
|
||||||
|
// W_i=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the R1CS that
|
||||||
|
// we're working with.
|
||||||
|
// Set U_i to be dummy instance
|
||||||
|
Ok(Self {
|
||||||
|
_c2: PhantomData,
|
||||||
|
r1cs,
|
||||||
|
poseidon_config,
|
||||||
|
pedersen_params,
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements IVC.P
|
||||||
|
pub fn prove_step(&mut self) -> Result<(), Error> {
|
||||||
|
let u_i1_x: C1::ScalarField;
|
||||||
|
let augmented_F_circuit: AugmentedFCircuit<C1, FC>;
|
||||||
|
let z_i1 = self.F.step_native(self.z_i.clone());
|
||||||
|
|
||||||
|
let (W_i1, U_i1, cmT): (Witness<C1>, CommittedInstance<C1>, C1);
|
||||||
|
|
||||||
|
if self.i == C1::ScalarField::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 = self.U_i.hash(
|
||||||
|
&self.poseidon_config,
|
||||||
|
C1::ScalarField::one(),
|
||||||
|
self.z_0.clone(),
|
||||||
|
z_i1.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
(W_i1, U_i1, cmT) = (self.w_i.clone(), self.u_i.clone(), C1::generator());
|
||||||
|
|
||||||
|
// base case
|
||||||
|
augmented_F_circuit = AugmentedFCircuit::<C1, FC> {
|
||||||
|
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()), // = dummy
|
||||||
|
cmT: Some(cmT),
|
||||||
|
F: self.F,
|
||||||
|
x: Some(u_i1_x),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
let T: Vec<C1::ScalarField>;
|
||||||
|
(T, cmT) = NIFS::<C1>::compute_cmT(
|
||||||
|
&self.pedersen_params,
|
||||||
|
&self.r1cs,
|
||||||
|
&self.w_i,
|
||||||
|
&self.u_i,
|
||||||
|
&self.W_i,
|
||||||
|
&self.U_i,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let r_Fr = AugmentedFCircuit::<C1, FC>::get_challenge_native(
|
||||||
|
&self.poseidon_config,
|
||||||
|
self.u_i.clone(),
|
||||||
|
self.U_i.clone(),
|
||||||
|
cmT,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// compute W_{i+1} and U_{i+1}
|
||||||
|
(W_i1, U_i1) = NIFS::<C1>::fold_instances(
|
||||||
|
r_Fr, &self.w_i, &self.u_i, &self.W_i, &self.U_i, &T, cmT,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.r1cs.check_relaxed_instance_relation(&W_i1, &U_i1)?;
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
&self.poseidon_config,
|
||||||
|
self.i + C1::ScalarField::one(),
|
||||||
|
self.z_0.clone(),
|
||||||
|
z_i1.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
augmented_F_circuit = AugmentedFCircuit::<C1, FC> {
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
|
||||||
|
|
||||||
|
augmented_F_circuit.generate_constraints(cs.clone())?;
|
||||||
|
|
||||||
|
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||||
|
// notice that here we use 'Z' (uppercase) to denote the 'z-vector' as in the paper, not
|
||||||
|
// the value 'z' (lowercase) which is the state
|
||||||
|
let Z_i1 = extract_z::<C1::ScalarField>(&cs);
|
||||||
|
let (w_i1, x_i1) = self.r1cs.split_z(&Z_i1);
|
||||||
|
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.
|
||||||
|
self.w_i = Witness::<C1>::new(w_i1.clone(), self.r1cs.A.n_rows);
|
||||||
|
self.u_i = self.w_i.commit(&self.pedersen_params, vec![u_i1_x])?;
|
||||||
|
|
||||||
|
// set values for next iteration
|
||||||
|
self.i += C1::ScalarField::one();
|
||||||
|
self.z_i = z_i1.clone();
|
||||||
|
self.U_i = U_i1.clone();
|
||||||
|
self.W_i = W_i1.clone();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements IVC.V
|
||||||
|
pub fn verify(&mut self, z_0: Vec<C1::ScalarField>, num_steps: u32) -> Result<(), Error> {
|
||||||
|
if self.i != C1::ScalarField::from(num_steps) {
|
||||||
|
return Err(Error::IVCVerificationFail);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.u_i.x.len() != 1 || self.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 = self
|
||||||
|
.U_i
|
||||||
|
.hash(&self.poseidon_config, self.i, z_0, self.z_i.clone())?;
|
||||||
|
if expected_u_i_x != self.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 self.u_i.cmE != C1::zero() || self.u_i.u != C1::ScalarField::one() {
|
||||||
|
return Err(Error::IVCVerificationFail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check R1CS satisfiability
|
||||||
|
self.r1cs.check_instance_relation(&self.w_i, &self.u_i)?;
|
||||||
|
// check RelaxedR1CS satisfiability
|
||||||
|
self.r1cs
|
||||||
|
.check_relaxed_instance_relation(&self.W_i, &self.U_i)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ark_pallas::{Fr, Projective};
|
||||||
|
use ark_vesta::Projective as Projective2;
|
||||||
|
|
||||||
|
use crate::folding::nova::circuits::tests::TestFCircuit;
|
||||||
|
use crate::transcript::poseidon::tests::poseidon_test_config;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ivc() {
|
||||||
|
let mut rng = ark_std::test_rng();
|
||||||
|
let poseidon_config = poseidon_test_config::<Fr>();
|
||||||
|
|
||||||
|
let F_circuit = TestFCircuit::<Fr>::new();
|
||||||
|
let z_0 = vec![Fr::from(3_u32)];
|
||||||
|
|
||||||
|
let mut ivc = IVC::<Projective, Projective2, TestFCircuit<Fr>>::new(
|
||||||
|
&mut rng,
|
||||||
|
poseidon_config, // poseidon config
|
||||||
|
F_circuit,
|
||||||
|
z_0.clone(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let num_steps: usize = 3;
|
||||||
|
for _ in 0..num_steps {
|
||||||
|
ivc.prove_step().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
ivc.verify(z_0, num_steps as u32).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,14 +7,15 @@ use ark_ec::{CurveGroup, Group};
|
|||||||
use ark_std::fmt::Debug;
|
use ark_std::fmt::Debug;
|
||||||
use ark_std::{One, Zero};
|
use ark_std::{One, Zero};
|
||||||
|
|
||||||
use crate::ccs::r1cs::R1CS;
|
|
||||||
use crate::folding::circuits::nonnative::point_to_nonnative_limbs;
|
use crate::folding::circuits::nonnative::point_to_nonnative_limbs;
|
||||||
use crate::pedersen::{Params as PedersenParams, Pedersen};
|
use crate::pedersen::{Params as PedersenParams, Pedersen};
|
||||||
use crate::utils::vec::is_zero_vec;
|
use crate::utils::vec::is_zero_vec;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
pub mod circuits;
|
pub mod circuits;
|
||||||
|
pub mod ivc;
|
||||||
pub mod nifs;
|
pub mod nifs;
|
||||||
|
pub mod traits;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct CommittedInstance<C: CurveGroup> {
|
pub struct CommittedInstance<C: CurveGroup> {
|
||||||
@@ -52,7 +53,7 @@ where
|
|||||||
let (cmE_x, cmE_y) = point_to_nonnative_limbs::<C>(self.cmE)?;
|
let (cmE_x, cmE_y) = point_to_nonnative_limbs::<C>(self.cmE)?;
|
||||||
let (cmW_x, cmW_y) = point_to_nonnative_limbs::<C>(self.cmW)?;
|
let (cmW_x, cmW_y) = point_to_nonnative_limbs::<C>(self.cmW)?;
|
||||||
|
|
||||||
Ok(CRH::<C::ScalarField>::evaluate(
|
CRH::<C::ScalarField>::evaluate(
|
||||||
poseidon_config,
|
poseidon_config,
|
||||||
vec![
|
vec![
|
||||||
vec![i],
|
vec![i],
|
||||||
@@ -67,7 +68,7 @@ where
|
|||||||
]
|
]
|
||||||
.concat(),
|
.concat(),
|
||||||
)
|
)
|
||||||
.unwrap())
|
.map_err(|e| Error::Other(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,16 +110,3 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_instance_relation<C: CurveGroup>(
|
|
||||||
r1cs: &R1CS<C::ScalarField>,
|
|
||||||
W: &Witness<C>,
|
|
||||||
U: &CommittedInstance<C>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut rel_r1cs = r1cs.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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::utils::vec::*;
|
|||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
/// Implements the Non-Interactive Folding Scheme described in section 4 of
|
/// Implements the Non-Interactive Folding Scheme described in section 4 of
|
||||||
/// https://eprint.iacr.org/2021/370.pdf
|
/// [Nova](https://eprint.iacr.org/2021/370.pdf)
|
||||||
pub struct NIFS<C: CurveGroup> {
|
pub struct NIFS<C: CurveGroup> {
|
||||||
_phantom: PhantomData<C>,
|
_phantom: PhantomData<C>,
|
||||||
}
|
}
|
||||||
@@ -85,36 +85,52 @@ where
|
|||||||
CommittedInstance::<C> { cmE, u, cmW, x }
|
CommittedInstance::<C> { cmE, u, cmW, x }
|
||||||
}
|
}
|
||||||
|
|
||||||
// NIFS.P
|
/// NIFS.P is the consecutive combination of compute_cmT with fold_instances
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub fn prove(
|
/// compute_cmT is part of the NIFS.P logic
|
||||||
|
pub fn compute_cmT(
|
||||||
pedersen_params: &PedersenParams<C>,
|
pedersen_params: &PedersenParams<C>,
|
||||||
// r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
|
|
||||||
r: C::ScalarField,
|
|
||||||
r1cs: &R1CS<C::ScalarField>,
|
r1cs: &R1CS<C::ScalarField>,
|
||||||
w1: &Witness<C>,
|
w1: &Witness<C>,
|
||||||
ci1: &CommittedInstance<C>,
|
ci1: &CommittedInstance<C>,
|
||||||
w2: &Witness<C>,
|
w2: &Witness<C>,
|
||||||
ci2: &CommittedInstance<C>,
|
ci2: &CommittedInstance<C>,
|
||||||
) -> Result<(Witness<C>, CommittedInstance<C>, Vec<C::ScalarField>, C), Error> {
|
) -> Result<(Vec<C::ScalarField>, C), Error> {
|
||||||
let z1: Vec<C::ScalarField> = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat();
|
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();
|
let z2: Vec<C::ScalarField> = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat();
|
||||||
|
|
||||||
// compute cross terms
|
// compute cross terms
|
||||||
let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?;
|
let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?;
|
||||||
let rT = C::ScalarField::one(); // use 1 as rT since we don't need hiding property for cm(T)
|
// use r_T=1 since we don't need hiding property for cm(T)
|
||||||
let cmT = Pedersen::commit(pedersen_params, &T, &rT)?;
|
let cmT = Pedersen::commit(pedersen_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 comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
|
||||||
|
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
|
// fold witness
|
||||||
let w3 = NIFS::<C>::fold_witness(r, w1, w2, &T, rT)?;
|
// use r_T=1 since we don't need hiding property for cm(T)
|
||||||
|
let w3 = NIFS::<C>::fold_witness(r, w1, w2, T, C::ScalarField::one())?;
|
||||||
|
|
||||||
// fold committed instancs
|
// fold committed instancs
|
||||||
let ci3 = NIFS::<C>::fold_committed_instance(r, ci1, ci2, &cmT);
|
let ci3 = NIFS::<C>::fold_committed_instance(r, ci1, ci2, &cmT);
|
||||||
|
|
||||||
Ok((w3, ci3, T, cmT))
|
Ok((w3, ci3))
|
||||||
}
|
}
|
||||||
|
|
||||||
// NIFS.V
|
/// 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(
|
pub fn verify(
|
||||||
// r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
|
// r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
|
||||||
r: C::ScalarField,
|
r: C::ScalarField,
|
||||||
@@ -135,11 +151,11 @@ where
|
|||||||
ci3: &CommittedInstance<C>,
|
ci3: &CommittedInstance<C>,
|
||||||
cmT: &C,
|
cmT: &C,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let r2 = r * r;
|
let expected = Self::fold_committed_instance(r, ci1, ci2, cmT);
|
||||||
if ci3.cmE != (ci1.cmE + cmT.mul(r) + ci2.cmE.mul(r2))
|
if ci3.cmE != expected.cmE
|
||||||
|| ci3.u != ci1.u + r * ci2.u
|
|| ci3.u != expected.u
|
||||||
|| ci3.cmW != (ci1.cmW + ci2.cmW.mul(r))
|
|| ci3.cmW != expected.cmW
|
||||||
|| ci3.x != vec_add(&ci1.x, &vec_scalar_mul(&ci2.x, &r))?
|
|| ci3.x != expected.x
|
||||||
{
|
{
|
||||||
return Err(Error::NotSatisfied);
|
return Err(Error::NotSatisfied);
|
||||||
}
|
}
|
||||||
@@ -168,7 +184,7 @@ where
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if cm_proofs.len() != 3 {
|
if cm_proofs.len() != 3 {
|
||||||
// cm_proofs should have length 3: [cmE_proof, cmW_proof, cmT_proof]
|
// cm_proofs should have length 3: [cmE_proof, cmW_proof, cmT_proof]
|
||||||
return Err(Error::NotExpectedLength);
|
return Err(Error::NotExpectedLength(cm_proofs.len(), 3));
|
||||||
}
|
}
|
||||||
Pedersen::verify(pedersen_params, tr, ci.cmE, cm_proofs[0].clone())?;
|
Pedersen::verify(pedersen_params, tr, ci.cmE, cm_proofs[0].clone())?;
|
||||||
Pedersen::verify(pedersen_params, tr, ci.cmW, cm_proofs[1].clone())?;
|
Pedersen::verify(pedersen_params, tr, ci.cmW, cm_proofs[1].clone())?;
|
||||||
@@ -222,9 +238,11 @@ pub mod tests {
|
|||||||
|
|
||||||
let r_Fr = Fr::from(3_u32);
|
let r_Fr = Fr::from(3_u32);
|
||||||
|
|
||||||
let (W_i1, U_i1, _, _) =
|
let (T, cmT) =
|
||||||
NIFS::<Projective>::prove(&pedersen_params, r_Fr, &r1cs, &w_i, &u_i, &W_i, &U_i)
|
NIFS::<Projective>::compute_cmT(&pedersen_params, &r1cs, &w_i, &u_i, &W_i, &U_i)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let (W_i1, U_i1) =
|
||||||
|
NIFS::<Projective>::fold_instances(r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT).unwrap();
|
||||||
|
|
||||||
let z: Vec<Fr> = [vec![U_i1.u], U_i1.x.to_vec(), W_i1.W.to_vec()].concat();
|
let z: Vec<Fr> = [vec![U_i1.u], U_i1.x.to_vec(), W_i1.W.to_vec()].concat();
|
||||||
assert_eq!(z.len(), z1.len());
|
assert_eq!(z.len(), z1.len());
|
||||||
@@ -253,8 +271,10 @@ pub mod tests {
|
|||||||
let ci2 = w2.commit(&pedersen_params, x2.clone()).unwrap();
|
let ci2 = w2.commit(&pedersen_params, x2.clone()).unwrap();
|
||||||
|
|
||||||
// NIFS.P
|
// NIFS.P
|
||||||
let (w3, ci3_aux, T, cmT) =
|
let (T, cmT) =
|
||||||
NIFS::<Projective>::prove(&pedersen_params, r, &r1cs, &w1, &ci1, &w2, &ci2).unwrap();
|
NIFS::<Projective>::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2).unwrap();
|
||||||
|
let (w3, ci3_aux) =
|
||||||
|
NIFS::<Projective>::fold_instances(r, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap();
|
||||||
|
|
||||||
// NIFS.V
|
// NIFS.V
|
||||||
let ci3 = NIFS::<Projective>::verify(r, &ci1, &ci2, &cmT);
|
let ci3 = NIFS::<Projective>::verify(r, &ci1, &ci2, &cmT);
|
||||||
@@ -348,9 +368,8 @@ pub mod tests {
|
|||||||
let r = Fr::rand(&mut rng); // folding challenge would come from the transcript
|
let r = Fr::rand(&mut rng); // folding challenge would come from the transcript
|
||||||
|
|
||||||
// NIFS.P
|
// NIFS.P
|
||||||
let (folded_w, _, _, cmT) = NIFS::<Projective>::prove(
|
let (T, cmT) = NIFS::<Projective>::compute_cmT(
|
||||||
&pedersen_params,
|
&pedersen_params,
|
||||||
r,
|
|
||||||
&r1cs,
|
&r1cs,
|
||||||
&running_instance_w,
|
&running_instance_w,
|
||||||
&running_committed_instance,
|
&running_committed_instance,
|
||||||
@@ -358,6 +377,16 @@ pub mod tests {
|
|||||||
&incomming_committed_instance,
|
&incomming_committed_instance,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let (folded_w, _) = NIFS::<Projective>::fold_instances(
|
||||||
|
r,
|
||||||
|
&running_instance_w,
|
||||||
|
&running_committed_instance,
|
||||||
|
&incomming_instance_w,
|
||||||
|
&incomming_committed_instance,
|
||||||
|
&T,
|
||||||
|
cmT,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// NIFS.V
|
// NIFS.V
|
||||||
let folded_committed_instance = NIFS::<Projective>::verify(
|
let folded_committed_instance = NIFS::<Projective>::verify(
|
||||||
|
|||||||
67
src/folding/nova/traits.rs
Normal file
67
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/lib.rs
20
src/lib.rs
@@ -20,21 +20,29 @@ pub mod utils;
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("ark_relations::r1cs::SynthesisError")]
|
#[error("ark_relations::r1cs::SynthesisError")]
|
||||||
SynthesisError(#[from] ark_relations::r1cs::SynthesisError),
|
SynthesisError(#[from] ark_relations::r1cs::SynthesisError),
|
||||||
|
#[error("{0}")]
|
||||||
|
Other(String),
|
||||||
|
|
||||||
#[error("Relation not satisfied")]
|
#[error("Relation not satisfied")]
|
||||||
NotSatisfied,
|
NotSatisfied,
|
||||||
#[error("Not equal")]
|
#[error("Not equal")]
|
||||||
NotEqual,
|
NotEqual,
|
||||||
#[error("Vectors should have the same length")]
|
#[error("Vectors should have the same length ({0}, {1})")]
|
||||||
NotSameLength,
|
NotSameLength(usize, usize),
|
||||||
#[error("Vector's length is not the expected")]
|
#[error("Vector's length ({0}) is not the expected ({1})")]
|
||||||
NotExpectedLength,
|
NotExpectedLength(usize, usize),
|
||||||
#[error("Can not be empty")]
|
#[error("Can not be empty")]
|
||||||
Empty,
|
Empty,
|
||||||
#[error("Pedersen parameters length is not suficient")]
|
#[error("Pedersen parameters length is not suficient (generators.len={0} < vector.len={1} unsatisfied)")]
|
||||||
PedersenParamsLen,
|
PedersenParamsLen(usize, usize),
|
||||||
#[error("Pedersen verification failed")]
|
#[error("Pedersen verification failed")]
|
||||||
PedersenVerificationFail,
|
PedersenVerificationFail,
|
||||||
|
#[error("IVC verification failed")]
|
||||||
|
IVCVerificationFail,
|
||||||
|
#[error("R1CS instance is expected to not be relaxed")]
|
||||||
|
R1CSUnrelaxedFail,
|
||||||
|
#[error("Could not find the inner ConstraintSystem")]
|
||||||
|
NoInnerConstraintSystem,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined
|
/// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ impl<C: CurveGroup> Pedersen<C> {
|
|||||||
r: &C::ScalarField,
|
r: &C::ScalarField,
|
||||||
) -> Result<C, Error> {
|
) -> Result<C, Error> {
|
||||||
if params.generators.len() < v.len() {
|
if params.generators.len() < v.len() {
|
||||||
return Err(Error::PedersenParamsLen);
|
return Err(Error::PedersenParamsLen(params.generators.len(), v.len()));
|
||||||
}
|
}
|
||||||
// h⋅r + <g, v>
|
// h⋅r + <g, v>
|
||||||
// use msm_unchecked because we already ensured at the if that lengths match
|
// use msm_unchecked because we already ensured at the if that lengths match
|
||||||
@@ -58,10 +58,10 @@ impl<C: CurveGroup> Pedersen<C> {
|
|||||||
r: &C::ScalarField,
|
r: &C::ScalarField,
|
||||||
) -> Result<Proof<C>, Error> {
|
) -> Result<Proof<C>, Error> {
|
||||||
if params.generators.len() < v.len() {
|
if params.generators.len() < v.len() {
|
||||||
return Err(Error::PedersenParamsLen);
|
return Err(Error::PedersenParamsLen(params.generators.len(), v.len()));
|
||||||
}
|
}
|
||||||
|
|
||||||
transcript.absorb_point(cm);
|
transcript.absorb_point(cm)?;
|
||||||
let r1 = transcript.get_challenge();
|
let r1 = transcript.get_challenge();
|
||||||
let d = transcript.get_challenges(v.len());
|
let d = transcript.get_challenges(v.len());
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ impl<C: CurveGroup> Pedersen<C> {
|
|||||||
// use msm_unchecked because we already ensured at the if that lengths match
|
// use msm_unchecked because we already ensured at the if that lengths match
|
||||||
let R: C = params.h.mul(r1) + C::msm_unchecked(¶ms.generators[..d.len()], &d);
|
let R: C = params.h.mul(r1) + C::msm_unchecked(¶ms.generators[..d.len()], &d);
|
||||||
|
|
||||||
transcript.absorb_point(&R);
|
transcript.absorb_point(&R)?;
|
||||||
let e = transcript.get_challenge();
|
let e = transcript.get_challenge();
|
||||||
|
|
||||||
// u = d + v⋅e
|
// u = d + v⋅e
|
||||||
@@ -87,13 +87,16 @@ impl<C: CurveGroup> Pedersen<C> {
|
|||||||
proof: Proof<C>,
|
proof: Proof<C>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if params.generators.len() < proof.u.len() {
|
if params.generators.len() < proof.u.len() {
|
||||||
return Err(Error::PedersenParamsLen);
|
return Err(Error::PedersenParamsLen(
|
||||||
|
params.generators.len(),
|
||||||
|
proof.u.len(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
transcript.absorb_point(&cm);
|
transcript.absorb_point(&cm)?;
|
||||||
transcript.get_challenge(); // r_1
|
transcript.get_challenge(); // r_1
|
||||||
transcript.get_challenges(proof.u.len()); // d
|
transcript.get_challenges(proof.u.len()); // d
|
||||||
transcript.absorb_point(&proof.R);
|
transcript.absorb_point(&proof.R)?;
|
||||||
let e = transcript.get_challenge();
|
let e = transcript.get_challenge();
|
||||||
|
|
||||||
// check that: R + cm == h⋅r_u + <g, u>
|
// check that: R + cm == h⋅r_u + <g, u>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::Error;
|
||||||
use ark_ec::CurveGroup;
|
use ark_ec::CurveGroup;
|
||||||
use ark_std::fmt::Debug;
|
use ark_std::fmt::Debug;
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ pub trait Transcript<C: CurveGroup> {
|
|||||||
fn new(config: &Self::TranscriptConfig) -> Self;
|
fn new(config: &Self::TranscriptConfig) -> Self;
|
||||||
fn absorb(&mut self, v: &C::ScalarField);
|
fn absorb(&mut self, v: &C::ScalarField);
|
||||||
fn absorb_vec(&mut self, v: &[C::ScalarField]);
|
fn absorb_vec(&mut self, v: &[C::ScalarField]);
|
||||||
fn absorb_point(&mut self, v: &C);
|
fn absorb_point(&mut self, v: &C) -> Result<(), Error>;
|
||||||
fn get_challenge(&mut self) -> C::ScalarField;
|
fn get_challenge(&mut self) -> C::ScalarField;
|
||||||
/// get_challenge_nbits returns a field element of size nbits
|
/// get_challenge_nbits returns a field element of size nbits
|
||||||
fn get_challenge_nbits(&mut self, nbits: usize) -> Vec<bool>;
|
fn get_challenge_nbits(&mut self, nbits: usize) -> Vec<bool>;
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ use ark_ec::{AffineRepr, CurveGroup, Group};
|
|||||||
use ark_ff::{BigInteger, Field, PrimeField};
|
use ark_ff::{BigInteger, Field, PrimeField};
|
||||||
use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar};
|
use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar};
|
||||||
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
||||||
|
use ark_std::{One, Zero};
|
||||||
|
|
||||||
use crate::transcript::Transcript;
|
use crate::transcript::Transcript;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// PoseidonTranscript implements the Transcript trait using the Poseidon hash
|
/// PoseidonTranscript implements the Transcript trait using the Poseidon hash
|
||||||
pub struct PoseidonTranscript<C: CurveGroup>
|
pub struct PoseidonTranscript<C: CurveGroup>
|
||||||
@@ -34,8 +36,9 @@ where
|
|||||||
fn absorb_vec(&mut self, v: &[C::ScalarField]) {
|
fn absorb_vec(&mut self, v: &[C::ScalarField]) {
|
||||||
self.sponge.absorb(&v);
|
self.sponge.absorb(&v);
|
||||||
}
|
}
|
||||||
fn absorb_point(&mut self, p: &C) {
|
fn absorb_point(&mut self, p: &C) -> Result<(), Error> {
|
||||||
self.sponge.absorb(&prepare_point(p));
|
self.sponge.absorb(&prepare_point(p)?);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
fn get_challenge(&mut self) -> C::ScalarField {
|
fn get_challenge(&mut self) -> C::ScalarField {
|
||||||
let c = self.sponge.squeeze_field_elements(1);
|
let c = self.sponge.squeeze_field_elements(1);
|
||||||
@@ -54,25 +57,27 @@ where
|
|||||||
|
|
||||||
// Returns the point coordinates in Fr, so it can be absrobed by the transcript. It does not work
|
// Returns the point coordinates in Fr, so it can be absrobed by the transcript. It does not work
|
||||||
// over bytes in order to have a logic that can be reproduced in-circuit.
|
// over bytes in order to have a logic that can be reproduced in-circuit.
|
||||||
fn prepare_point<C: CurveGroup>(p: &C) -> Vec<C::ScalarField> {
|
fn prepare_point<C: CurveGroup>(p: &C) -> Result<Vec<C::ScalarField>, Error> {
|
||||||
let binding = p.into_affine();
|
let affine = p.into_affine();
|
||||||
let p_coords = &binding.xy().unwrap();
|
let xy_obj = &affine.xy();
|
||||||
let x_bi = p_coords
|
let mut xy = (&C::BaseField::zero(), &C::BaseField::one());
|
||||||
.0
|
if xy_obj.is_some() {
|
||||||
.to_base_prime_field_elements()
|
xy = xy_obj.unwrap();
|
||||||
|
}
|
||||||
|
let x_bi =
|
||||||
|
xy.0.to_base_prime_field_elements()
|
||||||
.next()
|
.next()
|
||||||
.expect("a")
|
.expect("a")
|
||||||
.into_bigint();
|
.into_bigint();
|
||||||
let y_bi = p_coords
|
let y_bi =
|
||||||
.1
|
xy.1.to_base_prime_field_elements()
|
||||||
.to_base_prime_field_elements()
|
|
||||||
.next()
|
.next()
|
||||||
.expect("a")
|
.expect("a")
|
||||||
.into_bigint();
|
.into_bigint();
|
||||||
vec![
|
Ok(vec![
|
||||||
C::ScalarField::from_le_bytes_mod_order(x_bi.to_bytes_le().as_ref()),
|
C::ScalarField::from_le_bytes_mod_order(x_bi.to_bytes_le().as_ref()),
|
||||||
C::ScalarField::from_le_bytes_mod_order(y_bi.to_bytes_le().as_ref()),
|
C::ScalarField::from_le_bytes_mod_order(y_bi.to_bytes_le().as_ref()),
|
||||||
]
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PoseidonTranscriptVar implements the gadget compatible with PoseidonTranscript
|
/// PoseidonTranscriptVar implements the gadget compatible with PoseidonTranscript
|
||||||
@@ -166,7 +171,7 @@ pub mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transcript_and_transcriptvar_nbits() {
|
fn test_transcript_and_transcriptvar_nbits() {
|
||||||
let nbits = crate::constants::N_BITS_CHALLENGE;
|
let nbits = 128;
|
||||||
|
|
||||||
// use 'native' transcript
|
// use 'native' transcript
|
||||||
let config = poseidon_test_config::<Fq>();
|
let config = poseidon_test_config::<Fq>();
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::filter_map_bool_then)]
|
||||||
fn barycentric_weights<F: PrimeField>(points: &[F]) -> Vec<F> {
|
fn barycentric_weights<F: PrimeField>(points: &[F]) -> Vec<F> {
|
||||||
let mut weights = points
|
let mut weights = points
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -46,14 +46,14 @@ pub fn dense_matrix_to_sparse<F: PrimeField>(m: Vec<Vec<F>>) -> SparseMatrix<F>
|
|||||||
|
|
||||||
pub fn vec_add<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
|
pub fn vec_add<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
|
||||||
if a.len() != b.len() {
|
if a.len() != b.len() {
|
||||||
return Err(Error::NotSameLength);
|
return Err(Error::NotSameLength(a.len(), b.len()));
|
||||||
}
|
}
|
||||||
Ok(a.iter().zip(b.iter()).map(|(x, y)| *x + y).collect())
|
Ok(a.iter().zip(b.iter()).map(|(x, y)| *x + y).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vec_sub<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
|
pub fn vec_sub<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
|
||||||
if a.len() != b.len() {
|
if a.len() != b.len() {
|
||||||
return Err(Error::NotSameLength);
|
return Err(Error::NotSameLength(a.len(), b.len()));
|
||||||
}
|
}
|
||||||
Ok(a.iter().zip(b.iter()).map(|(x, y)| *x - y).collect())
|
Ok(a.iter().zip(b.iter()).map(|(x, y)| *x - y).collect())
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ pub fn mat_vec_mul<F: PrimeField>(M: &Vec<Vec<F>>, z: &[F]) -> Result<Vec<F>, Er
|
|||||||
return Err(Error::Empty);
|
return Err(Error::Empty);
|
||||||
}
|
}
|
||||||
if M[0].len() != z.len() {
|
if M[0].len() != z.len() {
|
||||||
return Err(Error::NotSameLength);
|
return Err(Error::NotSameLength(M[0].len(), z.len()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut r: Vec<F> = vec![F::zero(); M.len()];
|
let mut r: Vec<F> = vec![F::zero(); M.len()];
|
||||||
@@ -95,7 +95,7 @@ pub fn mat_vec_mul_sparse<F: PrimeField>(matrix: &SparseMatrix<F>, vector: &[F])
|
|||||||
|
|
||||||
pub fn hadamard<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
|
pub fn hadamard<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
|
||||||
if a.len() != b.len() {
|
if a.len() != b.len() {
|
||||||
return Err(Error::NotSameLength);
|
return Err(Error::NotSameLength(a.len(), b.len()));
|
||||||
}
|
}
|
||||||
Ok(cfg_iter!(a).zip(b).map(|(a, b)| *a * b).collect())
|
Ok(cfg_iter!(a).zip(b).map(|(a, b)| *a * b).collect())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user