From 15e2886e6144dbf9d1475f9d19ef34d21857b403 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 23 Aug 2023 15:07:33 +0200 Subject: [PATCH] Implement Nova's NIFS.P & NIFS.V (#7) --- src/folding/mod.rs | 1 + src/folding/nova/mod.rs | 62 ++++++++ src/folding/nova/nifs.rs | 336 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 400 insertions(+) create mode 100644 src/folding/mod.rs create mode 100644 src/folding/nova/mod.rs create mode 100644 src/folding/nova/nifs.rs diff --git a/src/folding/mod.rs b/src/folding/mod.rs new file mode 100644 index 0000000..9861770 --- /dev/null +++ b/src/folding/mod.rs @@ -0,0 +1 @@ +pub mod nova; diff --git a/src/folding/nova/mod.rs b/src/folding/nova/mod.rs new file mode 100644 index 0000000..84b03ab --- /dev/null +++ b/src/folding/nova/mod.rs @@ -0,0 +1,62 @@ +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; +use ark_std::fmt::Debug; +use ark_std::{One, Zero}; + +use crate::pedersen::{Params as PedersenParams, Pedersen}; + +pub mod nifs; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct CommittedInstance { + pub cmE: C, + pub u: C::ScalarField, + pub cmW: C, + pub x: Vec, +} +impl CommittedInstance { + pub fn empty() -> Self { + CommittedInstance { + cmE: C::zero(), + u: C::ScalarField::one(), + cmW: C::zero(), + x: Vec::new(), + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Witness { + pub E: Vec, + pub rE: C::ScalarField, + pub W: Vec, + pub rW: C::ScalarField, +} + +impl Witness +where + ::ScalarField: Absorb, +{ + pub fn new(w: Vec, e_len: usize) -> Self { + Self { + E: vec![C::ScalarField::zero(); e_len], + rE: C::ScalarField::one(), + W: w, + rW: C::ScalarField::one(), + } + } + pub fn commit( + &self, + params: &PedersenParams, + x: Vec, + ) -> CommittedInstance { + let cmE = Pedersen::commit(params, &self.E, &self.rE); + let cmW = Pedersen::commit(params, &self.W, &self.rW); + CommittedInstance { + cmE, + u: C::ScalarField::one(), + cmW, + x, + } + } +} diff --git a/src/folding/nova/nifs.rs b/src/folding/nova/nifs.rs new file mode 100644 index 0000000..c2e2ab1 --- /dev/null +++ b/src/folding/nova/nifs.rs @@ -0,0 +1,336 @@ +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; +use ark_std::One; +use std::marker::PhantomData; + +use crate::ccs::r1cs::R1CS; +use crate::folding::nova::{CommittedInstance, Witness}; +use crate::pedersen::{Params as PedersenParams, Pedersen, Proof as PedersenProof}; +use crate::transcript::Transcript; +use crate::utils::vec::*; + +pub struct NIFS { + _phantom: PhantomData, +} + +impl 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], + ) -> Vec { + let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone()); + + // this is parallelizable (for the future) + let Az1 = mat_vec_mul_sparse(&A, z1); + let Bz1 = mat_vec_mul_sparse(&B, z1); + let Cz1 = mat_vec_mul_sparse(&C, z1); + let Az2 = mat_vec_mul_sparse(&A, z2); + let Bz2 = mat_vec_mul_sparse(&B, z2); + let Cz2 = mat_vec_mul_sparse(&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, + ) -> Witness { + 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_add(&w1.W, &vec_scalar_mul(&w2.W, &r)); + let rW = w1.rW + r * w2.rW; + Witness:: { E, rE, W, rW } + } + + pub fn fold_committed_instance( + r: C::ScalarField, + ci1: &CommittedInstance, + ci2: &CommittedInstance, + 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 = vec_add(&ci1.x, &vec_scalar_mul(&ci2.x, &r)); + + CommittedInstance:: { cmE, u, cmW, x } + } + + // NIFS.P + #[allow(clippy::type_complexity)] + pub fn prove( + pedersen_params: &PedersenParams, + r: C::ScalarField, + r1cs: &R1CS, + w1: &Witness, + ci1: &CommittedInstance, + w2: &Witness, + ci2: &CommittedInstance, + ) -> (Witness, CommittedInstance, Vec, C) { + 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); + + // fold witness + let w3 = NIFS::::fold_witness(r, w1, w2, &T, rT); + + // fold committed instancs + let ci3 = NIFS::::fold_committed_instance(r, ci1, ci2, &cmT); + + (w3, ci3, T, cmT) + } + + // NIFS.V + pub fn verify( + r: C::ScalarField, + ci1: &CommittedInstance, + ci2: &CommittedInstance, + cmT: &C, + ) -> CommittedInstance { + NIFS::::fold_committed_instance(r, ci1, ci2, cmT) + } + + // verify commited folded instance (ci) relations + pub fn verify_folded_instance( + r: C::ScalarField, + ci1: &CommittedInstance, + ci2: &CommittedInstance, + ci3: &CommittedInstance, + cmT: &C, + ) -> bool { + let r2 = r * r; + if ci3.cmE != (ci1.cmE + cmT.mul(r) + ci2.cmE.mul(r2)) { + return false; + } + if ci3.u != ci1.u + r * ci2.u { + return false; + } + if ci3.cmW != (ci1.cmW + ci2.cmW.mul(r)) { + return false; + } + if ci3.x != vec_add(&ci1.x, &vec_scalar_mul(&ci2.x, &r)) { + return false; + } + true + } + + pub fn open_commitments( + tr: &mut impl Transcript, + pedersen_params: &PedersenParams, + w: &Witness, + ci: &CommittedInstance, + T: Vec, + cmT: &C, + ) -> (PedersenProof, PedersenProof, PedersenProof) { + let cmE_proof = Pedersen::prove(pedersen_params, tr, &ci.cmE, &w.E, &w.rE); + let cmW_proof = Pedersen::prove(pedersen_params, tr, &ci.cmW, &w.W, &w.rW); + let cmT_proof = Pedersen::prove(pedersen_params, tr, cmT, &T, &C::ScalarField::one()); // cm(T) is committed with rT=1 + (cmE_proof, cmW_proof, cmT_proof) + } + pub fn verify_commitments( + tr: &mut impl Transcript, + pedersen_params: &PedersenParams, + ci: CommittedInstance, + cmT: C, + cmE_proof: PedersenProof, + cmW_proof: PedersenProof, + cmT_proof: PedersenProof, + ) -> bool { + if !Pedersen::verify(pedersen_params, tr, ci.cmE, cmE_proof) { + return false; + } + if !Pedersen::verify(pedersen_params, tr, ci.cmW, cmW_proof) { + return false; + } + if !Pedersen::verify(pedersen_params, tr, cmT, cmT_proof) { + return false; + } + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z}; + use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript}; + use ark_bls12_377::{Fr, G1Projective}; + use ark_ff::PrimeField; + use ark_std::UniformRand; + + pub fn check_relaxed_r1cs(r1cs: &R1CS, z: Vec, u: F, E: &[F]) { + let Az = mat_vec_mul_sparse(&r1cs.A, &z); + let Bz = mat_vec_mul_sparse(&r1cs.B, &z); + let Cz = mat_vec_mul_sparse(&r1cs.C, &z); + assert_eq!(hadamard(&Az, &Bz), vec_add(&vec_scalar_mul(&Cz, &u), E)); + } + + // fold 2 instances into one + #[test] + fn test_nifs_one_fold() { + 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_cols); + let w2 = Witness::::new(w2.clone(), r1cs.A.n_cols); + + let mut rng = ark_std::test_rng(); + let pedersen_params = Pedersen::::new_params(&mut rng, r1cs.A.n_cols); + + let r = Fr::rand(&mut rng); // folding challenge would come from the transcript + + // compute committed instances + let ci1 = w1.commit(&pedersen_params, x1.clone()); + let ci2 = w2.commit(&pedersen_params, x2.clone()); + // NIFS.P + let (w3, _, T, cmT) = + NIFS::::prove(&pedersen_params, r, &r1cs, &w1, &ci1, &w2, &ci2); + + // NIFS.V + let ci3 = NIFS::::verify(r, &ci1, &ci2, &cmT); + + // naive check that the folded witness satisfies the relaxed r1cs + let z3: Vec = [vec![ci3.u], ci3.x.to_vec(), w3.W.to_vec()].concat(); + // check that z3 is as expected + let z3_aux = vec_add(&z1, &vec_scalar_mul(&z2, &r)); + assert_eq!(z3, z3_aux); + // check that relations hold for the 2 inputted instances and the folded one + check_relaxed_r1cs(&r1cs, z1, ci1.u, &w1.E); + check_relaxed_r1cs(&r1cs, z2, ci2.u, &w2.E); + check_relaxed_r1cs(&r1cs, z3, ci3.u, &w3.E); + + // 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(&pedersen_params, ci3.x.clone()); + assert_eq!(ci3_expected.cmE, ci3.cmE); + assert_eq!(ci3_expected.cmW, ci3.cmW); + + // NIFS.Verify_Folded_Instance: + assert!(NIFS::::verify_folded_instance( + r, &ci1, &ci2, &ci3, &cmT + )); + + let poseidon_config = poseidon_test_config::(); + // init Prover's transcript + let mut transcript_p = PoseidonTranscript::::new(&poseidon_config); + // init Verifier's transcript + let mut transcript_v = PoseidonTranscript::::new(&poseidon_config); + + // check openings of ci3.cmE, ci3.cmW and cmT + let (cmE_proof, cmW_proof, cmT_proof) = NIFS::::open_commitments( + &mut transcript_p, + &pedersen_params, + &w3, + &ci3, + T, + &cmT, + ); + let v = NIFS::::verify_commitments( + &mut transcript_v, + &pedersen_params, + ci3, + cmT, + cmE_proof, + cmW_proof, + cmT_proof, + ); + assert!(v); + } + + #[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::::new_params(&mut rng, r1cs.A.n_cols); + + // prepare the running instance + let mut running_instance_w = Witness::::new(w.clone(), r1cs.A.n_cols); + let mut running_committed_instance = running_instance_w.commit(&pedersen_params, x); + check_relaxed_r1cs( + &r1cs, + z, + running_committed_instance.u, + &running_instance_w.E, + ); + + let num_iters = 10; + for i in 0..num_iters { + // prepare the incomming instance + let incomming_instance_z = get_test_z(i + 4); + let (w, x) = r1cs.split_z(&incomming_instance_z); + let incomming_instance_w = Witness::::new(w.clone(), r1cs.A.n_cols); + let incomming_committed_instance = incomming_instance_w.commit(&pedersen_params, x); + check_relaxed_r1cs( + &r1cs, + incomming_instance_z.clone(), + incomming_committed_instance.u, + &incomming_instance_w.E, + ); + + let r = Fr::rand(&mut rng); // folding challenge would come from the transcript + + // NIFS.P + let (folded_w, _, _, cmT) = NIFS::::prove( + &pedersen_params, + r, + &r1cs, + &running_instance_w, + &running_committed_instance, + &incomming_instance_w, + &incomming_committed_instance, + ); + + // NIFS.V + let folded_committed_instance = NIFS::::verify( + r, + &running_committed_instance, + &incomming_committed_instance, + &cmT, + ); + + let folded_z: Vec = [ + vec![folded_committed_instance.u], + folded_committed_instance.x.to_vec(), + folded_w.W.to_vec(), + ] + .concat(); + + check_relaxed_r1cs(&r1cs, folded_z, folded_committed_instance.u, &folded_w.E); + + // set running_instance for next loop iteration + running_instance_w = folded_w; + running_committed_instance = folded_committed_instance; + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 335d233..4851ed5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ use thiserror::Error; pub mod transcript; use transcript::Transcript; pub mod ccs; +pub mod folding; pub mod pedersen; pub mod utils;