arnaucube
arnaucube/sonobePublic Self-hosted repository
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)
arnaucube root@arnaucube.com
Fri, 24 Nov 2023 11:15:14 +0100
commit

905ba44d8dd7700e5f044b5c0137c015f31cbc0f

parent

6d919d7a5bbb8d1339ccd7a29958d797d9861fda

M rust-toolchain → rust-toolchain

@@ -1,1 +1,1 @@

-1.71.1 +1.73.0
M src/constants.rs → src/constants.rs

@@ -1,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;
M src/decider/circuit.rs → src/decider/circuit.rs

@@ -48,7 +48,7 @@ a: &Vec<FpVar<F>>,

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 @@ a: &Vec<FpVar<F>>,

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() {
M src/folding/circuits/nonnative.rs → 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 @@ f().and_then(|val| {

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

#[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 @@ let cs = ConstraintSystem::<Fr>::new_ref();

// 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(); } }
M src/folding/nova/circuits.rs → 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 @@ eq::EqGadget,

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 @@ #[derive(Debug, Clone)]

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 @@ let u = FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;

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 @@ z_0,

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

/// 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 u_i: Option<CommittedInstance<C>>,

pub U_i: Option<CommittedInstance<C>>, pub U_i1: Option<CommittedInstance<C>>, pub cmT: Option<C>, - pub r: Option<C::ScalarField>, // This will not be an input and derived from a hash internally in the circuit (poseidon transcript) - pub F: FC, // F circuit - pub x: Option<CF1<C>>, // public inputs (u_{i+1}.x) + pub F: FC, // F circuit + pub x: Option<CF1<C>>, // public inputs (u_{i+1}.x) } impl<C: CurveGroup, FC: FCircuit<CF1<C>>> AugmentedFCircuit<C, FC> { - #[allow(dead_code)] // TMP while IVC does not use this method - fn empty(poseidon_config: &PoseidonConfig<CF1<C>>, F_circuit: FC) -> Self { + pub fn empty(poseidon_config: &PoseidonConfig<CF1<C>>, F_circuit: FC) -> Self { Self { poseidon_config: poseidon_config.clone(), i: None,

@@ -259,13 +263,77 @@ u_i: None,

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 @@ let z_i = Vec::<FpVar<CF1<C>>>::new_witness(cs.clone(), || {

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

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 @@ .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}. + // 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 @@ }

} #[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 @@ use ark_std::UniformRand;

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 @@ pub struct TestFCircuit<F: PrimeField> {

_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 @@ // compute committed instances

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 @@ // check that the natively computed and in-circuit computed hashes match

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 @@ // compute z vector for the initial instance

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 @@ let z_0 = vec![Fr::from(3_u32)];

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 @@ u_i: Some(u_i.clone()), // = dummy

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 @@ &U_i,

) .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 @@ u_i: Some(u_i),

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

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 @@ // assign them directly to w_i, u_i.

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();
A 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(); + } +}
M src/folding/nova/mod.rs → 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 @@ ) -> Result<C::ScalarField, Error> {

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 @@ cmW_y,

] .concat(), ) - .unwrap()) + .map_err(|e| Error::Other(e.to_string())) } }

@@ -109,16 +110,3 @@ x,

}) } } - -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) -}
M src/folding/nova/nifs.rs → 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 @@

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 @@ ci2: &CommittedInstance<C>,

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 @@ cm_proofs: [PedersenProof<C>; 3],

) -> 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 @@ assert!(check_relaxed_r1cs(&r1cs, &z_dummy, &U_i.u, &W_i.E));

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 @@ let ci1 = w1.commit(&pedersen_params, x1.clone()).unwrap();

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,14 +368,23 @@

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, + &r1cs, + &running_instance_w, + &running_committed_instance, + &incomming_instance_w, + &incomming_committed_instance, + ) + .unwrap(); + let (folded_w, _) = NIFS::<Projective>::fold_instances( r, - &r1cs, &running_instance_w, &running_committed_instance, &incomming_instance_w, &incomming_committed_instance, + &T, + cmT, ) .unwrap();
A 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) + } +}
M src/lib.rs → src/lib.rs

@@ -20,21 +20,29 @@ #[derive(Debug, Error, PartialEq)]

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
M src/pedersen.rs → src/pedersen.rs

@@ -43,7 +43,7 @@ v: &Vec<C::ScalarField>,

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 @@ v: &Vec<C::ScalarField>,

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 @@ // R = h⋅r_1 + <g, d>

// 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 @@ cm: C,

proof: Proof<C>, ) -> Result<(), Error> { if params.generators.len() < proof.u.len() { - return Err(Error::PedersenParamsLen); + return Err(Error::PedersenParamsLen( + params.generators.len(), + proof.u.len(), + )); } - transcript.absorb_point(&cm); + transcript.absorb_point(&cm)?; transcript.get_challenge(); // r_1 transcript.get_challenges(proof.u.len()); // d - transcript.absorb_point(&proof.R); + transcript.absorb_point(&proof.R)?; let e = transcript.get_challenge(); // check that: R + cm == h⋅r_u + <g, u>
M src/transcript/mod.rs → src/transcript/mod.rs

@@ -1,3 +1,4 @@

+use crate::Error; use ark_ec::CurveGroup; use ark_std::fmt::Debug;

@@ -9,7 +10,7 @@

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>;
M src/transcript/poseidon.rs → 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 @@ }

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

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

#[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>();
M src/utils/espresso/sum_check/prover.rs → src/utils/espresso/sum_check/prover.rs

@@ -184,6 +184,7 @@ })

} } +#[allow(clippy::filter_map_bool_then)] fn barycentric_weights<F: PrimeField>(points: &[F]) -> Vec<F> { let mut weights = points .iter()
M src/utils/vec.rs → src/utils/vec.rs

@@ -46,14 +46,14 @@ }

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 @@ if M.is_empty() {

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