mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-08 15:01: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>>,
|
||||
) -> Result<Vec<FpVar<F>>, Error> {
|
||||
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()];
|
||||
for i in 0..a.len() {
|
||||
@@ -68,7 +68,7 @@ pub fn hadamard<F: PrimeField>(
|
||||
b: &Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, Error> {
|
||||
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()];
|
||||
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::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
fields::nonnative::NonNativeFieldVar,
|
||||
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{Namespace, SynthesisError};
|
||||
use ark_std::{One, Zero};
|
||||
use core::borrow::Borrow;
|
||||
|
||||
/// NonNativeAffineVar represents an elliptic curve point in Affine represenation in the non-native
|
||||
/// field. It is not intended to perform operations, but just to contain the affine coordinates in
|
||||
/// order to perform hash operations of the point.
|
||||
/// field, over the constraint field. It is not intended to perform operations, but just to contain
|
||||
/// the affine coordinates in order to perform hash operations of the point.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NonNativeAffineVar<F: PrimeField, CF: PrimeField> {
|
||||
pub x: NonNativeFieldVar<F, CF>,
|
||||
pub y: NonNativeFieldVar<F, CF>,
|
||||
pub struct NonNativeAffineVar<F: PrimeField> {
|
||||
pub x: Vec<FpVar<F>>,
|
||||
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
|
||||
C: CurveGroup,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
@@ -32,30 +33,23 @@ where
|
||||
let cs = cs.into();
|
||||
|
||||
let affine = val.borrow().into_affine();
|
||||
if affine.is_zero() {
|
||||
let x = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(C::BaseField::zero()),
|
||||
mode,
|
||||
)?;
|
||||
let y = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(C::BaseField::one()),
|
||||
mode,
|
||||
)?;
|
||||
return Ok(Self { x, y });
|
||||
let xy_obj = &affine.xy();
|
||||
let mut xy = (&C::BaseField::zero(), &C::BaseField::one());
|
||||
if xy_obj.is_some() {
|
||||
xy = xy_obj.unwrap();
|
||||
}
|
||||
let xy = affine.xy().unwrap();
|
||||
let x = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(xy.0),
|
||||
mode,
|
||||
)?;
|
||||
)?
|
||||
.to_constraint_field()?;
|
||||
let y = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(xy.1),
|
||||
mode,
|
||||
)?;
|
||||
)?
|
||||
.to_constraint_field()?;
|
||||
|
||||
Ok(Self { x, y })
|
||||
})
|
||||
@@ -101,7 +95,7 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ark_pallas::{Fq, Fr, Projective};
|
||||
use ark_pallas::{Fr, Projective};
|
||||
use ark_r1cs_std::alloc::AllocVar;
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_std::Zero;
|
||||
@@ -112,6 +106,6 @@ mod tests {
|
||||
|
||||
// dealing with the 'zero' point should not panic when doing the unwrap
|
||||
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::{
|
||||
poseidon::constraints::{CRHGadget, CRHParametersVar},
|
||||
CRHSchemeGadget,
|
||||
poseidon::CRH,
|
||||
CRHScheme, CRHSchemeGadget,
|
||||
};
|
||||
use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
|
||||
use ark_ec::{AffineRepr, CurveGroup, Group};
|
||||
@@ -12,7 +13,6 @@ use ark_r1cs_std::{
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
groups::GroupOpsBounds,
|
||||
prelude::CurveVar,
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
|
||||
use ark_std::fmt::Debug;
|
||||
@@ -20,7 +20,10 @@ use ark_std::Zero;
|
||||
use core::{borrow::Borrow, marker::PhantomData};
|
||||
|
||||
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
|
||||
/// 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> {
|
||||
u: FpVar<C::ScalarField>,
|
||||
x: Vec<FpVar<C::ScalarField>>,
|
||||
cmE: NonNativeAffineVar<CF2<C>, C::ScalarField>,
|
||||
cmW: NonNativeAffineVar<CF2<C>, C::ScalarField>,
|
||||
cmE: NonNativeAffineVar<C::ScalarField>,
|
||||
cmW: NonNativeAffineVar<C::ScalarField>,
|
||||
}
|
||||
|
||||
impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceVar<C>
|
||||
@@ -57,12 +60,12 @@ where
|
||||
let x: Vec<FpVar<C::ScalarField>> =
|
||||
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(),
|
||||
|| Ok(val.borrow().cmE),
|
||||
mode,
|
||||
)?;
|
||||
let cmW = NonNativeAffineVar::<CF2<C>, C::ScalarField>::new_variable(
|
||||
let cmW = NonNativeAffineVar::<C::ScalarField>::new_variable(
|
||||
cs.clone(),
|
||||
|| Ok(val.borrow().cmW),
|
||||
mode,
|
||||
@@ -95,10 +98,10 @@ where
|
||||
z_i,
|
||||
vec![self.u],
|
||||
self.x,
|
||||
self.cmE.x.to_constraint_field()?,
|
||||
self.cmE.y.to_constraint_field()?,
|
||||
self.cmW.x.to_constraint_field()?,
|
||||
self.cmW.y.to_constraint_field()?,
|
||||
self.cmE.x,
|
||||
self.cmE.y,
|
||||
self.cmW.x,
|
||||
self.cmW.y,
|
||||
]
|
||||
.concat();
|
||||
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
|
||||
/// inside the agmented F' function.
|
||||
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
|
||||
/// computing the new z_i
|
||||
fn step_native(
|
||||
@@ -242,14 +248,12 @@ pub struct AugmentedFCircuit<C: CurveGroup, FC: FCircuit<CF1<C>>> {
|
||||
pub U_i: Option<CommittedInstance<C>>,
|
||||
pub U_i1: Option<CommittedInstance<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 x: Option<CF1<C>>, // public inputs (u_{i+1}.x)
|
||||
pub F: FC, // F circuit
|
||||
pub x: Option<CF1<C>>, // public inputs (u_{i+1}.x)
|
||||
}
|
||||
|
||||
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> AugmentedFCircuit<C, FC> {
|
||||
#[allow(dead_code)] // TMP while IVC does not use this method
|
||||
fn empty(poseidon_config: &PoseidonConfig<CF1<C>>, F_circuit: FC) -> Self {
|
||||
pub fn empty(poseidon_config: &PoseidonConfig<CF1<C>>, F_circuit: FC) -> Self {
|
||||
Self {
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
i: None,
|
||||
@@ -259,13 +263,77 @@ impl<C: CurveGroup, FC: FCircuit<CF1<C>>> AugmentedFCircuit<C, FC> {
|
||||
U_i: None,
|
||||
U_i1: None,
|
||||
cmT: None,
|
||||
r: None,
|
||||
F: F_circuit,
|
||||
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>
|
||||
where
|
||||
C: CurveGroup,
|
||||
@@ -282,15 +350,7 @@ where
|
||||
Ok(self.z_i.unwrap_or_else(|| vec![CF1::<C>::zero()]))
|
||||
})?;
|
||||
|
||||
// get z_{i+1} from the F circuit
|
||||
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_native = CommittedInstance::<C>::dummy(1);
|
||||
|
||||
let u_dummy =
|
||||
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(), || {
|
||||
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)))?;
|
||||
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 =
|
||||
FpVar::<CF1<C>>::new_input(cs.clone(), || Ok(self.x.unwrap_or_else(CF1::<C>::zero)))?;
|
||||
|
||||
let crh_params =
|
||||
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 is_basecase = i.is_eq(&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)?;
|
||||
|
||||
// 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
|
||||
// be done on the other curve.
|
||||
let nifs_check = NIFSGadget::<C>::verify(r, u_i, U_i.clone(), U_i1.clone())?;
|
||||
@@ -364,7 +428,7 @@ where
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ark_ff::BigInteger;
|
||||
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
|
||||
@@ -375,8 +439,7 @@ mod tests {
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
|
||||
use crate::constants::N_BITS_CHALLENGE;
|
||||
use crate::folding::nova::{check_instance_relation, nifs::NIFS, Witness};
|
||||
use crate::folding::nova::{nifs::NIFS, traits::NovaR1CS, Witness};
|
||||
use crate::frontend::arkworks::{extract_r1cs, extract_z};
|
||||
use crate::pedersen::Pedersen;
|
||||
use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript};
|
||||
@@ -391,6 +454,9 @@ mod tests {
|
||||
_f: PhantomData<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> {
|
||||
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 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 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 (_w3, ci3, _T, cmT) =
|
||||
NIFS::<Projective>::prove(&pedersen_params, r_Fr, &r1cs, &w1, &ci1, &w2, &ci2).unwrap();
|
||||
let (_, ci3) =
|
||||
NIFS::<Projective>::fold_instances(r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap();
|
||||
|
||||
let ci3_verifier = NIFS::<Projective>::verify(r_Fr, &ci1, &ci2, &cmT);
|
||||
assert_eq!(ci3_verifier, ci3);
|
||||
@@ -556,6 +627,60 @@ mod tests {
|
||||
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_augmented_f_circuit folds the TestFCircuit circuit in multiple iterations, feeding the
|
||||
/// values into the AugmentedFCircuit.
|
||||
@@ -572,20 +697,21 @@ mod tests {
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
// 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 =
|
||||
AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::empty(&poseidon_config, F_circuit);
|
||||
augmented_F_circuit
|
||||
.generate_constraints(cs.clone())
|
||||
.unwrap();
|
||||
cs.finalize();
|
||||
println!("num_constraints={:?}", cs.num_constraints());
|
||||
let cs = cs.into_inner().unwrap();
|
||||
let r1cs = extract_r1cs::<Fr>(&cs);
|
||||
let z = extract_z::<Fr>(&cs); // includes 1 and public inputs
|
||||
let (w, x) = r1cs.split_z(&z);
|
||||
let F_witness_len = w.len();
|
||||
|
||||
let mut tr = PoseidonTranscript::<Projective>::new(&poseidon_config);
|
||||
assert_eq!(z.len(), r1cs.A.n_cols);
|
||||
assert_eq!(1 + x.len() + w.len(), r1cs.A.n_cols);
|
||||
assert_eq!(r1cs.l, x.len());
|
||||
|
||||
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_rows);
|
||||
|
||||
@@ -594,31 +720,29 @@ mod tests {
|
||||
let mut z_i = z_0.clone();
|
||||
let mut z_i1 = vec![Fr::from(35_u32)];
|
||||
|
||||
let w_dummy = Witness::<Projective>::new(vec![Fr::zero(); 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());
|
||||
|
||||
// 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.
|
||||
// set U_i <-- dummay instance
|
||||
// set U_i <-- dummy instance
|
||||
let mut W_i = w_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 u_i = u_dummy.clone();
|
||||
let (mut W_i1, mut U_i1, mut _T, mut cmT) = (
|
||||
w_dummy.clone(),
|
||||
u_dummy.clone(),
|
||||
vec![],
|
||||
Projective::generator(),
|
||||
);
|
||||
let (mut W_i1, mut U_i1, mut cmT): (
|
||||
Witness<Projective>,
|
||||
CommittedInstance<Projective>,
|
||||
Projective,
|
||||
) = (w_dummy.clone(), u_dummy.clone(), Projective::generator());
|
||||
// as expected, dummy instances pass the relaxed_r1cs check
|
||||
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 u_i1_x: Fr;
|
||||
let n_steps: usize = 4;
|
||||
for _ in 0..n_steps {
|
||||
for _ in 0..4 {
|
||||
if i == Fr::zero() {
|
||||
// base case: i=0, z_i=z_0, U_i = U_d := dummy instance
|
||||
// u_1.x = H(1, z_0, z_i, U_i)
|
||||
@@ -636,23 +760,17 @@ mod tests {
|
||||
U_i: Some(U_i.clone()), // = dummy
|
||||
U_i1: Some(U_i1.clone()), // = dummy
|
||||
cmT: Some(cmT),
|
||||
r: Some(Fr::one()),
|
||||
F: F_circuit,
|
||||
x: Some(u_i1_x),
|
||||
};
|
||||
} else {
|
||||
// get challenge from transcript (since this is a test, we skip absorbing values to
|
||||
// the transcript for simplicity)
|
||||
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();
|
||||
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
|
||||
|
||||
// U_{i+1}
|
||||
(W_i1, U_i1, _T, cmT) = NIFS::<Projective>::prove(
|
||||
let T: Vec<Fr>;
|
||||
(T, cmT) = NIFS::<Projective>::compute_cmT(
|
||||
&pedersen_params,
|
||||
r_Fr,
|
||||
&r1cs,
|
||||
&w_i,
|
||||
&u_i,
|
||||
@@ -661,7 +779,20 @@ mod tests {
|
||||
)
|
||||
.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)
|
||||
// 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_i1: Some(U_i1.clone()),
|
||||
cmT: Some(cmT),
|
||||
r: Some(r_Fr),
|
||||
F: F_circuit,
|
||||
x: Some(u_i1_x),
|
||||
};
|
||||
@@ -698,7 +828,7 @@ mod tests {
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().unwrap();
|
||||
// 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 (w_i1, x_i1) = r1cs.split_z(&Z_i1);
|
||||
assert_eq!(x_i1.len(), 1);
|
||||
@@ -709,8 +839,8 @@ mod tests {
|
||||
w_i = Witness::<Projective>::new(w_i1.clone(), r1cs.A.n_rows);
|
||||
u_i = w_i.commit(&pedersen_params, vec![u_i1_x]).unwrap();
|
||||
|
||||
check_instance_relation(&r1cs, &w_i, &u_i).unwrap();
|
||||
check_instance_relation(&r1cs, &W_i1, &U_i1).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
|
||||
|
||||
// set values for next iteration
|
||||
i += Fr::one();
|
||||
|
||||
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::{One, Zero};
|
||||
|
||||
use crate::ccs::r1cs::R1CS;
|
||||
use crate::folding::circuits::nonnative::point_to_nonnative_limbs;
|
||||
use crate::pedersen::{Params as PedersenParams, Pedersen};
|
||||
use crate::utils::vec::is_zero_vec;
|
||||
use crate::Error;
|
||||
|
||||
pub mod circuits;
|
||||
pub mod ivc;
|
||||
pub mod nifs;
|
||||
pub mod traits;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct CommittedInstance<C: CurveGroup> {
|
||||
@@ -52,7 +53,7 @@ where
|
||||
let (cmE_x, cmE_y) = point_to_nonnative_limbs::<C>(self.cmE)?;
|
||||
let (cmW_x, cmW_y) = point_to_nonnative_limbs::<C>(self.cmW)?;
|
||||
|
||||
Ok(CRH::<C::ScalarField>::evaluate(
|
||||
CRH::<C::ScalarField>::evaluate(
|
||||
poseidon_config,
|
||||
vec![
|
||||
vec![i],
|
||||
@@ -67,7 +68,7 @@ where
|
||||
]
|
||||
.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;
|
||||
|
||||
/// 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> {
|
||||
_phantom: PhantomData<C>,
|
||||
}
|
||||
@@ -85,36 +85,52 @@ where
|
||||
CommittedInstance::<C> { cmE, u, cmW, x }
|
||||
}
|
||||
|
||||
// NIFS.P
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn prove(
|
||||
/// NIFS.P is the consecutive combination of compute_cmT with fold_instances
|
||||
|
||||
/// compute_cmT is part of the NIFS.P logic
|
||||
pub fn compute_cmT(
|
||||
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>,
|
||||
w1: &Witness<C>,
|
||||
ci1: &CommittedInstance<C>,
|
||||
w2: &Witness<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 z2: Vec<C::ScalarField> = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat();
|
||||
|
||||
// compute cross terms
|
||||
let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?;
|
||||
let rT = C::ScalarField::one(); // use 1 as rT since we don't need hiding property for cm(T)
|
||||
let cmT = Pedersen::commit(pedersen_params, &T, &rT)?;
|
||||
// use r_T=1 since we don't need hiding property for cm(T)
|
||||
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
|
||||
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
|
||||
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(
|
||||
// r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
|
||||
r: C::ScalarField,
|
||||
@@ -135,11 +151,11 @@ where
|
||||
ci3: &CommittedInstance<C>,
|
||||
cmT: &C,
|
||||
) -> Result<(), Error> {
|
||||
let r2 = r * r;
|
||||
if ci3.cmE != (ci1.cmE + cmT.mul(r) + ci2.cmE.mul(r2))
|
||||
|| ci3.u != ci1.u + r * ci2.u
|
||||
|| ci3.cmW != (ci1.cmW + ci2.cmW.mul(r))
|
||||
|| ci3.x != vec_add(&ci1.x, &vec_scalar_mul(&ci2.x, &r))?
|
||||
let expected = Self::fold_committed_instance(r, ci1, ci2, cmT);
|
||||
if ci3.cmE != expected.cmE
|
||||
|| ci3.u != expected.u
|
||||
|| ci3.cmW != expected.cmW
|
||||
|| ci3.x != expected.x
|
||||
{
|
||||
return Err(Error::NotSatisfied);
|
||||
}
|
||||
@@ -168,7 +184,7 @@ where
|
||||
) -> Result<(), Error> {
|
||||
if cm_proofs.len() != 3 {
|
||||
// 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.cmW, cm_proofs[1].clone())?;
|
||||
@@ -222,9 +238,11 @@ pub mod tests {
|
||||
|
||||
let r_Fr = Fr::from(3_u32);
|
||||
|
||||
let (W_i1, U_i1, _, _) =
|
||||
NIFS::<Projective>::prove(&pedersen_params, r_Fr, &r1cs, &w_i, &u_i, &W_i, &U_i)
|
||||
let (T, cmT) =
|
||||
NIFS::<Projective>::compute_cmT(&pedersen_params, &r1cs, &w_i, &u_i, &W_i, &U_i)
|
||||
.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();
|
||||
assert_eq!(z.len(), z1.len());
|
||||
@@ -253,8 +271,10 @@ pub mod tests {
|
||||
let ci2 = w2.commit(&pedersen_params, x2.clone()).unwrap();
|
||||
|
||||
// NIFS.P
|
||||
let (w3, ci3_aux, T, cmT) =
|
||||
NIFS::<Projective>::prove(&pedersen_params, r, &r1cs, &w1, &ci1, &w2, &ci2).unwrap();
|
||||
let (T, cmT) =
|
||||
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
|
||||
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
|
||||
|
||||
// NIFS.P
|
||||
let (folded_w, _, _, cmT) = NIFS::<Projective>::prove(
|
||||
let (T, cmT) = NIFS::<Projective>::compute_cmT(
|
||||
&pedersen_params,
|
||||
r,
|
||||
&r1cs,
|
||||
&running_instance_w,
|
||||
&running_committed_instance,
|
||||
@@ -358,6 +377,16 @@ pub mod tests {
|
||||
&incomming_committed_instance,
|
||||
)
|
||||
.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
|
||||
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 {
|
||||
#[error("ark_relations::r1cs::SynthesisError")]
|
||||
SynthesisError(#[from] ark_relations::r1cs::SynthesisError),
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
|
||||
#[error("Relation not satisfied")]
|
||||
NotSatisfied,
|
||||
#[error("Not equal")]
|
||||
NotEqual,
|
||||
#[error("Vectors should have the same length")]
|
||||
NotSameLength,
|
||||
#[error("Vector's length is not the expected")]
|
||||
NotExpectedLength,
|
||||
#[error("Vectors should have the same length ({0}, {1})")]
|
||||
NotSameLength(usize, usize),
|
||||
#[error("Vector's length ({0}) is not the expected ({1})")]
|
||||
NotExpectedLength(usize, usize),
|
||||
#[error("Can not be empty")]
|
||||
Empty,
|
||||
#[error("Pedersen parameters length is not suficient")]
|
||||
PedersenParamsLen,
|
||||
#[error("Pedersen parameters length is not suficient (generators.len={0} < vector.len={1} unsatisfied)")]
|
||||
PedersenParamsLen(usize, usize),
|
||||
#[error("Pedersen verification failed")]
|
||||
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
|
||||
|
||||
@@ -43,7 +43,7 @@ impl<C: CurveGroup> Pedersen<C> {
|
||||
r: &C::ScalarField,
|
||||
) -> Result<C, Error> {
|
||||
if params.generators.len() < v.len() {
|
||||
return Err(Error::PedersenParamsLen);
|
||||
return Err(Error::PedersenParamsLen(params.generators.len(), v.len()));
|
||||
}
|
||||
// h⋅r + <g, v>
|
||||
// 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,
|
||||
) -> Result<Proof<C>, Error> {
|
||||
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 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
|
||||
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();
|
||||
|
||||
// u = d + v⋅e
|
||||
@@ -87,13 +87,16 @@ impl<C: CurveGroup> Pedersen<C> {
|
||||
proof: Proof<C>,
|
||||
) -> Result<(), Error> {
|
||||
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_challenges(proof.u.len()); // d
|
||||
transcript.absorb_point(&proof.R);
|
||||
transcript.absorb_point(&proof.R)?;
|
||||
let e = transcript.get_challenge();
|
||||
|
||||
// check that: R + cm == h⋅r_u + <g, u>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::Error;
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_std::fmt::Debug;
|
||||
|
||||
@@ -9,7 +10,7 @@ pub trait Transcript<C: CurveGroup> {
|
||||
fn new(config: &Self::TranscriptConfig) -> Self;
|
||||
fn absorb(&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;
|
||||
/// get_challenge_nbits returns a field element of size nbits
|
||||
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_r1cs_std::{boolean::Boolean, fields::fp::FpVar};
|
||||
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
||||
use ark_std::{One, Zero};
|
||||
|
||||
use crate::transcript::Transcript;
|
||||
use crate::Error;
|
||||
|
||||
/// PoseidonTranscript implements the Transcript trait using the Poseidon hash
|
||||
pub struct PoseidonTranscript<C: CurveGroup>
|
||||
@@ -34,8 +36,9 @@ where
|
||||
fn absorb_vec(&mut self, v: &[C::ScalarField]) {
|
||||
self.sponge.absorb(&v);
|
||||
}
|
||||
fn absorb_point(&mut self, p: &C) {
|
||||
self.sponge.absorb(&prepare_point(p));
|
||||
fn absorb_point(&mut self, p: &C) -> Result<(), Error> {
|
||||
self.sponge.absorb(&prepare_point(p)?);
|
||||
Ok(())
|
||||
}
|
||||
fn get_challenge(&mut self) -> C::ScalarField {
|
||||
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
|
||||
// over bytes in order to have a logic that can be reproduced in-circuit.
|
||||
fn prepare_point<C: CurveGroup>(p: &C) -> Vec<C::ScalarField> {
|
||||
let binding = p.into_affine();
|
||||
let p_coords = &binding.xy().unwrap();
|
||||
let x_bi = p_coords
|
||||
.0
|
||||
.to_base_prime_field_elements()
|
||||
.next()
|
||||
.expect("a")
|
||||
.into_bigint();
|
||||
let y_bi = p_coords
|
||||
.1
|
||||
.to_base_prime_field_elements()
|
||||
.next()
|
||||
.expect("a")
|
||||
.into_bigint();
|
||||
vec![
|
||||
fn prepare_point<C: CurveGroup>(p: &C) -> Result<Vec<C::ScalarField>, Error> {
|
||||
let affine = p.into_affine();
|
||||
let xy_obj = &affine.xy();
|
||||
let mut xy = (&C::BaseField::zero(), &C::BaseField::one());
|
||||
if xy_obj.is_some() {
|
||||
xy = xy_obj.unwrap();
|
||||
}
|
||||
let x_bi =
|
||||
xy.0.to_base_prime_field_elements()
|
||||
.next()
|
||||
.expect("a")
|
||||
.into_bigint();
|
||||
let y_bi =
|
||||
xy.1.to_base_prime_field_elements()
|
||||
.next()
|
||||
.expect("a")
|
||||
.into_bigint();
|
||||
Ok(vec![
|
||||
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()),
|
||||
]
|
||||
])
|
||||
}
|
||||
|
||||
/// PoseidonTranscriptVar implements the gadget compatible with PoseidonTranscript
|
||||
@@ -166,7 +171,7 @@ pub mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_transcript_and_transcriptvar_nbits() {
|
||||
let nbits = crate::constants::N_BITS_CHALLENGE;
|
||||
let nbits = 128;
|
||||
|
||||
// use 'native' transcript
|
||||
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> {
|
||||
let mut weights = points
|
||||
.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> {
|
||||
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())
|
||||
}
|
||||
|
||||
pub fn vec_sub<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
|
||||
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())
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
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()];
|
||||
@@ -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> {
|
||||
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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user