From 905ba44d8dd7700e5f044b5c0137c015f31cbc0f Mon Sep 17 00:00:00 2001 From: arnaucube Date: Fri, 24 Nov 2023 11:15:14 +0100 Subject: [PATCH] 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) --- rust-toolchain | 2 +- src/constants.rs | 4 +- src/decider/circuit.rs | 4 +- src/folding/circuits/nonnative.rs | 42 ++-- src/folding/nova/circuits.rs | 266 ++++++++++++++++++------- src/folding/nova/ivc.rs | 261 ++++++++++++++++++++++++ src/folding/nova/mod.rs | 20 +- src/folding/nova/nifs.rs | 77 ++++--- src/folding/nova/traits.rs | 67 +++++++ src/lib.rs | 20 +- src/pedersen.rs | 17 +- src/transcript/mod.rs | 3 +- src/transcript/poseidon.rs | 45 +++-- src/utils/espresso/sum_check/prover.rs | 1 + src/utils/vec.rs | 8 +- 15 files changed, 663 insertions(+), 174 deletions(-) create mode 100644 src/folding/nova/ivc.rs create mode 100644 src/folding/nova/traits.rs diff --git a/rust-toolchain b/rust-toolchain index 68bc7ff..5e3a425 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.71.1 +1.73.0 diff --git a/src/constants.rs b/src/constants.rs index b2dc013..597fabb 100644 --- a/src/constants.rs +++ b/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; diff --git a/src/decider/circuit.rs b/src/decider/circuit.rs index 61b979c..a6c3036 100644 --- a/src/decider/circuit.rs +++ b/src/decider/circuit.rs @@ -48,7 +48,7 @@ pub fn vec_add( b: &Vec>, ) -> Result>, Error> { if a.len() != b.len() { - return Err(Error::NotSameLength); + return Err(Error::NotSameLength(a.len(), b.len())); } let mut r: Vec> = vec![FpVar::::zero(); a.len()]; for i in 0..a.len() { @@ -68,7 +68,7 @@ pub fn hadamard( b: &Vec>, ) -> Result>, Error> { if a.len() != b.len() { - return Err(Error::NotSameLength); + return Err(Error::NotSameLength(a.len(), b.len())); } let mut r: Vec> = vec![FpVar::::zero(); a.len()]; for i in 0..a.len() { diff --git a/src/folding/circuits/nonnative.rs b/src/folding/circuits/nonnative.rs index 08e1545..3424067 100644 --- a/src/folding/circuits/nonnative.rs +++ b/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 { - pub x: NonNativeFieldVar, - pub y: NonNativeFieldVar, +pub struct NonNativeAffineVar { + pub x: Vec>, + pub y: Vec>, } -impl AllocVar for NonNativeAffineVar +impl AllocVar for NonNativeAffineVar where C: 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::::new_variable( - cs.clone(), - || Ok(C::BaseField::zero()), - mode, - )?; - let y = NonNativeFieldVar::::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::::new_variable( cs.clone(), || Ok(xy.0), mode, - )?; + )? + .to_constraint_field()?; let y = NonNativeFieldVar::::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::::new_witness(cs.clone(), || Ok(p)).unwrap(); + NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p)).unwrap(); } } diff --git a/src/folding/nova/circuits.rs b/src/folding/nova/circuits.rs index b07d9b4..9e4ba02 100644 --- a/src/folding/nova/circuits.rs +++ b/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 { u: FpVar, x: Vec>, - cmE: NonNativeAffineVar, C::ScalarField>, - cmW: NonNativeAffineVar, C::ScalarField>, + cmE: NonNativeAffineVar, + cmW: NonNativeAffineVar, } impl AllocVar, CF1> for CommittedInstanceVar @@ -57,12 +60,12 @@ where let x: Vec> = Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; - let cmE = NonNativeAffineVar::, C::ScalarField>::new_variable( + let cmE = NonNativeAffineVar::::new_variable( cs.clone(), || Ok(val.borrow().cmE), mode, )?; - let cmW = NonNativeAffineVar::, C::ScalarField>::new_variable( + let cmW = NonNativeAffineVar::::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::::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: 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>, pub U_i1: Option>, pub cmT: Option, - pub r: Option, // 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>, // public inputs (u_{i+1}.x) + pub F: FC, // F circuit + pub x: Option>, // public inputs (u_{i+1}.x) } impl>> AugmentedFCircuit { - #[allow(dead_code)] // TMP while IVC does not use this method - fn empty(poseidon_config: &PoseidonConfig>, F_circuit: FC) -> Self { + pub fn empty(poseidon_config: &PoseidonConfig>, 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>> AugmentedFCircuit +where + C: CurveGroup, + ::BaseField: PrimeField, + ::ScalarField: Absorb, +{ + pub fn get_challenge_native( + poseidon_config: &PoseidonConfig, + u_i: CommittedInstance, + U_i: CommittedInstance, + cmT: C, + ) -> Result { + let (u_cmE_x, u_cmE_y) = point_to_nonnative_limbs::(u_i.cmE)?; + let (u_cmW_x, u_cmW_y) = point_to_nonnative_limbs::(u_i.cmW)?; + let (U_cmE_x, U_cmE_y) = point_to_nonnative_limbs::(U_i.cmE)?; + let (U_cmW_x, U_cmW_y) = point_to_nonnative_limbs::(U_i.cmW)?; + let (cmT_x, cmT_y) = point_to_nonnative_limbs::(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::::evaluate(poseidon_config, input).unwrap()) + } + + pub fn get_challenge( + crh_params: &CRHParametersVar, + u_i: CommittedInstanceVar, + U_i: CommittedInstanceVar, + cmT: NonNativeAffineVar, + ) -> Result, 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::::evaluate(crh_params, &input) + } +} + impl>> ConstraintSynthesizer> for AugmentedFCircuit where C: CurveGroup, @@ -282,15 +350,7 @@ where Ok(self.z_i.unwrap_or_else(|| vec![CF1::::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::::zero()], - }; + let u_dummy_native = CommittedInstance::::dummy(1); let u_dummy = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(u_dummy_native.clone()))?; @@ -303,16 +363,17 @@ where let U_i1 = CommittedInstanceVar::::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::>::new_witness(cs.clone(), || Ok(self.r.unwrap_or_else(CF1::::zero)))?; // r will come from higher level transcript let x = FpVar::>::new_input(cs.clone(), || Ok(self.x.unwrap_or_else(CF1::::zero)))?; let crh_params = CRHParametersVar::::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::>::new_constant(cs.clone(), CF1::::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::::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, } impl FCircuit for TestFCircuit { + fn new() -> Self { + Self { _f: PhantomData } + } fn step_native(self, z_i: Vec) -> Vec { 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::::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::(); let mut tr = PoseidonTranscript::::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::::prove(&pedersen_params, r_Fr, &r1cs, &w1, &ci1, &w2, &ci2).unwrap(); + let (_, ci3) = + NIFS::::fold_instances(r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap(); let ci3_verifier = NIFS::::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::(); + + let u_i = CommittedInstance:: { + 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:: { + 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::>::get_challenge_native( + &poseidon_config, + u_i.clone(), + U_i.clone(), + cmT, + ) + .unwrap(); + + let cs = ConstraintSystem::::new_ref(); + let u_iVar = + CommittedInstanceVar::::new_witness(cs.clone(), || Ok(u_i.clone())) + .unwrap(); + let U_iVar = + CommittedInstanceVar::::new_witness(cs.clone(), || Ok(U_i.clone())) + .unwrap(); + let cmTVar = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(cmT)).unwrap(); + + let crh_params = CRHParametersVar::::new_constant(cs.clone(), poseidon_config).unwrap(); + + // compute the challenge in-circuit + let rVar = AugmentedFCircuit::>::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::::new_ref(); // prepare the circuit to obtain its R1CS - let F_circuit = TestFCircuit:: { _f: PhantomData }; + let F_circuit = TestFCircuit::::new(); let mut augmented_F_circuit = AugmentedFCircuit::>::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::(&cs); let z = extract_z::(&cs); // includes 1 and public inputs let (w, x) = r1cs.split_z(&z); - let F_witness_len = w.len(); - - let mut tr = PoseidonTranscript::::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::::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::::new(vec![Fr::zero(); F_witness_len], r1cs.A.n_rows); + let w_dummy = Witness::::new(vec![Fr::zero(); w.len()], r1cs.A.n_rows); let u_dummy = CommittedInstance::::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, + CommittedInstance, + 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::::prove( + let T: Vec; + (T, cmT) = NIFS::::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::>::get_challenge_native( + &poseidon_config, + u_i.clone(), + U_i.clone(), + cmT, + ) + .unwrap(); + + (W_i1, U_i1) = + NIFS::::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::(&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::::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(); diff --git a/src/folding/nova/ivc.rs b/src/folding/nova/ivc.rs new file mode 100644 index 0000000..56a3ede --- /dev/null +++ b/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 +where + C1: CurveGroup, + C2: CurveGroup, + FC: FCircuit, +{ + _c2: PhantomData, + r1cs: R1CS, + poseidon_config: PoseidonConfig, + pedersen_params: PedersenParams, + F: FC, // F circuit + i: C1::ScalarField, + z_0: Vec, + z_i: Vec, + w_i: Witness, + u_i: CommittedInstance, + W_i: Witness, + U_i: CommittedInstance, +} + +impl IVC +where + C1: CurveGroup, + C2: CurveGroup, + FC: FCircuit, + ::BaseField: PrimeField, + ::ScalarField: Absorb, +{ + /// Initializes the IVC for the given parameters and initial state `z_0`. + pub fn new( + rng: &mut R, + poseidon_config: PoseidonConfig, + F: FC, + z_0: Vec, + ) -> Result { + // prepare the circuit to obtain its R1CS + let cs = ConstraintSystem::::new_ref(); + + let augmented_F_circuit = AugmentedFCircuit::::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::(&cs); + + let pedersen_params = Pedersen::::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; + let z_i1 = self.F.step_native(self.z_i.clone()); + + let (W_i1, U_i1, cmT): (Witness, CommittedInstance, 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:: { + 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; + (T, cmT) = NIFS::::compute_cmT( + &self.pedersen_params, + &self.r1cs, + &self.w_i, + &self.u_i, + &self.W_i, + &self.U_i, + )?; + + let r_Fr = AugmentedFCircuit::::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::::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:: { + 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::::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::(&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::::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, 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::(); + + let F_circuit = TestFCircuit::::new(); + let z_0 = vec![Fr::from(3_u32)]; + + let mut ivc = IVC::>::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(); + } +} diff --git a/src/folding/nova/mod.rs b/src/folding/nova/mod.rs index 3fd3144..9c09ce6 100644 --- a/src/folding/nova/mod.rs +++ b/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 { @@ -52,7 +53,7 @@ where let (cmE_x, cmE_y) = point_to_nonnative_limbs::(self.cmE)?; let (cmW_x, cmW_y) = point_to_nonnative_limbs::(self.cmW)?; - Ok(CRH::::evaluate( + CRH::::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( - r1cs: &R1CS, - W: &Witness, - U: &CommittedInstance, -) -> Result<(), Error> { - let mut rel_r1cs = r1cs.clone().relax(); - rel_r1cs.u = U.u; - rel_r1cs.E = W.E.clone(); - - let Z: Vec = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat(); - rel_r1cs.check_relation(&Z) -} diff --git a/src/folding/nova/nifs.rs b/src/folding/nova/nifs.rs index 06c8f5d..5b36ab6 100644 --- a/src/folding/nova/nifs.rs +++ b/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 { _phantom: PhantomData, } @@ -85,36 +85,52 @@ where CommittedInstance:: { 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, - // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element - r: C::ScalarField, r1cs: &R1CS, w1: &Witness, ci1: &CommittedInstance, w2: &Witness, ci2: &CommittedInstance, - ) -> Result<(Witness, CommittedInstance, Vec, C), Error> { + ) -> Result<(Vec, C), Error> { let z1: Vec = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat(); let z2: Vec = [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, + ci1: &CommittedInstance, + w2: &Witness, + ci2: &CommittedInstance, + T: &[C::ScalarField], + cmT: C, + ) -> Result<(Witness, CommittedInstance), Error> { // fold witness - let w3 = NIFS::::fold_witness(r, w1, w2, &T, rT)?; + // use r_T=1 since we don't need hiding property for cm(T) + let w3 = NIFS::::fold_witness(r, w1, w2, T, C::ScalarField::one())?; // fold committed instancs let ci3 = NIFS::::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, 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::::prove(&pedersen_params, r_Fr, &r1cs, &w_i, &u_i, &W_i, &U_i) + let (T, cmT) = + NIFS::::compute_cmT(&pedersen_params, &r1cs, &w_i, &u_i, &W_i, &U_i) .unwrap(); + let (W_i1, U_i1) = + NIFS::::fold_instances(r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT).unwrap(); let z: Vec = [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::::prove(&pedersen_params, r, &r1cs, &w1, &ci1, &w2, &ci2).unwrap(); + let (T, cmT) = + NIFS::::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2).unwrap(); + let (w3, ci3_aux) = + NIFS::::fold_instances(r, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap(); // NIFS.V let ci3 = NIFS::::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::::prove( + let (T, cmT) = NIFS::::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::::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::::verify( diff --git a/src/folding/nova/traits.rs b/src/folding/nova/traits.rs new file mode 100644 index 0000000..6b50da8 --- /dev/null +++ b/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 { + /// returns a dummy instance (Witness and CommittedInstance) for the current R1CS structure + fn dummy_instance(&self) -> (Witness, CommittedInstance); + + /// checks the R1CS relation (un-relaxed) for the given Witness and CommittedInstance. + fn check_instance_relation( + &self, + W: &Witness, + U: &CommittedInstance, + ) -> 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, + U: &CommittedInstance, + ) -> Result<(), Error>; +} + +impl NovaR1CS for R1CS +where + ::ScalarField: Absorb, + ::BaseField: ark_ff::PrimeField, +{ + fn dummy_instance(&self) -> (Witness, CommittedInstance) { + let w_len = self.A.n_cols - 1 - self.l; + let w_dummy = Witness::::new(vec![C::ScalarField::zero(); w_len], self.A.n_rows); + let u_dummy = CommittedInstance::::dummy(self.l); + (w_dummy, u_dummy) + } + + fn check_instance_relation( + &self, + W: &Witness, + U: &CommittedInstance, + ) -> Result<(), Error> { + if U.cmE != C::zero() || U.u != C::ScalarField::one() { + return Err(Error::R1CSUnrelaxedFail); + } + + let Z: Vec = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat(); + self.check_relation(&Z) + } + + fn check_relaxed_instance_relation( + &self, + W: &Witness, + U: &CommittedInstance, + ) -> Result<(), Error> { + let mut rel_r1cs = self.clone().relax(); + rel_r1cs.u = U.u; + rel_r1cs.E = W.E.clone(); + + let Z: Vec = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat(); + rel_r1cs.check_relation(&Z) + } +} diff --git a/src/lib.rs b/src/lib.rs index 3560eb3..b6ffd50 100644 --- a/src/lib.rs +++ b/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 diff --git a/src/pedersen.rs b/src/pedersen.rs index 823ec74..b10665d 100644 --- a/src/pedersen.rs +++ b/src/pedersen.rs @@ -43,7 +43,7 @@ impl Pedersen { r: &C::ScalarField, ) -> Result { if params.generators.len() < v.len() { - return Err(Error::PedersenParamsLen); + return Err(Error::PedersenParamsLen(params.generators.len(), v.len())); } // h⋅r + // use msm_unchecked because we already ensured at the if that lengths match @@ -58,10 +58,10 @@ impl Pedersen { r: &C::ScalarField, ) -> Result, 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(¶ms.generators[..d.len()], &d); - transcript.absorb_point(&R); + transcript.absorb_point(&R)?; let e = transcript.get_challenge(); // u = d + v⋅e @@ -87,13 +87,16 @@ impl Pedersen { proof: Proof, ) -> 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 + diff --git a/src/transcript/mod.rs b/src/transcript/mod.rs index ccfc59c..51f6eb7 100644 --- a/src/transcript/mod.rs +++ b/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; diff --git a/src/transcript/poseidon.rs b/src/transcript/poseidon.rs index 67d72ab..efb5082 100644 --- a/src/transcript/poseidon.rs +++ b/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 @@ -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(p: &C) -> Vec { - 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(p: &C) -> Result, 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::(); diff --git a/src/utils/espresso/sum_check/prover.rs b/src/utils/espresso/sum_check/prover.rs index 3b57283..d43cc65 100644 --- a/src/utils/espresso/sum_check/prover.rs +++ b/src/utils/espresso/sum_check/prover.rs @@ -184,6 +184,7 @@ impl SumCheckProver for IOPProverState { } } +#[allow(clippy::filter_map_bool_then)] fn barycentric_weights(points: &[F]) -> Vec { let mut weights = points .iter() diff --git a/src/utils/vec.rs b/src/utils/vec.rs index 341c548..11feb29 100644 --- a/src/utils/vec.rs +++ b/src/utils/vec.rs @@ -46,14 +46,14 @@ pub fn dense_matrix_to_sparse(m: Vec>) -> SparseMatrix pub fn vec_add(a: &[F], b: &[F]) -> Result, 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(a: &[F], b: &[F]) -> Result, 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 = vec![F::zero(); M.len()]; @@ -95,7 +95,7 @@ pub fn mat_vec_mul_sparse(matrix: &SparseMatrix, vector: &[F]) pub fn hadamard(a: &[F], b: &[F]) -> Result, 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()) }