From a1a963fd0b3a1488c8a63e7b09cca7a0c31c664d Mon Sep 17 00:00:00 2001 From: arnaucube Date: Sat, 13 May 2023 16:15:30 +0200 Subject: [PATCH] IVC proofs wip: phi hash CS & native impl matching, add test for augmented F --- src/circuits.rs | 323 +++++++++++++++++++++++++++++++++++++++++------- src/ivc.rs | 23 ++-- src/nifs.rs | 6 +- src/sumcheck.rs | 4 +- 4 files changed, 301 insertions(+), 55 deletions(-) diff --git a/src/circuits.rs b/src/circuits.rs index d40feab..accda7d 100644 --- a/src/circuits.rs +++ b/src/circuits.rs @@ -1,16 +1,20 @@ -use ark_ec::CurveGroup; -use ark_ff::{Field, PrimeField}; +use ark_ec::{AffineRepr, CurveGroup, Group}; +use ark_ff::{BigInteger, Field, PrimeField, ToConstraintField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, + bits::uint8::UInt8, boolean::Boolean, eq::EqGadget, fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar}, groups::GroupOpsBounds, prelude::CurveVar, ToBitsGadget, + ToBytesGadget, ToConstraintFieldGadget, // groups::curves::short_weierstrass::ProjectiveVar, }; +use ark_serialize::CanonicalSerialize; +use ark_std::{One, Zero}; // use ark_r1cs_std::groups::curves::twisted_edwards::AffineVar; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; @@ -20,10 +24,12 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, // }; // use ark_crypto_primitives::crh::{CRHScheme, CRHSchemeGadget}; // use ark_crypto_primitives::snark::{FromFieldElementsGadget, SNARKGadget, SNARK}; -use ark_crypto_primitives::sponge::constraints::CryptographicSpongeVar; use ark_crypto_primitives::sponge::poseidon::{ constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge, }; +use ark_crypto_primitives::sponge::{ + constraints::CryptographicSpongeVar, Absorb, CryptographicSponge, +}; use core::{borrow::Borrow, marker::PhantomData}; use derivative::Derivative; @@ -120,9 +126,16 @@ where } } -use ark_crypto_primitives::sponge::Absorb; -pub struct AugmentedFCircuit>> -where +pub trait FCircuit: ConstraintSynthesizer + Copy { + // method that returns z_i (input), z_i1 (output) + fn public(self) -> (F, F); +} + +pub struct AugmentedFCircuit< + C: CurveGroup, + GC: CurveVar>, + FC: FCircuit>, +> where <::BaseField as Field>::BasePrimeField: Absorb, { pub _c: PhantomData, @@ -130,19 +143,21 @@ where // pub poseidon_native: PoseidonSponge>, pub poseidon_config: PoseidonConfig>, - pub i: Option, + pub i: Option, // TODO rm Option in all params pub z_0: Option, pub z_i: Option, pub z_i1: Option, // z_{i+1} - pub phi: Option>, // phi_i in the paper sometimes appears as phi (φ) and others as 𝗎 + pub phi: Option>, // phi_i in the paper sometimes appears as phi (φ_i) and others as 𝗎 pub phiBig: Option>, // ϕ_i - pub phiOut: Option>, // ϕ_{i+1} + pub phiBigOut: Option>, // ϕ_{i+1} 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: ConstraintF, // pub input (φ_{i+1}.x) } -impl>> ConstraintSynthesizer> - for AugmentedFCircuit +impl>, FC: FCircuit>> + ConstraintSynthesizer> for AugmentedFCircuit where C: CurveGroup, GC: CurveVar>, @@ -154,14 +169,14 @@ where self, cs: ConstraintSystemRef>, ) -> Result<(), SynthesisError> { - let i = FpVar::>::new_witness(cs.clone(), || Ok(self.i.unwrap()))?; - let z_0 = FpVar::>::new_witness(cs.clone(), || Ok(self.z_0.unwrap()))?; - let z_i = FpVar::>::new_witness(cs.clone(), || Ok(self.z_i.unwrap()))?; - let z_i1 = FpVar::>::new_witness(cs.clone(), || Ok(self.z_i1.unwrap()))?; + let i = FpVar::>::new_input(cs.clone(), || Ok(self.i.unwrap()))?; + let z_0 = FpVar::>::new_input(cs.clone(), || Ok(self.z_0.unwrap()))?; + let z_i = FpVar::>::new_input(cs.clone(), || Ok(self.z_i.unwrap()))?; + let z_i1 = FpVar::>::new_input(cs.clone(), || Ok(self.z_i1.unwrap()))?; let phi = PhiVar::::new_witness(cs.clone(), || Ok(self.phi.unwrap()))?; let phiBig = PhiVar::::new_witness(cs.clone(), || Ok(self.phiBig.unwrap()))?; - let phiOut = PhiVar::::new_witness(cs.clone(), || Ok(self.phiOut.unwrap()))?; + let phiBigOut = PhiVar::::new_witness(cs.clone(), || Ok(self.phiBigOut.unwrap()))?; let cmT = GC::new_witness(cs.clone(), || Ok(self.cmT.unwrap()))?; let r = @@ -169,42 +184,134 @@ where Ok(self.r.unwrap()) })?; // r will come from transcript + // if i=0, output H(vk_nifs, 1, z_0, z_i1, phi) + let phiOut_x = AugmentedFCircuit::::phi_x_hash_var( + cs.clone(), + self.poseidon_config.clone(), + FpVar::>::one(), + z_0.clone(), + z_i1.clone(), + phi.clone(), + )?; + // TODO WIP: x is the output when i=0 + // 1. phi.x == H(vk_nifs, i, z_0, z_i, phiBig) - let mut sponge = - PoseidonSpongeVar::>::new(cs.clone(), &self.poseidon_config); - let input = vec![i.clone(), z_0.clone(), z_i.clone()]; - sponge.absorb(&input)?; - let input = vec![ - phiBig.u.to_constraint_field()?, - phiBig.x.to_constraint_field()?, - ]; - sponge.absorb(&input)?; - let input = vec![phiBig.cmE.to_bytes()?, phiBig.cmW.to_bytes()?]; - sponge.absorb(&input)?; - let h = sponge.squeeze_field_elements(1).unwrap(); + let h = AugmentedFCircuit::::phi_x_hash_var( + cs.clone(), + self.poseidon_config.clone(), + i.clone(), + z_0.clone(), + z_i.clone(), + phiBig.clone(), + )?; let x_CF = phi.x.to_constraint_field()?; // phi.x on the ConstraintF - x_CF[0].enforce_equal(&h[0])?; // review + // x_CF[0].enforce_equal(&h)?; // review - // 2. phi.cmE==0, phi.u==1 - (phi.cmE.is_zero()?).enforce_equal(&Boolean::TRUE)?; + // 2. phi.cmE==cm(0), phi.u==1 + // (phi.cmE.is_zero()?).enforce_equal(&Boolean::TRUE)?; // TODO check that cmE = cm(0) (phi.u.is_one()?).enforce_equal(&Boolean::TRUE)?; - // 3. nifs.verify, checks that folding phi & phiBig obtains phiOut - NIFSGadget::::verify(r, cmT, phi, phiBig, phiOut.clone())?; + // 3. nifs.verify, checks that folding phi & phiBig obtains phiBigOut + NIFSGadget::::verify(r, cmT, phi, phiBig, phiBigOut.clone())?; // 4. zksnark.V(vk_snark, phi_new, proof_phi) - // 5. phiOut.x == H(i+1, z_0, z_i+1, phiOut) + // 5. phi.x == H(i+1, z_0, z_i+1, phiBigOut) + let h = AugmentedFCircuit::::phi_x_hash_var( + cs.clone(), + self.poseidon_config.clone(), + i + FpVar::>::one(), + z_0.clone(), + z_i1.clone(), + phiBigOut.clone(), + )?; // WIP - let mut sponge = PoseidonSpongeVar::>::new(cs, &self.poseidon_config); - let input = vec![i + FpVar::>::one(), z_0, z_i1]; + let x_CF = phiBigOut.x.to_constraint_field()?; // phi.x on the ConstraintF + // x_CF[0].enforce_equal(&h)?; // TODO review + + // WIP assert that z_i1 == F(z_i).z_i1 + // self.F.generate_constraints(cs.clone())?; + + Ok(()) + } +} + +impl>, FC: FCircuit>> + AugmentedFCircuit +where + C: CurveGroup, + GC: CurveVar>, + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, + <::BaseField as Field>::BasePrimeField: Absorb, + ::BaseField: Absorb, +{ + fn phi_x_hash_var( + cs: ConstraintSystemRef>, + poseidon_config: PoseidonConfig>, + i: FpVar>, + z_0: FpVar>, + z_i: FpVar>, + phi: PhiVar, + ) -> Result>, SynthesisError> { + // note: this method can be optimized if instead of bytes representations we hash finite + // field elements (in the constraint field) + let mut sponge = PoseidonSpongeVar::>::new(cs.clone(), &poseidon_config); + + let input = vec![i, z_0, z_i]; + sponge.absorb(&input)?; + + let input: Vec>>> = vec![phi.u.to_bytes()?, phi.x.to_bytes()?]; sponge.absorb(&input)?; - let input = vec![phiOut.cmE.to_bytes()?, phiOut.cmW.to_bytes()?]; + + let input = vec![phi.cmE.to_bytes()?, phi.cmW.to_bytes()?]; sponge.absorb(&input)?; + let h = sponge.squeeze_field_elements(1).unwrap(); - let x_CF = phiOut.x.to_constraint_field()?; // phi.x on the ConstraintF - x_CF[0].enforce_equal(&h[0])?; // review + Ok(h[0].clone()) + } + fn phi_x_hash( + poseidon_config: PoseidonConfig>, + i: ConstraintF, + z_0: ConstraintF, + z_i: ConstraintF, + phi: Phi, + ) -> ConstraintF { + let mut sponge = PoseidonSponge::>::new(&poseidon_config); + + let input: Vec> = vec![i, z_0, z_i]; + sponge.absorb(&input); + + let input: Vec> = vec![ + phi.u.into_bigint().to_bytes_le(), + phi.x.into_bigint().to_bytes_le(), + ]; + sponge.absorb(&input); - Ok(()) + let cmE_bytes = Self::to_bytes_cs_compat(phi.cmE.0); + let cmW_bytes = Self::to_bytes_cs_compat(phi.cmW.0); + let input = vec![cmE_bytes, cmW_bytes]; + sponge.absorb(&input); + + let h = sponge.squeeze_field_elements(1); + h[0] + } + + fn to_bytes_cs_compat(c: C) -> Vec { + // note: this method has been implemented to be compatible with the arkworks/r1cs-std + // short_weierstrass/ProjectiveVar ToBytesGadget implementation, as the + // arkworks/algebra/serialize/SWCurveConfig version is not compatible. + let a = c.into_affine(); + let x = a.x().unwrap(); + let y = a.y().unwrap(); + let mut x_bytes = Vec::new(); + x.serialize_uncompressed(&mut x_bytes).unwrap(); + let mut y_bytes = Vec::new(); + y.serialize_uncompressed(&mut y_bytes).unwrap(); + + x_bytes.append(&mut vec![0, 0]); + y_bytes.append(&mut vec![0, 0]); + x_bytes.append(&mut y_bytes); + x_bytes.push(0); + x_bytes } } @@ -232,11 +339,34 @@ where // dummy_vk: >::VerifyingKey, // } +#[derive(Clone, Copy, Debug)] +pub struct TestFCircuit { + z_i: F, // z_i + z_i1: F, // z_{i+1} +} +impl FCircuit for TestFCircuit { + fn public(self) -> (F, F) { + (self.z_i, self.z_i1) + } +} +impl ConstraintSynthesizer for TestFCircuit { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + let z_i = FpVar::::new_input(cs.clone(), || Ok(self.z_i))?; + let z_i1 = FpVar::::new_input(cs.clone(), || Ok(self.z_i1))?; + let five = FpVar::::new_constant(cs.clone(), F::from(5u32))?; + + let y = &z_i * &z_i * &z_i + &z_i + &five; + y.enforce_equal(&z_i1)?; + Ok(()) + } +} + #[cfg(test)] mod test { use super::*; use crate::transcript::Transcript; + use ark_r1cs_std::R1CSVar; use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; @@ -246,8 +376,7 @@ mod test { use ark_ec::Group; // use ark_ed_on_mnt4_298::{constraints::EdwardsVar, EdwardsProjective}; use crate::pedersen::Commitment; - // use ark_mnt4_298::{constraints::G1Var as MNT4G1Var, G1Projective as MNT4G1Projective} - use ark_mnt4_298::{Fq, Fr}; + use ark_mnt4_298::{constraints::G1Var as MNT4G1Var, Fq, Fr, G1Projective as MNT4G1Projective}; use ark_mnt6_298::{constraints::G1Var as MNT6G1Var, G1Projective as MNT6G1Projective}; use ark_std::One; @@ -269,6 +398,49 @@ mod test { // println!("num_constraints={:?}", cs.num_constraints()); } + #[test] + fn test_phi_x_hash() { + let mut rng = ark_std::test_rng(); + let poseidon_config = poseidon_test_config::(); + + let z_0 = Fr::from(0_u32); + let z_i = Fr::from(3_u32); + let phi = Phi:: { + // cmE: Commitment(MNT6G1Projective::generator()), + cmE: Commitment(MNT6G1Projective::rand(&mut rng)), + u: Fq::one(), + cmW: Commitment(MNT6G1Projective::generator()), + x: Fq::one(), + }; + let x = AugmentedFCircuit::>::phi_x_hash( + poseidon_config.clone(), + Fr::one(), + z_0.clone(), + z_i.clone(), + phi.clone(), + ); + + let cs = ConstraintSystem::::new_ref(); + let z_0Var = FpVar::::new_witness(cs.clone(), || Ok(z_0)).unwrap(); + let z_iVar = FpVar::::new_witness(cs.clone(), || Ok(z_i)).unwrap(); + let phiVar = + PhiVar::::new_witness(cs.clone(), || Ok(phi)).unwrap(); + + let xVar = + AugmentedFCircuit::>::phi_x_hash_var( + cs.clone(), + poseidon_config.clone(), + FpVar::::one(), + z_0Var, + z_iVar, + phiVar, + ) + .unwrap(); + println!("num_constraints={:?}", cs.num_constraints()); + + assert_eq!(x, xVar.value().unwrap()); + } + #[test] fn test_nifs_gadget() { let mut rng = ark_std::test_rng(); @@ -310,5 +482,72 @@ mod test { NIFSGadget::::verify(rVar, cmTVar, phi1Var, phi2Var, phi3Var) .unwrap(); // println!("num_constraints={:?}", cs.num_constraints()); + assert!(cs.is_satisfied().unwrap()); + } + + #[test] + fn test_augmented_f_circuit() { + let mut rng = ark_std::test_rng(); + let pedersen_params = pedersen::Pedersen::::new_params(&mut rng, 100); // 100 is wip, will get it from actual vec + let poseidon_config_Fq = poseidon_test_config::(); + let poseidon_config_Fr = poseidon_test_config::(); + + let cs = ConstraintSystem::::new_ref(); + + let (r1cs, ws, _) = nifs::gen_test_values::(2); + let A = r1cs.A.clone(); + + let r = Fq::rand(&mut rng); // this would come from the transcript + + let fw1 = nifs::FWit::::new(ws[0].clone(), A.len()); + let fw2 = nifs::FWit::::new(ws[1].clone(), A.len()); + + let mut transcript_p = Transcript::::new(&poseidon_config_Fq); + + let (_fw3, phi1, phi2, _T, cmT) = nifs::NIFS::::P( + &mut transcript_p, + &pedersen_params, + r, + &r1cs, + fw1, + fw2, + ); + let phi3 = nifs::NIFS::::V(r, &phi1, &phi2, &cmT); + println!("phi1 {:?}", phi1.cmE); + + let i = Fr::from(1_u32); + let z_0 = Fr::from(0_u32); + let z_i = Fr::from(3_u32); + let z_i1 = Fr::from(35_u32); + let test_F_circuit = TestFCircuit:: { z_i, z_i1 }; + + let x = AugmentedFCircuit::>::phi_x_hash( + poseidon_config_Fr.clone(), + i + Fr::one(), + z_0.clone(), + z_i1.clone(), + phi3.clone(), + ); + + let augmentedF = AugmentedFCircuit::> { + _c: PhantomData, + _gc: PhantomData, + poseidon_config: poseidon_config_Fr.clone(), + i: Some(i), + z_0: Some(z_0), + z_i: Some(z_i), + z_i1: Some(z_i1), + phi: Some(phi1), + phiBig: Some(phi2), + phiBigOut: Some(phi3.clone()), + cmT: Some(cmT.0), + r: Some(r), + F: test_F_circuit, + x, + }; + augmentedF.generate_constraints(cs.clone()).unwrap(); + println!("num_constraints={:?}", cs.num_constraints()); + + assert!(cs.is_satisfied().unwrap()); } } diff --git a/src/ivc.rs b/src/ivc.rs index 9d53db5..ce69c63 100644 --- a/src/ivc.rs +++ b/src/ivc.rs @@ -7,7 +7,7 @@ use ark_crypto_primitives::sponge::poseidon::{PoseidonConfig, PoseidonSponge}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use crate::circuits::{AugmentedFCircuit, ConstraintF}; +use crate::circuits::{AugmentedFCircuit, ConstraintF, FCircuit}; use crate::nifs::{FWit, Phi, NIFS, R1CS}; use crate::pedersen::{Commitment, Params as PedersenParams, Pedersen, Proof as PedersenProof}; use crate::transcript::Transcript; @@ -29,6 +29,7 @@ pub struct IVC< GC1: CurveVar>, C2: CurveGroup, GC2: CurveVar>, + FC: FCircuit, > where C1: CurveGroup::ScalarField>, C2: CurveGroup::ScalarField>, @@ -37,10 +38,12 @@ pub struct IVC< _gc1: PhantomData, _c2: PhantomData, _gc2: PhantomData, + _fc: PhantomData, pub poseidon_config: PoseidonConfig>, pub pedersen_params_C1: PedersenParams, pub pedersen_params_C2: PedersenParams, + pub F: FC, // F circuit } impl< @@ -48,7 +51,8 @@ impl< GC1: CurveVar>, C2: CurveGroup, GC2: CurveVar>, - > IVC + FC: FCircuit, + > IVC where for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, @@ -68,14 +72,13 @@ where pub fn prove( &self, cs: ConstraintSystemRef>, + // TODO move part of these parameters to struct constructor instead of prove method tr1: &mut Transcript, tr2: &mut Transcript, r1cs: &R1CS, i: C1::ScalarField, z_0: C1::ScalarField, z_i: C1::ScalarField, - // phi1: &Phi, - // phi2: &Phi, fw1: FWit, fw2: FWit, ) -> Result, SynthesisError> { @@ -87,22 +90,24 @@ where NIFS::::P(tr2, &self.pedersen_params_C2, r, r1cs, fw1, fw2); let phi3 = NIFS::::V(r, &phi1, &phi2, &cmT); - // TODO compute z_{i+1} - let z_i1 = z_i.clone(); // WIP this will be the actual computed z_{i+1} + // get z_{i+1} + let (_, F_z_i1) = self.F.public(); - let c = AugmentedFCircuit:: { + let c = AugmentedFCircuit:: { _c: PhantomData, _gc: PhantomData, poseidon_config: self.poseidon_config.clone(), i: Some(i), z_0: Some(z_0), z_i: Some(z_i), - z_i1: Some(z_i1), + z_i1: Some(F_z_i1), phi: Some(phi1), phiBig: Some(phi2), - phiOut: Some(phi3.clone()), + phiBigOut: Some(phi3.clone()), cmT: Some(cmT.0), r: Some(r), + F: self.F, + x: i, // TODO WIP put x in there }; c.generate_constraints(cs.clone())?; diff --git a/src/nifs.rs b/src/nifs.rs index 48acb0c..c17fd67 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -40,12 +40,14 @@ where pub fn new(z: Vec, e_len: usize) -> Self { FWit:: { E: vec![C::ScalarField::zero(); e_len], - rE: C::ScalarField::one(), + rE: C::ScalarField::one(), // TODO rand W: z, - rW: C::ScalarField::one(), + rW: C::ScalarField::one(), // rand } } pub fn commit(&self, params: &PedersenParams) -> Phi { + // TODO maybe, if E=0, set cmE=0 (instead of cm(0)), to indicate that is a commitment to a + // 0 E let cmE = Pedersen::commit(params, &self.E, &self.rE); let cmW = Pedersen::commit(params, &self.W, &self.rW); Phi { diff --git a/src/sumcheck.rs b/src/sumcheck.rs index c127d2d..18a622a 100644 --- a/src/sumcheck.rs +++ b/src/sumcheck.rs @@ -17,8 +17,8 @@ use crate::transcript::Transcript; pub struct SumCheck< F: PrimeField + Absorb, C: CurveGroup, - UV: Polynomial + DenseUVPolynomial, - MV: Polynomial + DenseMVPolynomial, + UV: DenseUVPolynomial, + MV: DenseMVPolynomial, > { _f: PhantomData, _c: PhantomData,