arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/nova/nifs.rs
folding-schemes/src/folding/nova/nifs.rsRaw
  1use ark_crypto_primitives::sponge::Absorb;
  2use ark_ec::{CurveGroup, Group};
  3use ark_std::Zero;
  4use std::marker::PhantomData;
  5
  6use super::{CommittedInstance, Witness};
  7use crate::arith::r1cs::R1CS;
  8use crate::commitment::CommitmentScheme;
  9use crate::folding::circuits::cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness};
 10use crate::transcript::Transcript;
 11use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub};
 12use crate::Error;
 13
 14/// Implements the Non-Interactive Folding Scheme described in section 4 of
 15/// [Nova](https://eprint.iacr.org/2021/370.pdf)
 16/// `H` specifies whether the NIFS will use a blinding factor
 17pub struct NIFS<C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool = false> {
 18    _c: PhantomData<C>,
 19    _cp: PhantomData<CS>,
 20}
 21
 22impl<C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool> NIFS<C, CS, H>
 23where
 24    <C as Group>::ScalarField: Absorb,
 25{
 26    // compute_T: compute cross-terms T
 27    pub fn compute_T(
 28        r1cs: &R1CS<C::ScalarField>,
 29        u1: C::ScalarField,
 30        u2: C::ScalarField,
 31        z1: &[C::ScalarField],
 32        z2: &[C::ScalarField],
 33    ) -> Result<Vec<C::ScalarField>, Error> {
 34        let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone());
 35
 36        // this is parallelizable (for the future)
 37        let Az1 = mat_vec_mul(&A, z1)?;
 38        let Bz1 = mat_vec_mul(&B, z1)?;
 39        let Cz1 = mat_vec_mul(&C, z1)?;
 40        let Az2 = mat_vec_mul(&A, z2)?;
 41        let Bz2 = mat_vec_mul(&B, z2)?;
 42        let Cz2 = mat_vec_mul(&C, z2)?;
 43
 44        let Az1_Bz2 = hadamard(&Az1, &Bz2)?;
 45        let Az2_Bz1 = hadamard(&Az2, &Bz1)?;
 46        let u1Cz2 = vec_scalar_mul(&Cz2, &u1);
 47        let u2Cz1 = vec_scalar_mul(&Cz1, &u2);
 48
 49        vec_sub(&vec_sub(&vec_add(&Az1_Bz2, &Az2_Bz1)?, &u1Cz2)?, &u2Cz1)
 50    }
 51
 52    pub fn fold_witness(
 53        r: C::ScalarField,
 54        w1: &Witness<C>,
 55        w2: &Witness<C>,
 56        T: &[C::ScalarField],
 57        rT: C::ScalarField,
 58    ) -> Result<Witness<C>, Error> {
 59        let r2 = r * r;
 60        let E: Vec<C::ScalarField> = vec_add(
 61            &vec_add(&w1.E, &vec_scalar_mul(T, &r))?,
 62            &vec_scalar_mul(&w2.E, &r2),
 63        )?;
 64        let rE = w1.rE + r * rT + r2 * w2.rE;
 65        let W: Vec<C::ScalarField> = w1.W.iter().zip(&w2.W).map(|(a, b)| *a + (r * b)).collect();
 66
 67        let rW = w1.rW + r * w2.rW;
 68        Ok(Witness::<C> { E, rE, W, rW })
 69    }
 70
 71    pub fn fold_committed_instance(
 72        r: C::ScalarField,
 73        ci1: &CommittedInstance<C>, // U_i
 74        ci2: &CommittedInstance<C>, // u_i
 75        cmT: &C,
 76    ) -> CommittedInstance<C> {
 77        let r2 = r * r;
 78        let cmE = ci1.cmE + cmT.mul(r) + ci2.cmE.mul(r2);
 79        let u = ci1.u + r * ci2.u;
 80        let cmW = ci1.cmW + ci2.cmW.mul(r);
 81        let x = ci1
 82            .x
 83            .iter()
 84            .zip(&ci2.x)
 85            .map(|(a, b)| *a + (r * b))
 86            .collect::<Vec<C::ScalarField>>();
 87
 88        CommittedInstance::<C> { cmE, u, cmW, x }
 89    }
 90
 91    /// NIFS.P is the consecutive combination of compute_cmT with fold_instances
 92
 93    /// compute_cmT is part of the NIFS.P logic
 94    pub fn compute_cmT(
 95        cs_prover_params: &CS::ProverParams,
 96        r1cs: &R1CS<C::ScalarField>,
 97        w1: &Witness<C>,
 98        ci1: &CommittedInstance<C>,
 99        w2: &Witness<C>,
100        ci2: &CommittedInstance<C>,
101    ) -> Result<(Vec<C::ScalarField>, C), Error> {
102        let z1: Vec<C::ScalarField> = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat();
103        let z2: Vec<C::ScalarField> = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat();
104
105        // compute cross terms
106        let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?;
107        // use r_T=0 since we don't need hiding property for cm(T)
108        let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?;
109        Ok((T, cmT))
110    }
111    pub fn compute_cyclefold_cmT(
112        cs_prover_params: &CS::ProverParams,
113        r1cs: &R1CS<C::ScalarField>, // R1CS over C2.Fr=C1.Fq (here C=C2)
114        w1: &CycleFoldWitness<C>,
115        ci1: &CycleFoldCommittedInstance<C>,
116        w2: &CycleFoldWitness<C>,
117        ci2: &CycleFoldCommittedInstance<C>,
118    ) -> Result<(Vec<C::ScalarField>, C), Error>
119    where
120        <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
121    {
122        let z1: Vec<C::ScalarField> = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat();
123        let z2: Vec<C::ScalarField> = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat();
124
125        // compute cross terms
126        let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?;
127        // use r_T=0 since we don't need hiding property for cm(T)
128        let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?;
129        Ok((T, cmT))
130    }
131
132    /// fold_instances is part of the NIFS.P logic described in
133    /// [Nova](https://eprint.iacr.org/2021/370.pdf)'s section 4. It returns the folded Committed
134    /// Instances and the Witness.
135    pub fn fold_instances(
136        r: C::ScalarField,
137        w1: &Witness<C>,
138        ci1: &CommittedInstance<C>,
139        w2: &Witness<C>,
140        ci2: &CommittedInstance<C>,
141        T: &[C::ScalarField],
142        cmT: C,
143    ) -> Result<(Witness<C>, CommittedInstance<C>), Error> {
144        // fold witness
145        // use r_T=0 since we don't need hiding property for cm(T)
146        let w3 = NIFS::<C, CS, H>::fold_witness(r, w1, w2, T, C::ScalarField::zero())?;
147
148        // fold committed instances
149        let ci3 = NIFS::<C, CS, H>::fold_committed_instance(r, ci1, ci2, &cmT);
150
151        Ok((w3, ci3))
152    }
153
154    /// verify implements NIFS.V logic described in [Nova](https://eprint.iacr.org/2021/370.pdf)'s
155    /// section 4. It returns the folded Committed Instance
156    pub fn verify(
157        // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
158        r: C::ScalarField,
159        ci1: &CommittedInstance<C>,
160        ci2: &CommittedInstance<C>,
161        cmT: &C,
162    ) -> CommittedInstance<C> {
163        NIFS::<C, CS, H>::fold_committed_instance(r, ci1, ci2, cmT)
164    }
165
166    /// Verify committed folded instance (ci) relations. Notice that this method does not open the
167    /// commitments, but just checks that the given committed instances (ci1, ci2) when folded
168    /// result in the folded committed instance (ci3) values.
169    pub fn verify_folded_instance(
170        r: C::ScalarField,
171        ci1: &CommittedInstance<C>,
172        ci2: &CommittedInstance<C>,
173        ci3: &CommittedInstance<C>,
174        cmT: &C,
175    ) -> Result<(), Error> {
176        let expected = Self::fold_committed_instance(r, ci1, ci2, cmT);
177        if ci3.cmE != expected.cmE
178            || ci3.u != expected.u
179            || ci3.cmW != expected.cmW
180            || ci3.x != expected.x
181        {
182            return Err(Error::NotSatisfied);
183        }
184        Ok(())
185    }
186
187    pub fn prove_commitments(
188        tr: &mut impl Transcript<C::ScalarField>,
189        cs_prover_params: &CS::ProverParams,
190        w: &Witness<C>,
191        ci: &CommittedInstance<C>,
192        T: Vec<C::ScalarField>,
193        cmT: &C,
194    ) -> Result<[CS::Proof; 3], Error> {
195        let cmE_proof = CS::prove(cs_prover_params, tr, &ci.cmE, &w.E, &w.rE, None)?;
196        let cmW_proof = CS::prove(cs_prover_params, tr, &ci.cmW, &w.W, &w.rW, None)?;
197        let cmT_proof = CS::prove(cs_prover_params, tr, cmT, &T, &C::ScalarField::zero(), None)?; // cm(T) is committed with rT=0
198        Ok([cmE_proof, cmW_proof, cmT_proof])
199    }
200}
201
202#[cfg(test)]
203pub mod tests {
204    use super::*;
205    use ark_crypto_primitives::sponge::{
206        poseidon::{PoseidonConfig, PoseidonSponge},
207        CryptographicSponge,
208    };
209    use ark_ff::{BigInteger, PrimeField};
210    use ark_pallas::{Fr, Projective};
211    use ark_std::{ops::Mul, test_rng, UniformRand};
212
213    use crate::commitment::pedersen::{Params as PedersenParams, Pedersen};
214    use crate::folding::nova::circuits::ChallengeGadget;
215    use crate::transcript::poseidon::poseidon_canonical_config;
216    use crate::{
217        arith::{
218            r1cs::tests::{get_test_r1cs, get_test_z},
219            Arith,
220        },
221        folding::traits::Dummy,
222    };
223
224    #[allow(clippy::type_complexity)]
225    pub(crate) fn prepare_simple_fold_inputs<C>() -> (
226        PedersenParams<C>,
227        PoseidonConfig<C::ScalarField>,
228        R1CS<C::ScalarField>,
229        Witness<C>,           // w1
230        CommittedInstance<C>, // ci1
231        Witness<C>,           // w2
232        CommittedInstance<C>, // ci2
233        Witness<C>,           // w3
234        CommittedInstance<C>, // ci3
235        Vec<C::ScalarField>,  // T
236        C,                    // cmT
237        Vec<bool>,            // r_bits
238        C::ScalarField,       // r_Fr
239    )
240    where
241        C: CurveGroup,
242        <C as CurveGroup>::BaseField: PrimeField,
243        C::ScalarField: Absorb,
244    {
245        let r1cs = get_test_r1cs();
246        let z1 = get_test_z(3);
247        let z2 = get_test_z(4);
248        let (w1, x1) = r1cs.split_z(&z1);
249        let (w2, x2) = r1cs.split_z(&z2);
250
251        let w1 = Witness::<C>::new::<false>(w1.clone(), r1cs.A.n_rows, test_rng());
252        let w2 = Witness::<C>::new::<false>(w2.clone(), r1cs.A.n_rows, test_rng());
253
254        let mut rng = ark_std::test_rng();
255        let (pedersen_params, _) = Pedersen::<C>::setup(&mut rng, r1cs.A.n_cols).unwrap();
256
257        // compute committed instances
258        let ci1 = w1
259            .commit::<Pedersen<C>, false>(&pedersen_params, x1.clone())
260            .unwrap();
261        let ci2 = w2
262            .commit::<Pedersen<C>, false>(&pedersen_params, x2.clone())
263            .unwrap();
264
265        // NIFS.P
266        let (T, cmT) =
267            NIFS::<C, Pedersen<C>>::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2)
268                .unwrap();
269
270        let poseidon_config = poseidon_canonical_config::<C::ScalarField>();
271        let mut transcript = PoseidonSponge::<C::ScalarField>::new(&poseidon_config);
272
273        let pp_hash = C::ScalarField::from(42u32); // only for test
274        let r_bits = ChallengeGadget::<C>::get_challenge_native(
275            &mut transcript,
276            pp_hash,
277            ci1.clone(),
278            ci2.clone(),
279            cmT,
280        );
281        let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
282
283        let (w3, ci3) =
284            NIFS::<C, Pedersen<C>>::fold_instances(r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap();
285
286        (
287            pedersen_params,
288            poseidon_config,
289            r1cs,
290            w1,
291            ci1,
292            w2,
293            ci2,
294            w3,
295            ci3,
296            T,
297            cmT,
298            r_bits,
299            r_Fr,
300        )
301    }
302
303    // fold 2 dummy instances and check that the folded instance holds the relaxed R1CS relation
304    #[test]
305    fn test_nifs_fold_dummy() {
306        let r1cs = get_test_r1cs::<Fr>();
307        let z1 = get_test_z(3);
308        let (_, x1) = r1cs.split_z(&z1);
309
310        let mut rng = ark_std::test_rng();
311        let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, r1cs.A.n_cols).unwrap();
312
313        // dummy instance, witness and public inputs zeroes
314        let w_dummy = Witness::<Projective>::dummy(&r1cs);
315        let mut u_dummy = w_dummy
316            .commit::<Pedersen<Projective>, false>(&pedersen_params, vec![Fr::zero(); x1.len()])
317            .unwrap();
318        u_dummy.u = Fr::zero();
319
320        let w_i = w_dummy.clone();
321        let u_i = u_dummy.clone();
322        let W_i = w_dummy.clone();
323        let U_i = u_dummy.clone();
324        r1cs.check_relation(&w_i, &u_i).unwrap();
325        r1cs.check_relation(&W_i, &U_i).unwrap();
326
327        let r_Fr = Fr::from(3_u32);
328
329        let (T, cmT) = NIFS::<Projective, Pedersen<Projective>>::compute_cmT(
330            &pedersen_params,
331            &r1cs,
332            &w_i,
333            &u_i,
334            &W_i,
335            &U_i,
336        )
337        .unwrap();
338        let (W_i1, U_i1) = NIFS::<Projective, Pedersen<Projective>>::fold_instances(
339            r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT,
340        )
341        .unwrap();
342        r1cs.check_relation(&W_i1, &U_i1).unwrap();
343    }
344
345    // fold 2 instances into one
346    #[test]
347    fn test_nifs_one_fold() {
348        let (pedersen_params, poseidon_config, r1cs, w1, ci1, w2, ci2, w3, ci3, T, cmT, _, r) =
349            prepare_simple_fold_inputs();
350
351        // NIFS.V
352        let ci3_v = NIFS::<Projective, Pedersen<Projective>>::verify(r, &ci1, &ci2, &cmT);
353        assert_eq!(ci3_v, ci3);
354
355        // check that relations hold for the 2 inputted instances and the folded one
356        r1cs.check_relation(&w1, &ci1).unwrap();
357        r1cs.check_relation(&w2, &ci2).unwrap();
358        r1cs.check_relation(&w3, &ci3).unwrap();
359
360        // check that folded commitments from folded instance (ci) are equal to folding the
361        // use folded rE, rW to commit w3
362        let ci3_expected = w3
363            .commit::<Pedersen<Projective>, false>(&pedersen_params, ci3.x.clone())
364            .unwrap();
365        assert_eq!(ci3_expected.cmE, ci3.cmE);
366        assert_eq!(ci3_expected.cmW, ci3.cmW);
367
368        // next equalities should hold since we started from two cmE of zero-vector E's
369        assert_eq!(ci3.cmE, cmT.mul(r));
370        assert_eq!(w3.E, vec_scalar_mul(T.as_slice(), &r));
371
372        // NIFS.Verify_Folded_Instance:
373        NIFS::<Projective, Pedersen<Projective>>::verify_folded_instance(r, &ci1, &ci2, &ci3, &cmT)
374            .unwrap();
375
376        // init Prover's transcript
377        let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config);
378        // init Verifier's transcript
379        let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config);
380
381        // prove the ci3.cmE, ci3.cmW, cmT commitments
382        let cm_proofs = NIFS::<Projective, Pedersen<Projective>>::prove_commitments(
383            &mut transcript_p,
384            &pedersen_params,
385            &w3,
386            &ci3,
387            T,
388            &cmT,
389        )
390        .unwrap();
391
392        // verify the ci3.cmE, ci3.cmW, cmT commitments
393        assert_eq!(cm_proofs.len(), 3);
394        Pedersen::<Projective>::verify(
395            &pedersen_params,
396            &mut transcript_v,
397            &ci3.cmE,
398            &cm_proofs[0].clone(),
399        )
400        .unwrap();
401        Pedersen::<Projective>::verify(
402            &pedersen_params,
403            &mut transcript_v,
404            &ci3.cmW,
405            &cm_proofs[1].clone(),
406        )
407        .unwrap();
408        Pedersen::<Projective>::verify(
409            &pedersen_params,
410            &mut transcript_v,
411            &cmT,
412            &cm_proofs[2].clone(),
413        )
414        .unwrap();
415    }
416
417    #[test]
418    fn test_nifs_fold_loop() {
419        let r1cs = get_test_r1cs();
420        let z = get_test_z(3);
421        let (w, x) = r1cs.split_z(&z);
422
423        let mut rng = ark_std::test_rng();
424        let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, r1cs.A.n_cols).unwrap();
425
426        // prepare the running instance
427        let mut running_instance_w =
428            Witness::<Projective>::new::<false>(w.clone(), r1cs.A.n_rows, test_rng());
429        let mut running_committed_instance = running_instance_w
430            .commit::<Pedersen<Projective>, false>(&pedersen_params, x)
431            .unwrap();
432
433        r1cs.check_relation(&running_instance_w, &running_committed_instance)
434            .unwrap();
435
436        let num_iters = 10;
437        for i in 0..num_iters {
438            // prepare the incoming instance
439            let incoming_instance_z = get_test_z(i + 4);
440            let (w, x) = r1cs.split_z(&incoming_instance_z);
441            let incoming_instance_w =
442                Witness::<Projective>::new::<false>(w.clone(), r1cs.A.n_rows, test_rng());
443            let incoming_committed_instance = incoming_instance_w
444                .commit::<Pedersen<Projective>, false>(&pedersen_params, x)
445                .unwrap();
446            r1cs.check_relation(&incoming_instance_w, &incoming_committed_instance)
447                .unwrap();
448
449            let r = Fr::rand(&mut rng); // folding challenge would come from the RO
450
451            // NIFS.P
452            let (T, cmT) = NIFS::<Projective, Pedersen<Projective>>::compute_cmT(
453                &pedersen_params,
454                &r1cs,
455                &running_instance_w,
456                &running_committed_instance,
457                &incoming_instance_w,
458                &incoming_committed_instance,
459            )
460            .unwrap();
461            let (folded_w, _) = NIFS::<Projective, Pedersen<Projective>>::fold_instances(
462                r,
463                &running_instance_w,
464                &running_committed_instance,
465                &incoming_instance_w,
466                &incoming_committed_instance,
467                &T,
468                cmT,
469            )
470            .unwrap();
471
472            // NIFS.V
473            let folded_committed_instance = NIFS::<Projective, Pedersen<Projective>>::verify(
474                r,
475                &running_committed_instance,
476                &incoming_committed_instance,
477                &cmT,
478            );
479
480            r1cs.check_relation(&folded_w, &folded_committed_instance)
481                .unwrap();
482
483            // set running_instance for next loop iteration
484            running_instance_w = folded_w;
485            running_committed_instance = folded_committed_instance;
486        }
487    }
488}