arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/nova/zk.rs
folding-schemes/src/folding/nova/zk.rsRaw
  1/// Implements Nova's zero-knowledge layer, as described in https://eprint.iacr.org/2023/573.pdf.
  2///
  3/// Remark: this zk layer implementation only covers a subset of the use cases:
  4///
  5/// We identify 3 interesting places to use the nova zk-layer: one before all the folding pipeline
  6/// (Use-case-1), one at the end of the folding pipeline right before the final Decider SNARK
  7/// proof (Use-case-2), and a third one for cases where compressed SNARK proofs are not needed, and
  8/// just IVC proofs (bigger than SNARK proofs) suffice (Use-case-3):
  9///
 10/// * Use-case-1: at the beginning of the folding pipeline, right when the user has their original
 11///   instance prior to be folded into the running instance, the user can fold it with the
 12///   random-satisfying-instance to then have a blinded instance that can be sent to a server that
 13///   will fold it with the running instance.
 14///     --> In this one, the user could externalize all the IVC folding and also the Decider
 15///     final proof generation to a server.
 16/// * Use-case-2: at the end of all the IVC folding steps (after n iterations of nova.prove_step),
 17///   to 'blind' the IVC proof so then it can be sent to a server that will generate the final
 18///   decider snark proof.
 19///     --> In this one, the user could externalize the Decider final proof generation to a
 20///     server.
 21/// * Use-case-3: the user does not care about the Decider (final compressed SNARK proof), and
 22///   wants to generate a zk-proof of the IVC state to an IVC verifier (without any SNARK proof
 23///   involved). Note that this proof will be much bigger and expensive to verify than a Decider
 24///   SNARK proof.
 25///
 26/// The current implementation covers the Use-case-3.
 27/// Use-case-1 can be achieved directly by a simpler version of the zk IVC scheme skipping steps
 28/// and implemented directly at the app level by folding the original instance with a randomized
 29/// instance (steps 2,3,4 from section D.4 of the [HyperNova](https://eprint.iacr.org/2023/573.pdf)
 30/// paper).
 31/// And the Use-case-2 would require a modified version of the Decider circuits.
 32///
 33use ark_crypto_primitives::sponge::CryptographicSponge;
 34use ark_ff::{BigInteger, PrimeField};
 35use ark_std::{One, Zero};
 36
 37use crate::{
 38    arith::r1cs::{RelaxedR1CS, R1CS},
 39    folding::traits::CommittedInstanceOps,
 40    RngCore,
 41};
 42use ark_crypto_primitives::sponge::{
 43    poseidon::{PoseidonConfig, PoseidonSponge},
 44    Absorb,
 45};
 46use ark_ec::{CurveGroup, Group};
 47use ark_r1cs_std::{
 48    groups::{CurveVar, GroupOpsBounds},
 49    ToConstraintFieldGadget,
 50};
 51
 52use crate::{commitment::CommitmentScheme, folding::circuits::CF2, frontend::FCircuit, Error};
 53
 54use super::{circuits::ChallengeGadget, nifs::NIFS, CommittedInstance, Nova, Witness};
 55
 56// We use the same definition of a folding proof as in https://eprint.iacr.org/2023/969.pdf
 57// It consists in the commitment to the T term
 58pub struct FoldingProof<C: CurveGroup> {
 59    cmT: C,
 60}
 61
 62pub struct RandomizedIVCProof<C1: CurveGroup, C2: CurveGroup> {
 63    pub U_i: CommittedInstance<C1>,
 64    pub u_i: CommittedInstance<C1>,
 65    pub U_r: CommittedInstance<C1>,
 66    pub pi: FoldingProof<C1>,
 67    pub pi_prime: FoldingProof<C1>,
 68    pub W_i_prime: Witness<C1>,
 69    pub cf_U_i: CommittedInstance<C2>,
 70    pub cf_W_i: Witness<C2>,
 71}
 72
 73impl<C1: CurveGroup, C2: CurveGroup> RandomizedIVCProof<C1, C2>
 74where
 75    <C1 as Group>::ScalarField: Absorb,
 76    <C1 as CurveGroup>::BaseField: PrimeField,
 77{
 78    /// Computes challenge required before folding instances
 79    fn get_folding_challenge(
 80        sponge: &mut PoseidonSponge<C1::ScalarField>,
 81        pp_hash: C1::ScalarField,
 82        U_i: CommittedInstance<C1>,
 83        u_i: CommittedInstance<C1>,
 84        cmT: C1,
 85    ) -> Result<C1::ScalarField, Error> {
 86        let r_bits = ChallengeGadget::<C1>::get_challenge_native(sponge, pp_hash, U_i, u_i, cmT);
 87        C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)).ok_or(Error::OutOfBounds)
 88    }
 89
 90    /// Compute a zero-knowledge proof of a Nova IVC proof
 91    /// It implements the prover of appendix D.4.in https://eprint.iacr.org/2023/573.pdf
 92    /// For further details on why folding is hiding, see lemma 9
 93    pub fn new<
 94        GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
 95        GC2: CurveVar<C2, CF2<C2>>,
 96        FC: FCircuit<C1::ScalarField>,
 97        CS1: CommitmentScheme<C1, true>,
 98        CS2: CommitmentScheme<C2, true>,
 99    >(
100        nova: &Nova<C1, GC1, C2, GC2, FC, CS1, CS2, true>,
101        mut rng: impl RngCore,
102    ) -> Result<RandomizedIVCProof<C1, C2>, Error>
103    where
104        <C1 as Group>::ScalarField: Absorb,
105        <C2 as Group>::ScalarField: Absorb,
106        <C2 as Group>::ScalarField: PrimeField,
107        <C2 as CurveGroup>::BaseField: PrimeField,
108        <C2 as CurveGroup>::BaseField: Absorb,
109        for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
110        GC2: ToConstraintFieldGadget<<C2 as CurveGroup>::BaseField>,
111        C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
112    {
113        let mut challenges_sponge = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config);
114
115        // I. Compute proof for 'regular' instances
116        // 1. Fold the instance-witness pairs (U_i, W_i) with (u_i, w_i)
117        // a. Compute T
118        let (T, cmT) = NIFS::<C1, CS1, true>::compute_cmT(
119            &nova.cs_pp,
120            &nova.r1cs,
121            &nova.w_i,
122            &nova.u_i,
123            &nova.W_i,
124            &nova.U_i,
125        )?;
126
127        // b. Compute folding challenge
128        let r = RandomizedIVCProof::<C1, C2>::get_folding_challenge(
129            &mut challenges_sponge,
130            nova.pp_hash,
131            nova.U_i.clone(),
132            nova.u_i.clone(),
133            cmT,
134        )?;
135
136        // c. Compute fold
137        let (W_f, U_f) = NIFS::<C1, CS1, true>::fold_instances(
138            r, &nova.w_i, &nova.u_i, &nova.W_i, &nova.U_i, &T, cmT,
139        )?;
140
141        // d. Store folding proof
142        let pi = FoldingProof { cmT };
143
144        // 2. Sample a satisfying relaxed R1CS instance-witness pair (W_r, U_r)
145        let (W_r, U_r) = nova.r1cs.sample::<CS1>(&nova.cs_pp, &mut rng)?;
146
147        // 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r)
148        // a. Compute T
149        let (T_i_prime, cmT_i_prime) =
150            NIFS::<C1, CS1, true>::compute_cmT(&nova.cs_pp, &nova.r1cs, &W_f, &U_f, &W_r, &U_r)?;
151
152        // b. Compute folding challenge
153        let r_2 = RandomizedIVCProof::<C1, C2>::get_folding_challenge(
154            &mut challenges_sponge,
155            nova.pp_hash,
156            U_f.clone(),
157            U_r.clone(),
158            cmT_i_prime,
159        )?;
160
161        // c. Compute fold
162        let (W_i_prime, _) = NIFS::<C1, CS1, true>::fold_instances(
163            r_2,
164            &W_f,
165            &U_f,
166            &W_r,
167            &U_r,
168            &T_i_prime,
169            cmT_i_prime,
170        )?;
171
172        // d. Store folding proof
173        let pi_prime = FoldingProof { cmT: cmT_i_prime };
174
175        Ok(RandomizedIVCProof {
176            U_i: nova.U_i.clone(),
177            u_i: nova.u_i.clone(),
178            U_r,
179            pi,
180            pi_prime,
181            W_i_prime,
182            cf_U_i: nova.cf_U_i.clone(),
183            cf_W_i: nova.cf_W_i.clone(),
184        })
185    }
186
187    /// Verify a zero-knowledge proof of a Nova IVC proof
188    /// It implements the verifier of appendix D.4. in https://eprint.iacr.org/2023/573.pdf
189    #[allow(clippy::too_many_arguments)]
190    pub fn verify<
191        CS1: CommitmentScheme<C1, true>,
192        GC2: CurveVar<C2, CF2<C2>>,
193        CS2: CommitmentScheme<C2, true>,
194    >(
195        r1cs: &R1CS<C1::ScalarField>,
196        cf_r1cs: &R1CS<C2::ScalarField>,
197        pp_hash: C1::ScalarField,
198        poseidon_config: &PoseidonConfig<C1::ScalarField>,
199        i: C1::ScalarField,
200        z_0: Vec<C1::ScalarField>,
201        z_i: Vec<C1::ScalarField>,
202        proof: &RandomizedIVCProof<C1, C2>,
203    ) -> Result<(), Error>
204    where
205        <C1 as Group>::ScalarField: Absorb,
206        <C2 as Group>::ScalarField: Absorb,
207        <C2 as CurveGroup>::BaseField: PrimeField,
208        <C2 as CurveGroup>::BaseField: Absorb,
209        for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
210        GC2: ToConstraintFieldGadget<<C2 as CurveGroup>::BaseField>,
211        C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
212    {
213        // Handles case where i=0
214        if i == C1::ScalarField::zero() {
215            if z_0 == z_i {
216                return Ok(());
217            } else {
218                return Err(Error::zkIVCVerificationFail);
219            }
220        }
221
222        // 1. Check that u_i.x is correct - including the cyclefold running instance
223        // a. Check length
224        if proof.u_i.x.len() != 2 {
225            return Err(Error::IVCVerificationFail);
226        }
227
228        // b. Check computed hashes are correct
229        let mut sponge = PoseidonSponge::<C1::ScalarField>::new(poseidon_config);
230        let expected_u_i_x = proof.U_i.hash(&sponge, pp_hash, i, &z_0, &z_i);
231        if expected_u_i_x != proof.u_i.x[0] {
232            return Err(Error::zkIVCVerificationFail);
233        }
234
235        let expected_cf_u_i_x = proof.cf_U_i.hash_cyclefold(&sponge, pp_hash);
236        if expected_cf_u_i_x != proof.u_i.x[1] {
237            return Err(Error::IVCVerificationFail);
238        }
239
240        // 2. Check that u_i values are correct
241        if !proof.u_i.cmE.is_zero() || proof.u_i.u != C1::ScalarField::one() {
242            return Err(Error::zkIVCVerificationFail);
243        }
244
245        // 3. Obtain the U_f folded instance
246        // a. Compute folding challenge
247        let r = RandomizedIVCProof::<C1, C2>::get_folding_challenge(
248            &mut sponge,
249            pp_hash,
250            proof.U_i.clone(),
251            proof.u_i.clone(),
252            proof.pi.cmT,
253        )?;
254
255        // b. Get the U_f instance
256        let U_f = NIFS::<C1, CS1, true>::fold_committed_instance(
257            r,
258            &proof.u_i,
259            &proof.U_i,
260            &proof.pi.cmT,
261        );
262
263        // 4. Obtain the U^{\prime}_i folded instance
264        // a. Compute folding challenge
265        let r_2 = RandomizedIVCProof::<C1, C2>::get_folding_challenge(
266            &mut sponge,
267            pp_hash,
268            U_f.clone(),
269            proof.U_r.clone(),
270            proof.pi_prime.cmT,
271        )?;
272
273        // b. Compute fold
274        let U_i_prime = NIFS::<C1, CS1, true>::fold_committed_instance(
275            r_2,
276            &U_f,
277            &proof.U_r,
278            &proof.pi_prime.cmT,
279        );
280
281        // 5. Check that W^{\prime}_i is a satisfying witness
282        r1cs.check_relaxed_relation(&proof.W_i_prime, &U_i_prime)?;
283
284        // 6. Check that the cyclefold instance-witness pair satisfies the cyclefold relaxed r1cs
285        cf_r1cs.check_relaxed_relation(&proof.cf_W_i, &proof.cf_U_i)?;
286
287        Ok(())
288    }
289}
290
291#[cfg(test)]
292pub mod tests {
293    use super::*;
294    use crate::commitment::pedersen::Pedersen;
295    use crate::folding::nova::tests::test_ivc_opt;
296    use crate::frontend::utils::CubicFCircuit;
297    use crate::transcript::poseidon::poseidon_canonical_config;
298    use ark_bn254::{Fr, G1Projective as Projective};
299    use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
300    use rand::rngs::OsRng;
301
302    // Tests zk proof generation and verification for a valid nova IVC proof
303    #[test]
304    fn test_zk_nova_ivc() {
305        let mut rng = OsRng;
306        let poseidon_config = poseidon_canonical_config::<Fr>();
307        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
308        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
309            poseidon_config.clone(),
310            F_circuit,
311            3,
312        );
313
314        let proof = RandomizedIVCProof::new(&nova, &mut rng).unwrap();
315        let verify = RandomizedIVCProof::verify::<
316            Pedersen<Projective, true>,
317            GVar2,
318            Pedersen<Projective2, true>,
319        >(
320            &nova.r1cs,
321            &nova.cf_r1cs,
322            nova.pp_hash,
323            &nova.poseidon_config,
324            nova.i,
325            nova.z_0,
326            nova.z_i,
327            &proof,
328        );
329        assert!(verify.is_ok());
330    }
331
332    #[test]
333    fn test_zk_nova_when_i_is_zero() {
334        let mut rng = OsRng;
335        let poseidon_config = poseidon_canonical_config::<Fr>();
336        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
337        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
338            poseidon_config.clone(),
339            F_circuit,
340            0,
341        );
342
343        let proof = RandomizedIVCProof::new(&nova, &mut rng).unwrap();
344        let verify = RandomizedIVCProof::verify::<
345            Pedersen<Projective, true>,
346            GVar2,
347            Pedersen<Projective2, true>,
348        >(
349            &nova.r1cs,
350            &nova.cf_r1cs,
351            nova.pp_hash,
352            &nova.poseidon_config,
353            nova.i,
354            nova.z_0,
355            nova.z_i,
356            &proof,
357        );
358        assert!(verify.is_ok());
359    }
360
361    #[test]
362    fn test_zk_nova_verification_fails_with_wrong_running_instance() {
363        let mut rng = OsRng;
364        let poseidon_config = poseidon_canonical_config::<Fr>();
365        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
366        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
367            poseidon_config.clone(),
368            F_circuit,
369            3,
370        );
371        let (_, sampled_committed_instance) = nova
372            .r1cs
373            .sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
374            .unwrap();
375
376        // proof verification fails with incorrect running instance
377        let mut nova_with_incorrect_running_instance = nova.clone();
378        nova_with_incorrect_running_instance.U_i = sampled_committed_instance;
379        let incorrect_proof =
380            RandomizedIVCProof::new(&nova_with_incorrect_running_instance, &mut rng).unwrap();
381        let verify = RandomizedIVCProof::verify::<
382            Pedersen<Projective, true>,
383            GVar2,
384            Pedersen<Projective2, true>,
385        >(
386            &nova_with_incorrect_running_instance.r1cs,
387            &nova_with_incorrect_running_instance.cf_r1cs,
388            nova_with_incorrect_running_instance.pp_hash,
389            &nova_with_incorrect_running_instance.poseidon_config,
390            nova_with_incorrect_running_instance.i,
391            nova_with_incorrect_running_instance.z_0,
392            nova_with_incorrect_running_instance.z_i,
393            &incorrect_proof,
394        );
395        assert!(verify.is_err());
396    }
397
398    #[test]
399    fn test_zk_nova_verification_fails_with_wrong_running_witness() {
400        let mut rng = OsRng;
401        let poseidon_config = poseidon_canonical_config::<Fr>();
402        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
403        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
404            poseidon_config.clone(),
405            F_circuit,
406            3,
407        );
408        let (sampled_committed_witness, _) = nova
409            .r1cs
410            .sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
411            .unwrap();
412
413        // proof generation fails with incorrect running witness
414        let mut nova_with_incorrect_running_witness = nova.clone();
415        nova_with_incorrect_running_witness.W_i = sampled_committed_witness;
416        let incorrect_proof =
417            RandomizedIVCProof::new(&nova_with_incorrect_running_witness, &mut rng).unwrap();
418        let verify = RandomizedIVCProof::verify::<
419            Pedersen<Projective, true>,
420            GVar2,
421            Pedersen<Projective2, true>,
422        >(
423            &nova_with_incorrect_running_witness.r1cs,
424            &nova_with_incorrect_running_witness.cf_r1cs,
425            nova_with_incorrect_running_witness.pp_hash,
426            &nova_with_incorrect_running_witness.poseidon_config,
427            nova_with_incorrect_running_witness.i,
428            nova_with_incorrect_running_witness.z_0,
429            nova_with_incorrect_running_witness.z_i,
430            &incorrect_proof,
431        );
432        assert!(verify.is_err());
433    }
434}