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::R1CS, Arith, ArithSampler},
 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
146            .r1cs
147            .sample_witness_instance::<CS1>(&nova.cs_pp, &mut rng)?;
148
149        // 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r)
150        // a. Compute T
151        let (T_i_prime, cmT_i_prime) =
152            NIFS::<C1, CS1, true>::compute_cmT(&nova.cs_pp, &nova.r1cs, &W_f, &U_f, &W_r, &U_r)?;
153
154        // b. Compute folding challenge
155        let r_2 = RandomizedIVCProof::<C1, C2>::get_folding_challenge(
156            &mut challenges_sponge,
157            nova.pp_hash,
158            U_f.clone(),
159            U_r.clone(),
160            cmT_i_prime,
161        )?;
162
163        // c. Compute fold
164        let (W_i_prime, _) = NIFS::<C1, CS1, true>::fold_instances(
165            r_2,
166            &W_f,
167            &U_f,
168            &W_r,
169            &U_r,
170            &T_i_prime,
171            cmT_i_prime,
172        )?;
173
174        // d. Store folding proof
175        let pi_prime = FoldingProof { cmT: cmT_i_prime };
176
177        Ok(RandomizedIVCProof {
178            U_i: nova.U_i.clone(),
179            u_i: nova.u_i.clone(),
180            U_r,
181            pi,
182            pi_prime,
183            W_i_prime,
184            cf_U_i: nova.cf_U_i.clone(),
185            cf_W_i: nova.cf_W_i.clone(),
186        })
187    }
188
189    /// Verify a zero-knowledge proof of a Nova IVC proof
190    /// It implements the verifier of appendix D.4. in https://eprint.iacr.org/2023/573.pdf
191    #[allow(clippy::too_many_arguments)]
192    pub fn verify<
193        CS1: CommitmentScheme<C1, true>,
194        GC2: CurveVar<C2, CF2<C2>>,
195        CS2: CommitmentScheme<C2, true>,
196    >(
197        r1cs: &R1CS<C1::ScalarField>,
198        cf_r1cs: &R1CS<C2::ScalarField>,
199        pp_hash: C1::ScalarField,
200        poseidon_config: &PoseidonConfig<C1::ScalarField>,
201        i: C1::ScalarField,
202        z_0: Vec<C1::ScalarField>,
203        z_i: Vec<C1::ScalarField>,
204        proof: &RandomizedIVCProof<C1, C2>,
205    ) -> Result<(), Error>
206    where
207        <C1 as Group>::ScalarField: Absorb,
208        <C2 as Group>::ScalarField: Absorb,
209        <C2 as CurveGroup>::BaseField: PrimeField,
210        <C2 as CurveGroup>::BaseField: Absorb,
211        for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
212        GC2: ToConstraintFieldGadget<<C2 as CurveGroup>::BaseField>,
213        C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
214    {
215        // Handles case where i=0
216        if i == C1::ScalarField::zero() {
217            if z_0 == z_i {
218                return Ok(());
219            } else {
220                return Err(Error::zkIVCVerificationFail);
221            }
222        }
223
224        // 1. Check that u_i.x is correct - including the cyclefold running instance
225        // a. Check length
226        if proof.u_i.x.len() != 2 {
227            return Err(Error::IVCVerificationFail);
228        }
229
230        // b. Check computed hashes are correct
231        let mut sponge = PoseidonSponge::<C1::ScalarField>::new(poseidon_config);
232        let expected_u_i_x = proof.U_i.hash(&sponge, pp_hash, i, &z_0, &z_i);
233        if expected_u_i_x != proof.u_i.x[0] {
234            return Err(Error::zkIVCVerificationFail);
235        }
236
237        let expected_cf_u_i_x = proof.cf_U_i.hash_cyclefold(&sponge, pp_hash);
238        if expected_cf_u_i_x != proof.u_i.x[1] {
239            return Err(Error::IVCVerificationFail);
240        }
241
242        // 2. Check that u_i values are correct
243        if !proof.u_i.cmE.is_zero() || proof.u_i.u != C1::ScalarField::one() {
244            return Err(Error::zkIVCVerificationFail);
245        }
246
247        // 3. Obtain the U_f folded instance
248        // a. Compute folding challenge
249        let r = RandomizedIVCProof::<C1, C2>::get_folding_challenge(
250            &mut sponge,
251            pp_hash,
252            proof.U_i.clone(),
253            proof.u_i.clone(),
254            proof.pi.cmT,
255        )?;
256
257        // b. Get the U_f instance
258        let U_f = NIFS::<C1, CS1, true>::fold_committed_instance(
259            r,
260            &proof.u_i,
261            &proof.U_i,
262            &proof.pi.cmT,
263        );
264
265        // 4. Obtain the U^{\prime}_i folded instance
266        // a. Compute folding challenge
267        let r_2 = RandomizedIVCProof::<C1, C2>::get_folding_challenge(
268            &mut sponge,
269            pp_hash,
270            U_f.clone(),
271            proof.U_r.clone(),
272            proof.pi_prime.cmT,
273        )?;
274
275        // b. Compute fold
276        let U_i_prime = NIFS::<C1, CS1, true>::fold_committed_instance(
277            r_2,
278            &U_f,
279            &proof.U_r,
280            &proof.pi_prime.cmT,
281        );
282
283        // 5. Check that W^{\prime}_i is a satisfying witness
284        r1cs.check_relation(&proof.W_i_prime, &U_i_prime)?;
285
286        // 6. Check that the cyclefold instance-witness pair satisfies the cyclefold relaxed r1cs
287        cf_r1cs.check_relation(&proof.cf_W_i, &proof.cf_U_i)?;
288
289        Ok(())
290    }
291}
292
293#[cfg(test)]
294pub mod tests {
295    use super::*;
296    use crate::commitment::pedersen::Pedersen;
297    use crate::folding::nova::tests::test_ivc_opt;
298    use crate::frontend::utils::CubicFCircuit;
299    use crate::transcript::poseidon::poseidon_canonical_config;
300    use ark_bn254::{Fr, G1Projective as Projective};
301    use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
302    use rand::rngs::OsRng;
303
304    // Tests zk proof generation and verification for a valid nova IVC proof
305    #[test]
306    fn test_zk_nova_ivc() {
307        let mut rng = OsRng;
308        let poseidon_config = poseidon_canonical_config::<Fr>();
309        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
310        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
311            poseidon_config.clone(),
312            F_circuit,
313            3,
314        );
315
316        let proof = RandomizedIVCProof::new(&nova, &mut rng).unwrap();
317        let verify = RandomizedIVCProof::verify::<
318            Pedersen<Projective, true>,
319            GVar2,
320            Pedersen<Projective2, true>,
321        >(
322            &nova.r1cs,
323            &nova.cf_r1cs,
324            nova.pp_hash,
325            &nova.poseidon_config,
326            nova.i,
327            nova.z_0,
328            nova.z_i,
329            &proof,
330        );
331        assert!(verify.is_ok());
332    }
333
334    #[test]
335    fn test_zk_nova_when_i_is_zero() {
336        let mut rng = OsRng;
337        let poseidon_config = poseidon_canonical_config::<Fr>();
338        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
339        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
340            poseidon_config.clone(),
341            F_circuit,
342            0,
343        );
344
345        let proof = RandomizedIVCProof::new(&nova, &mut rng).unwrap();
346        let verify = RandomizedIVCProof::verify::<
347            Pedersen<Projective, true>,
348            GVar2,
349            Pedersen<Projective2, true>,
350        >(
351            &nova.r1cs,
352            &nova.cf_r1cs,
353            nova.pp_hash,
354            &nova.poseidon_config,
355            nova.i,
356            nova.z_0,
357            nova.z_i,
358            &proof,
359        );
360        assert!(verify.is_ok());
361    }
362
363    #[test]
364    fn test_zk_nova_verification_fails_with_wrong_running_instance() {
365        let mut rng = OsRng;
366        let poseidon_config = poseidon_canonical_config::<Fr>();
367        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
368        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
369            poseidon_config.clone(),
370            F_circuit,
371            3,
372        );
373        let (_, sampled_committed_instance) = nova
374            .r1cs
375            .sample_witness_instance::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
376            .unwrap();
377
378        // proof verification fails with incorrect running instance
379        let mut nova_with_incorrect_running_instance = nova.clone();
380        nova_with_incorrect_running_instance.U_i = sampled_committed_instance;
381        let incorrect_proof =
382            RandomizedIVCProof::new(&nova_with_incorrect_running_instance, &mut rng).unwrap();
383        let verify = RandomizedIVCProof::verify::<
384            Pedersen<Projective, true>,
385            GVar2,
386            Pedersen<Projective2, true>,
387        >(
388            &nova_with_incorrect_running_instance.r1cs,
389            &nova_with_incorrect_running_instance.cf_r1cs,
390            nova_with_incorrect_running_instance.pp_hash,
391            &nova_with_incorrect_running_instance.poseidon_config,
392            nova_with_incorrect_running_instance.i,
393            nova_with_incorrect_running_instance.z_0,
394            nova_with_incorrect_running_instance.z_i,
395            &incorrect_proof,
396        );
397        assert!(verify.is_err());
398    }
399
400    #[test]
401    fn test_zk_nova_verification_fails_with_wrong_running_witness() {
402        let mut rng = OsRng;
403        let poseidon_config = poseidon_canonical_config::<Fr>();
404        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
405        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
406            poseidon_config.clone(),
407            F_circuit,
408            3,
409        );
410        let (sampled_committed_witness, _) = nova
411            .r1cs
412            .sample_witness_instance::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
413            .unwrap();
414
415        // proof generation fails with incorrect running witness
416        let mut nova_with_incorrect_running_witness = nova.clone();
417        nova_with_incorrect_running_witness.W_i = sampled_committed_witness;
418        let incorrect_proof =
419            RandomizedIVCProof::new(&nova_with_incorrect_running_witness, &mut rng).unwrap();
420        let verify = RandomizedIVCProof::verify::<
421            Pedersen<Projective, true>,
422            GVar2,
423            Pedersen<Projective2, true>,
424        >(
425            &nova_with_incorrect_running_witness.r1cs,
426            &nova_with_incorrect_running_witness.cf_r1cs,
427            nova_with_incorrect_running_witness.pp_hash,
428            &nova_with_incorrect_running_witness.poseidon_config,
429            nova_with_incorrect_running_witness.i,
430            nova_with_incorrect_running_witness.z_0,
431            nova_with_incorrect_running_witness.z_i,
432            &incorrect_proof,
433        );
434        assert!(verify.is_err());
435    }
436}