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). In this use-case, the zk is only added at the last IVCProof. Note that this proof
 24///   will be much bigger and expensive to verify than a Decider 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_ff::PrimeField;
 34use ark_std::{One, Zero};
 35
 36use crate::{
 37    arith::{r1cs::R1CS, Arith, ArithSampler},
 38    folding::traits::CommittedInstanceOps,
 39    RngCore,
 40};
 41use ark_crypto_primitives::sponge::{
 42    poseidon::{PoseidonConfig, PoseidonSponge},
 43    Absorb, CryptographicSponge,
 44};
 45use ark_ec::{CurveGroup, Group};
 46use ark_r1cs_std::{groups::CurveVar, ToConstraintFieldGadget};
 47
 48use crate::{commitment::CommitmentScheme, folding::circuits::CF2, frontend::FCircuit, Error};
 49
 50use super::{
 51    nifs::{nova::NIFS, NIFSTrait},
 52    CommittedInstance, Nova, Witness,
 53};
 54
 55pub struct RandomizedIVCProof<C1: CurveGroup, C2: CurveGroup> {
 56    pub U_i: CommittedInstance<C1>,
 57    pub u_i: CommittedInstance<C1>,
 58    pub U_r: CommittedInstance<C1>,
 59    pub pi: C1,       // proof = cmT
 60    pub pi_prime: C1, // proof' = cmT'
 61    pub W_i_prime: Witness<C1>,
 62    pub cf_U_i: CommittedInstance<C2>,
 63    pub cf_W_i: Witness<C2>,
 64}
 65
 66impl<C1: CurveGroup, C2: CurveGroup> RandomizedIVCProof<C1, C2>
 67where
 68    <C1 as Group>::ScalarField: Absorb,
 69    <C1 as CurveGroup>::BaseField: PrimeField,
 70{
 71    /// Compute a zero-knowledge proof of a Nova IVC proof
 72    /// It implements the prover of appendix D.4.in https://eprint.iacr.org/2023/573.pdf
 73    /// For further details on why folding is hiding, see lemma 9
 74    pub fn new<
 75        GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
 76        GC2: CurveVar<C2, CF2<C2>>,
 77        FC: FCircuit<C1::ScalarField>,
 78        CS1: CommitmentScheme<C1, true>,
 79        CS2: CommitmentScheme<C2, true>,
 80    >(
 81        nova: &Nova<C1, GC1, C2, GC2, FC, CS1, CS2, true>,
 82        mut rng: impl RngCore,
 83    ) -> Result<RandomizedIVCProof<C1, C2>, Error>
 84    where
 85        <C1 as Group>::ScalarField: Absorb,
 86        <C2 as Group>::ScalarField: Absorb,
 87        <C2 as Group>::ScalarField: PrimeField,
 88        <C2 as CurveGroup>::BaseField: PrimeField,
 89        <C2 as CurveGroup>::BaseField: Absorb,
 90        GC2: ToConstraintFieldGadget<<C2 as CurveGroup>::BaseField>,
 91        C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
 92    {
 93        let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config);
 94
 95        // I. Compute proof for 'regular' instances
 96        // 1. Fold the instance-witness pairs (U_i, W_i) with (u_i, w_i)
 97        let (W_f, U_f, cmT, _) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, true>::prove(
 98            &nova.cs_pp,
 99            &nova.r1cs,
100            &mut transcript,
101            nova.pp_hash,
102            &nova.w_i,
103            &nova.u_i,
104            &nova.W_i,
105            &nova.U_i,
106        )?;
107
108        // 2. Sample a satisfying relaxed R1CS instance-witness pair (W_r, U_r)
109        let (W_r, U_r) = nova
110            .r1cs
111            .sample_witness_instance::<CS1>(&nova.cs_pp, &mut rng)?;
112
113        // 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r)
114        let (W_i_prime, _, cmT_i_prime, _) =
115            NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, true>::prove(
116                &nova.cs_pp,
117                &nova.r1cs,
118                &mut transcript,
119                nova.pp_hash,
120                &W_f,
121                &U_f,
122                &W_r,
123                &U_r,
124            )?;
125
126        Ok(RandomizedIVCProof {
127            U_i: nova.U_i.clone(),
128            u_i: nova.u_i.clone(),
129            U_r,
130            pi: cmT,
131            pi_prime: cmT_i_prime,
132            W_i_prime,
133            cf_U_i: nova.cf_U_i.clone(),
134            cf_W_i: nova.cf_W_i.clone(),
135        })
136    }
137
138    /// Verify a zero-knowledge proof of a Nova IVC proof
139    /// It implements the verifier of appendix D.4. in https://eprint.iacr.org/2023/573.pdf
140    #[allow(clippy::too_many_arguments)]
141    pub fn verify<
142        CS1: CommitmentScheme<C1, true>,
143        GC2: CurveVar<C2, CF2<C2>>,
144        CS2: CommitmentScheme<C2, true>,
145    >(
146        r1cs: &R1CS<C1::ScalarField>,
147        cf_r1cs: &R1CS<C2::ScalarField>,
148        pp_hash: C1::ScalarField,
149        poseidon_config: &PoseidonConfig<C1::ScalarField>,
150        i: C1::ScalarField,
151        z_0: Vec<C1::ScalarField>,
152        z_i: Vec<C1::ScalarField>,
153        proof: &RandomizedIVCProof<C1, C2>,
154    ) -> Result<(), Error>
155    where
156        <C1 as Group>::ScalarField: Absorb,
157        <C2 as Group>::ScalarField: Absorb,
158        <C2 as CurveGroup>::BaseField: PrimeField,
159        <C2 as CurveGroup>::BaseField: Absorb,
160        GC2: ToConstraintFieldGadget<<C2 as CurveGroup>::BaseField>,
161        C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
162    {
163        // Handles case where i=0
164        if i == C1::ScalarField::zero() {
165            if z_0 == z_i {
166                return Ok(());
167            } else {
168                return Err(Error::zkIVCVerificationFail);
169            }
170        }
171
172        // 1. Check that u_i.x is correct - including the cyclefold running instance
173        // a. Check length
174        if proof.u_i.x.len() != 2 {
175            return Err(Error::IVCVerificationFail);
176        }
177
178        // b. Check computed hashes are correct
179        let sponge = PoseidonSponge::<C1::ScalarField>::new(poseidon_config);
180        let expected_u_i_x = proof.U_i.hash(&sponge, pp_hash, i, &z_0, &z_i);
181        if expected_u_i_x != proof.u_i.x[0] {
182            return Err(Error::zkIVCVerificationFail);
183        }
184
185        let expected_cf_u_i_x = proof.cf_U_i.hash_cyclefold(&sponge, pp_hash);
186        if expected_cf_u_i_x != proof.u_i.x[1] {
187            return Err(Error::IVCVerificationFail);
188        }
189
190        // 2. Check that u_i values are correct
191        if !proof.u_i.cmE.is_zero() || proof.u_i.u != C1::ScalarField::one() {
192            return Err(Error::zkIVCVerificationFail);
193        }
194
195        let mut transcript = PoseidonSponge::<C1::ScalarField>::new(poseidon_config);
196        // 3. Obtain the U_f folded instance
197        let (U_f, _) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, true>::verify(
198            &mut transcript,
199            pp_hash,
200            &proof.u_i,
201            &proof.U_i,
202            &proof.pi,
203        )?;
204
205        // 4. Obtain the U^{\prime}_i folded instance
206        let (U_i_prime, _) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, true>::verify(
207            &mut transcript,
208            pp_hash,
209            &U_f,
210            &proof.U_r,
211            &proof.pi_prime,
212        )?;
213
214        // 5. Check that W^{\prime}_i is a satisfying witness
215        r1cs.check_relation(&proof.W_i_prime, &U_i_prime)?;
216
217        // 6. Check that the cyclefold instance-witness pair satisfies the cyclefold relaxed r1cs
218        cf_r1cs.check_relation(&proof.cf_W_i, &proof.cf_U_i)?;
219
220        Ok(())
221    }
222}
223
224#[cfg(test)]
225pub mod tests {
226    use super::*;
227    use crate::commitment::pedersen::Pedersen;
228    use crate::folding::nova::tests::test_ivc_opt;
229    use crate::frontend::utils::CubicFCircuit;
230    use crate::transcript::poseidon::poseidon_canonical_config;
231    use ark_bn254::{Fr, G1Projective as Projective};
232    use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
233    use rand::rngs::OsRng;
234
235    // Tests zk proof generation and verification for a valid nova IVC proof
236    #[test]
237    fn test_zk_nova_ivc() {
238        let mut rng = OsRng;
239        let poseidon_config = poseidon_canonical_config::<Fr>();
240        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
241        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
242            poseidon_config.clone(),
243            F_circuit,
244            3,
245        );
246
247        let proof = RandomizedIVCProof::new(&nova, &mut rng).unwrap();
248        let verify = RandomizedIVCProof::verify::<
249            Pedersen<Projective, true>,
250            GVar2,
251            Pedersen<Projective2, true>,
252        >(
253            &nova.r1cs,
254            &nova.cf_r1cs,
255            nova.pp_hash,
256            &nova.poseidon_config,
257            nova.i,
258            nova.z_0,
259            nova.z_i,
260            &proof,
261        );
262        assert!(verify.is_ok());
263    }
264
265    #[test]
266    fn test_zk_nova_when_i_is_zero() {
267        let mut rng = OsRng;
268        let poseidon_config = poseidon_canonical_config::<Fr>();
269        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
270        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
271            poseidon_config.clone(),
272            F_circuit,
273            0,
274        );
275
276        let proof = RandomizedIVCProof::new(&nova, &mut rng).unwrap();
277        let verify = RandomizedIVCProof::verify::<
278            Pedersen<Projective, true>,
279            GVar2,
280            Pedersen<Projective2, true>,
281        >(
282            &nova.r1cs,
283            &nova.cf_r1cs,
284            nova.pp_hash,
285            &nova.poseidon_config,
286            nova.i,
287            nova.z_0,
288            nova.z_i,
289            &proof,
290        );
291        assert!(verify.is_ok());
292    }
293
294    #[test]
295    fn test_zk_nova_verification_fails_with_wrong_running_instance() {
296        let mut rng = OsRng;
297        let poseidon_config = poseidon_canonical_config::<Fr>();
298        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
299        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
300            poseidon_config.clone(),
301            F_circuit,
302            3,
303        );
304        let (_, sampled_committed_instance) = nova
305            .r1cs
306            .sample_witness_instance::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
307            .unwrap();
308
309        // proof verification fails with incorrect running instance
310        let mut nova_with_incorrect_running_instance = nova.clone();
311        nova_with_incorrect_running_instance.U_i = sampled_committed_instance;
312        let incorrect_proof =
313            RandomizedIVCProof::new(&nova_with_incorrect_running_instance, &mut rng).unwrap();
314        let verify = RandomizedIVCProof::verify::<
315            Pedersen<Projective, true>,
316            GVar2,
317            Pedersen<Projective2, true>,
318        >(
319            &nova_with_incorrect_running_instance.r1cs,
320            &nova_with_incorrect_running_instance.cf_r1cs,
321            nova_with_incorrect_running_instance.pp_hash,
322            &nova_with_incorrect_running_instance.poseidon_config,
323            nova_with_incorrect_running_instance.i,
324            nova_with_incorrect_running_instance.z_0,
325            nova_with_incorrect_running_instance.z_i,
326            &incorrect_proof,
327        );
328        assert!(verify.is_err());
329    }
330
331    #[test]
332    fn test_zk_nova_verification_fails_with_wrong_running_witness() {
333        let mut rng = OsRng;
334        let poseidon_config = poseidon_canonical_config::<Fr>();
335        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
336        let (_, nova) = test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
337            poseidon_config.clone(),
338            F_circuit,
339            3,
340        );
341        let (sampled_committed_witness, _) = nova
342            .r1cs
343            .sample_witness_instance::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
344            .unwrap();
345
346        // proof generation fails with incorrect running witness
347        let mut nova_with_incorrect_running_witness = nova.clone();
348        nova_with_incorrect_running_witness.W_i = sampled_committed_witness;
349        let incorrect_proof =
350            RandomizedIVCProof::new(&nova_with_incorrect_running_witness, &mut rng).unwrap();
351        let verify = RandomizedIVCProof::verify::<
352            Pedersen<Projective, true>,
353            GVar2,
354            Pedersen<Projective2, true>,
355        >(
356            &nova_with_incorrect_running_witness.r1cs,
357            &nova_with_incorrect_running_witness.cf_r1cs,
358            nova_with_incorrect_running_witness.pp_hash,
359            &nova_with_incorrect_running_witness.poseidon_config,
360            nova_with_incorrect_running_witness.i,
361            nova_with_incorrect_running_witness.z_0,
362            nova_with_incorrect_running_witness.z_i,
363            &incorrect_proof,
364        );
365        assert!(verify.is_err());
366    }
367}