Browse Source

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)
main
arnaucube 1 year ago
committed by GitHub
parent
commit
905ba44d8d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 663 additions and 174 deletions
  1. +1
    -1
      rust-toolchain
  2. +3
    -1
      src/constants.rs
  3. +2
    -2
      src/decider/circuit.rs
  4. +18
    -24
      src/folding/circuits/nonnative.rs
  5. +198
    -68
      src/folding/nova/circuits.rs
  6. +261
    -0
      src/folding/nova/ivc.rs
  7. +4
    -16
      src/folding/nova/mod.rs
  8. +53
    -24
      src/folding/nova/nifs.rs
  9. +67
    -0
      src/folding/nova/traits.rs
  10. +14
    -6
      src/lib.rs
  11. +10
    -7
      src/pedersen.rs
  12. +2
    -1
      src/transcript/mod.rs
  13. +25
    -20
      src/transcript/poseidon.rs
  14. +1
    -0
      src/utils/espresso/sum_check/prover.rs
  15. +4
    -4
      src/utils/vec.rs

+ 1
- 1
rust-toolchain

@ -1 +1 @@
1.71.1
1.73.0

+ 3
- 1
src/constants.rs

@ -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;

+ 2
- 2
src/decider/circuit.rs

@ -48,7 +48,7 @@ pub fn vec_add(
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(
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() {

+ 18
- 24
src/folding/circuits/nonnative.rs

@ -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
/// 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)] #[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 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 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( 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();
} }
} }

+ 198
- 68
src/folding/nova/circuits.rs

@ -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 = <::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>,
cmW: NonNativeAffineVar<CF2<C>, C::ScalarField>,
cmE: NonNativeAffineVar<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.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(); .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>> {
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 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> { 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 { Self {
poseidon_config: poseidon_config.clone(), poseidon_config: poseidon_config.clone(),
i: None, i: None,
@ -259,13 +263,77 @@ impl>> AugmentedFCircuit {
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 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 = 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::{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::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) =
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); 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();
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); 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) = (
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 // 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..n_steps {
for _ in 0..4 {
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
// 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} // 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();
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 // set values for next iteration
i += Fr::one(); i += Fr::one();

+ 261
- 0
src/folding/nova/ivc.rs

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

+ 4
- 16
src/folding/nova/mod.rs

@ -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)
}

+ 53
- 24
src/folding/nova/nifs.rs

@ -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
#[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>, 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)
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 // 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;
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); 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, _, _) =
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(); .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) =
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 // 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
- 0
src/folding/nova/traits.rs

@ -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)
}
}

+ 14
- 6
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")]
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")] #[error("Can not be empty")]
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")] #[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

+ 10
- 7
src/pedersen.rs

@ -43,7 +43,7 @@ impl Pedersen {
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 Pedersen {
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 Pedersen {
// 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(&params.generators[..d.len()], &d); let R: C = params.h.mul(r1) + C::msm_unchecked(&params.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 Pedersen {
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>

+ 2
- 1
src/transcript/mod.rs

@ -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 {
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>;

+ 25
- 20
src/transcript/poseidon.rs

@ -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) {
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 { 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> {
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(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>();

+ 1
- 0
src/utils/espresso/sum_check/prover.rs

@ -184,6 +184,7 @@ impl SumCheckProver for IOPProverState {
} }
} }
#[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()

+ 4
- 4
src/utils/vec.rs

@ -46,14 +46,14 @@ pub fn dense_matrix_to_sparse(m: Vec>) -> SparseMatrix
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(M: &Vec>, z: &[F]) -> Result, 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(matrix: &SparseMatrix, 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())
} }

Loading…
Cancel
Save