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 10 months 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>>,
) -> 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(
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() {

+ 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::{
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();
}
}

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

@ -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 = <::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>> {
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>> AugmentedFCircuit {
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
- 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::{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)
}

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

@ -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
- 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 {
#[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

+ 10
- 7
src/pedersen.rs

@ -43,7 +43,7 @@ impl Pedersen {
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 Pedersen {
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 Pedersen {
// 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);
transcript.absorb_point(&R);
transcript.absorb_point(&R)?;
let e = transcript.get_challenge();
// u = d + v⋅e
@ -87,13 +87,16 @@ impl Pedersen {
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>

+ 2
- 1
src/transcript/mod.rs

@ -1,3 +1,4 @@
use crate::Error;
use ark_ec::CurveGroup;
use ark_std::fmt::Debug;
@ -9,7 +10,7 @@ pub trait Transcript {
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>;

+ 25
- 20
src/transcript/poseidon.rs

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

+ 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> {
let mut weights = points
.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> {
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(M: &Vec>, z: &[F]) -> Result, 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(matrix: &SparseMatrix, 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())
}

Loading…
Cancel
Save