use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; use ark_std::Zero; use std::marker::PhantomData; use super::{CommittedInstance, Witness}; use crate::arith::r1cs::R1CS; use crate::commitment::CommitmentScheme; use crate::folding::circuits::cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness}; use crate::transcript::Transcript; use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub}; use crate::Error; /// Implements the Non-Interactive Folding Scheme described in section 4 of /// [Nova](https://eprint.iacr.org/2021/370.pdf) /// `H` specifies whether the NIFS will use a blinding factor pub struct NIFS, const H: bool = false> { _c: PhantomData, _cp: PhantomData, } impl, const H: bool> NIFS where ::ScalarField: Absorb, { // compute_T: compute cross-terms T pub fn compute_T( r1cs: &R1CS, u1: C::ScalarField, u2: C::ScalarField, z1: &[C::ScalarField], z2: &[C::ScalarField], ) -> Result, Error> { let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone()); // this is parallelizable (for the future) let Az1 = mat_vec_mul(&A, z1)?; let Bz1 = mat_vec_mul(&B, z1)?; let Cz1 = mat_vec_mul(&C, z1)?; let Az2 = mat_vec_mul(&A, z2)?; let Bz2 = mat_vec_mul(&B, z2)?; let Cz2 = mat_vec_mul(&C, z2)?; let Az1_Bz2 = hadamard(&Az1, &Bz2)?; let Az2_Bz1 = hadamard(&Az2, &Bz1)?; let u1Cz2 = vec_scalar_mul(&Cz2, &u1); let u2Cz1 = vec_scalar_mul(&Cz1, &u2); vec_sub(&vec_sub(&vec_add(&Az1_Bz2, &Az2_Bz1)?, &u1Cz2)?, &u2Cz1) } pub fn fold_witness( r: C::ScalarField, w1: &Witness, w2: &Witness, T: &[C::ScalarField], rT: C::ScalarField, ) -> Result, Error> { let r2 = r * r; let E: Vec = vec_add( &vec_add(&w1.E, &vec_scalar_mul(T, &r))?, &vec_scalar_mul(&w2.E, &r2), )?; let rE = w1.rE + r * rT + r2 * w2.rE; let W: Vec = w1.W.iter().zip(&w2.W).map(|(a, b)| *a + (r * b)).collect(); let rW = w1.rW + r * w2.rW; Ok(Witness:: { E, rE, W, rW }) } pub fn fold_committed_instance( r: C::ScalarField, ci1: &CommittedInstance, // U_i ci2: &CommittedInstance, // u_i cmT: &C, ) -> CommittedInstance { let r2 = r * r; let cmE = ci1.cmE + cmT.mul(r) + ci2.cmE.mul(r2); let u = ci1.u + r * ci2.u; let cmW = ci1.cmW + ci2.cmW.mul(r); let x = ci1 .x .iter() .zip(&ci2.x) .map(|(a, b)| *a + (r * b)) .collect::>(); CommittedInstance:: { cmE, u, cmW, x } } /// 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( cs_prover_params: &CS::ProverParams, r1cs: &R1CS, w1: &Witness, ci1: &CommittedInstance, w2: &Witness, ci2: &CommittedInstance, ) -> 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)?; // use r_T=0 since we don't need hiding property for cm(T) let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?; Ok((T, cmT)) } pub fn compute_cyclefold_cmT( cs_prover_params: &CS::ProverParams, r1cs: &R1CS, // R1CS over C2.Fr=C1.Fq (here C=C2) w1: &CycleFoldWitness, ci1: &CycleFoldCommittedInstance, w2: &CycleFoldWitness, ci2: &CycleFoldCommittedInstance, ) -> Result<(Vec, C), Error> where ::BaseField: ark_ff::PrimeField, { 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)?; // use r_T=0 since we don't need hiding property for cm(T) let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?; 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: C::ScalarField, w1: &Witness, ci1: &CommittedInstance, w2: &Witness, ci2: &CommittedInstance, T: &[C::ScalarField], cmT: C, ) -> Result<(Witness, CommittedInstance), Error> { // fold witness // use r_T=0 since we don't need hiding property for cm(T) let w3 = NIFS::::fold_witness(r, w1, w2, T, C::ScalarField::zero())?; // fold committed instances let ci3 = NIFS::::fold_committed_instance(r, ci1, ci2, &cmT); Ok((w3, ci3)) } /// 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, ci1: &CommittedInstance, ci2: &CommittedInstance, cmT: &C, ) -> CommittedInstance { NIFS::::fold_committed_instance(r, ci1, ci2, cmT) } /// Verify committed folded instance (ci) relations. Notice that this method does not open the /// commitments, but just checks that the given committed instances (ci1, ci2) when folded /// result in the folded committed instance (ci3) values. pub fn verify_folded_instance( r: C::ScalarField, ci1: &CommittedInstance, ci2: &CommittedInstance, ci3: &CommittedInstance, cmT: &C, ) -> Result<(), Error> { 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); } Ok(()) } pub fn prove_commitments( tr: &mut impl Transcript, cs_prover_params: &CS::ProverParams, w: &Witness, ci: &CommittedInstance, T: Vec, cmT: &C, ) -> Result<[CS::Proof; 3], Error> { let cmE_proof = CS::prove(cs_prover_params, tr, &ci.cmE, &w.E, &w.rE, None)?; let cmW_proof = CS::prove(cs_prover_params, tr, &ci.cmW, &w.W, &w.rW, None)?; let cmT_proof = CS::prove(cs_prover_params, tr, cmT, &T, &C::ScalarField::zero(), None)?; // cm(T) is committed with rT=0 Ok([cmE_proof, cmW_proof, cmT_proof]) } } #[cfg(test)] pub mod tests { use super::*; use ark_crypto_primitives::sponge::{ poseidon::{PoseidonConfig, PoseidonSponge}, CryptographicSponge, }; use ark_ff::{BigInteger, PrimeField}; use ark_pallas::{Fr, Projective}; use ark_std::{ops::Mul, test_rng, UniformRand}; use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; use crate::folding::nova::circuits::ChallengeGadget; use crate::transcript::poseidon::poseidon_canonical_config; use crate::{ arith::{ r1cs::tests::{get_test_r1cs, get_test_z}, Arith, }, folding::traits::Dummy, }; #[allow(clippy::type_complexity)] pub(crate) fn prepare_simple_fold_inputs() -> ( PedersenParams, PoseidonConfig, R1CS, Witness, // w1 CommittedInstance, // ci1 Witness, // w2 CommittedInstance, // ci2 Witness, // w3 CommittedInstance, // ci3 Vec, // T C, // cmT Vec, // r_bits C::ScalarField, // r_Fr ) where C: CurveGroup, ::BaseField: PrimeField, C::ScalarField: Absorb, { let r1cs = get_test_r1cs(); let z1 = get_test_z(3); let z2 = get_test_z(4); let (w1, x1) = r1cs.split_z(&z1); let (w2, x2) = r1cs.split_z(&z2); let w1 = Witness::::new::(w1.clone(), r1cs.A.n_rows, test_rng()); let w2 = Witness::::new::(w2.clone(), r1cs.A.n_rows, test_rng()); let mut rng = ark_std::test_rng(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); // compute committed instances let ci1 = w1 .commit::, false>(&pedersen_params, x1.clone()) .unwrap(); let ci2 = w2 .commit::, false>(&pedersen_params, x2.clone()) .unwrap(); // NIFS.P let (T, cmT) = NIFS::>::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2) .unwrap(); let poseidon_config = poseidon_canonical_config::(); let mut transcript = PoseidonSponge::::new(&poseidon_config); let pp_hash = C::ScalarField::from(42u32); // only for test let r_bits = ChallengeGadget::::get_challenge_native( &mut transcript, pp_hash, ci1.clone(), ci2.clone(), cmT, ); let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); let (w3, ci3) = NIFS::>::fold_instances(r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap(); ( pedersen_params, poseidon_config, r1cs, w1, ci1, w2, ci2, w3, ci3, T, cmT, r_bits, r_Fr, ) } // fold 2 dummy instances and check that the folded instance holds the relaxed R1CS relation #[test] fn test_nifs_fold_dummy() { let r1cs = get_test_r1cs::(); let z1 = get_test_z(3); let (_, x1) = r1cs.split_z(&z1); let mut rng = ark_std::test_rng(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); // dummy instance, witness and public inputs zeroes let w_dummy = Witness::::dummy(&r1cs); let mut u_dummy = w_dummy .commit::, false>(&pedersen_params, vec![Fr::zero(); x1.len()]) .unwrap(); u_dummy.u = Fr::zero(); let w_i = w_dummy.clone(); let u_i = u_dummy.clone(); let W_i = w_dummy.clone(); let U_i = u_dummy.clone(); r1cs.check_relation(&w_i, &u_i).unwrap(); r1cs.check_relation(&W_i, &U_i).unwrap(); let r_Fr = Fr::from(3_u32); 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(); r1cs.check_relation(&W_i1, &U_i1).unwrap(); } // fold 2 instances into one #[test] fn test_nifs_one_fold() { let (pedersen_params, poseidon_config, r1cs, w1, ci1, w2, ci2, w3, ci3, T, cmT, _, r) = prepare_simple_fold_inputs(); // NIFS.V let ci3_v = NIFS::>::verify(r, &ci1, &ci2, &cmT); assert_eq!(ci3_v, ci3); // check that relations hold for the 2 inputted instances and the folded one r1cs.check_relation(&w1, &ci1).unwrap(); r1cs.check_relation(&w2, &ci2).unwrap(); r1cs.check_relation(&w3, &ci3).unwrap(); // check that folded commitments from folded instance (ci) are equal to folding the // use folded rE, rW to commit w3 let ci3_expected = w3 .commit::, false>(&pedersen_params, ci3.x.clone()) .unwrap(); assert_eq!(ci3_expected.cmE, ci3.cmE); assert_eq!(ci3_expected.cmW, ci3.cmW); // next equalities should hold since we started from two cmE of zero-vector E's assert_eq!(ci3.cmE, cmT.mul(r)); assert_eq!(w3.E, vec_scalar_mul(&T, &r)); // NIFS.Verify_Folded_Instance: NIFS::>::verify_folded_instance(r, &ci1, &ci2, &ci3, &cmT) .unwrap(); // init Prover's transcript let mut transcript_p = PoseidonSponge::::new(&poseidon_config); // init Verifier's transcript let mut transcript_v = PoseidonSponge::::new(&poseidon_config); // prove the ci3.cmE, ci3.cmW, cmT commitments let cm_proofs = NIFS::>::prove_commitments( &mut transcript_p, &pedersen_params, &w3, &ci3, T, &cmT, ) .unwrap(); // verify the ci3.cmE, ci3.cmW, cmT commitments assert_eq!(cm_proofs.len(), 3); Pedersen::::verify( &pedersen_params, &mut transcript_v, &ci3.cmE, &cm_proofs[0].clone(), ) .unwrap(); Pedersen::::verify( &pedersen_params, &mut transcript_v, &ci3.cmW, &cm_proofs[1].clone(), ) .unwrap(); Pedersen::::verify( &pedersen_params, &mut transcript_v, &cmT, &cm_proofs[2].clone(), ) .unwrap(); } #[test] fn test_nifs_fold_loop() { let r1cs = get_test_r1cs(); let z = get_test_z(3); let (w, x) = r1cs.split_z(&z); let mut rng = ark_std::test_rng(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); // prepare the running instance let mut running_instance_w = Witness::::new::(w.clone(), r1cs.A.n_rows, test_rng()); let mut running_committed_instance = running_instance_w .commit::, false>(&pedersen_params, x) .unwrap(); r1cs.check_relation(&running_instance_w, &running_committed_instance) .unwrap(); let num_iters = 10; for i in 0..num_iters { // prepare the incoming instance let incoming_instance_z = get_test_z(i + 4); let (w, x) = r1cs.split_z(&incoming_instance_z); let incoming_instance_w = Witness::::new::(w.clone(), r1cs.A.n_rows, test_rng()); let incoming_committed_instance = incoming_instance_w .commit::, false>(&pedersen_params, x) .unwrap(); r1cs.check_relation(&incoming_instance_w, &incoming_committed_instance) .unwrap(); let r = Fr::rand(&mut rng); // folding challenge would come from the RO // NIFS.P let (T, cmT) = NIFS::>::compute_cmT( &pedersen_params, &r1cs, &running_instance_w, &running_committed_instance, &incoming_instance_w, &incoming_committed_instance, ) .unwrap(); let (folded_w, _) = NIFS::>::fold_instances( r, &running_instance_w, &running_committed_instance, &incoming_instance_w, &incoming_committed_instance, &T, cmT, ) .unwrap(); // NIFS.V let folded_committed_instance = NIFS::>::verify( r, &running_committed_instance, &incoming_committed_instance, &cmT, ); r1cs.check_relation(&folded_w, &folded_committed_instance) .unwrap(); // set running_instance for next loop iteration running_instance_w = folded_w; running_committed_instance = folded_committed_instance; } } }