arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/nova/decider_eth.rs
folding-schemes/src/folding/nova/decider_eth.rsRaw
  1/// This file implements the Nova's onchain (Ethereum's EVM) decider. For non-ethereum use cases,
  2/// the Decider from decider.rs file will be more efficient.
  3/// More details can be found at the documentation page:
  4/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html
  5use ark_bn254::Bn254;
  6use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge};
  7use ark_ec::{AffineRepr, CurveGroup, Group};
  8use ark_ff::{BigInteger, PrimeField};
  9use ark_groth16::Groth16;
 10use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
 11use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
 12use ark_snark::SNARK;
 13use ark_std::rand::{CryptoRng, RngCore};
 14use ark_std::{One, Zero};
 15use core::marker::PhantomData;
 16
 17pub use super::decider_eth_circuit::DeciderEthCircuit;
 18use super::{
 19    nifs::{nova::NIFS, NIFSTrait},
 20    CommittedInstance, Nova,
 21};
 22use crate::commitment::{
 23    kzg::{Proof as KZGProof, KZG},
 24    pedersen::Params as PedersenParams,
 25    CommitmentScheme,
 26};
 27use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2};
 28use crate::folding::nova::circuits::ChallengeGadget;
 29use crate::frontend::FCircuit;
 30use crate::transcript::poseidon::poseidon_canonical_config;
 31use crate::Error;
 32use crate::{Decider as DeciderTrait, FoldingScheme};
 33
 34#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
 35pub struct Proof<C1, CS1, S>
 36where
 37    C1: CurveGroup,
 38    CS1: CommitmentScheme<C1, ProverChallenge = C1::ScalarField, Challenge = C1::ScalarField>,
 39    S: SNARK<C1::ScalarField>,
 40{
 41    snark_proof: S::Proof,
 42    kzg_proofs: [CS1::Proof; 2],
 43    // cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are
 44    // checked in-circuit
 45    cmT: C1,
 46    // the KZG challenges are provided by the prover, but in-circuit they are checked to match
 47    // the in-circuit computed computed ones.
 48    kzg_challenges: [C1::ScalarField; 2],
 49}
 50
 51#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
 52pub struct VerifierParam<C1, CS_VerifyingKey, S_VerifyingKey>
 53where
 54    C1: CurveGroup,
 55    CS_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
 56    S_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
 57{
 58    pub pp_hash: C1::ScalarField,
 59    pub snark_vp: S_VerifyingKey,
 60    pub cs_vp: CS_VerifyingKey,
 61}
 62
 63/// Onchain Decider, for ethereum use cases
 64#[derive(Clone, Debug)]
 65pub struct Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS> {
 66    _c1: PhantomData<C1>,
 67    _gc1: PhantomData<GC1>,
 68    _c2: PhantomData<C2>,
 69    _gc2: PhantomData<GC2>,
 70    _fc: PhantomData<FC>,
 71    _cs1: PhantomData<CS1>,
 72    _cs2: PhantomData<CS2>,
 73    _s: PhantomData<S>,
 74    _fs: PhantomData<FS>,
 75}
 76
 77impl<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS> DeciderTrait<C1, C2, FC, FS>
 78    for Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS>
 79where
 80    C1: CurveGroup,
 81    GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
 82    C2: CurveGroup,
 83    GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
 84    FC: FCircuit<C1::ScalarField>,
 85    // CS1 is a KZG commitment, where challenge is C1::Fr elem
 86    CS1: CommitmentScheme<
 87        C1,
 88        ProverChallenge = C1::ScalarField,
 89        Challenge = C1::ScalarField,
 90        Proof = KZGProof<C1>,
 91    >,
 92    // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
 93    CS2: CommitmentScheme<C2, ProverParams = PedersenParams<C2>>,
 94    S: SNARK<C1::ScalarField>,
 95    FS: FoldingScheme<C1, C2, FC>,
 96    <C1 as CurveGroup>::BaseField: PrimeField,
 97    <C2 as CurveGroup>::BaseField: PrimeField,
 98    <C1 as Group>::ScalarField: Absorb,
 99    <C2 as Group>::ScalarField: Absorb,
100    C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
101    for<'b> &'b GC1: GroupOpsBounds<'b, C1, GC1>,
102    for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
103    // constrain FS into Nova, since this is a Decider specifically for Nova
104    Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false>: From<FS>,
105    crate::folding::nova::ProverParams<C1, C2, CS1, CS2, false>:
106        From<<FS as FoldingScheme<C1, C2, FC>>::ProverParam>,
107    crate::folding::nova::VerifierParams<C1, C2, CS1, CS2, false>:
108        From<<FS as FoldingScheme<C1, C2, FC>>::VerifierParam>,
109{
110    type PreprocessorParam = (FS::ProverParam, FS::VerifierParam);
111    type ProverParam = (S::ProvingKey, CS1::ProverParams);
112    type Proof = Proof<C1, CS1, S>;
113    type VerifierParam = VerifierParam<C1, CS1::VerifierParams, S::VerifyingKey>;
114    type PublicInput = Vec<C1::ScalarField>;
115    type CommittedInstance = CommittedInstance<C1>;
116
117    fn preprocess(
118        mut rng: impl RngCore + CryptoRng,
119        prep_param: Self::PreprocessorParam,
120        fs: FS,
121    ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
122        let circuit =
123            DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_nova::<FC>(fs.into()).unwrap();
124
125        // get the Groth16 specific setup for the circuit
126        let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng).unwrap();
127
128        // get the FoldingScheme prover & verifier params from Nova
129        #[allow(clippy::type_complexity)]
130        let nova_pp: <Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false> as FoldingScheme<
131            C1,
132            C2,
133            FC,
134        >>::ProverParam = prep_param.0.clone().into();
135        #[allow(clippy::type_complexity)]
136        let nova_vp: <Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false> as FoldingScheme<
137            C1,
138            C2,
139            FC,
140        >>::VerifierParam = prep_param.1.clone().into();
141        let pp_hash = nova_vp.pp_hash()?;
142
143        let pp = (g16_pk, nova_pp.cs_pp);
144        let vp = Self::VerifierParam {
145            pp_hash,
146            snark_vp: g16_vk,
147            cs_vp: nova_vp.cs_vp,
148        };
149        Ok((pp, vp))
150    }
151
152    fn prove(
153        mut rng: impl RngCore + CryptoRng,
154        pp: Self::ProverParam,
155        folding_scheme: FS,
156    ) -> Result<Self::Proof, Error> {
157        let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp;
158
159        let circuit = DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_nova::<FC>(
160            folding_scheme.into(),
161        )?;
162
163        let snark_proof = S::prove(&snark_pk, circuit.clone(), &mut rng)
164            .map_err(|e| Error::Other(e.to_string()))?;
165
166        let cmT = circuit.cmT.unwrap();
167        let W_i1 = circuit.W_i1.unwrap();
168
169        // get the challenges that have been already computed when preparing the circuit inputs in
170        // the above `from_nova` call
171        let challenge_W = circuit
172            .kzg_c_W
173            .ok_or(Error::MissingValue("kzg_c_W".to_string()))?;
174        let challenge_E = circuit
175            .kzg_c_E
176            .ok_or(Error::MissingValue("kzg_c_E".to_string()))?;
177
178        // generate KZG proofs
179        let U_cmW_proof = CS1::prove_with_challenge(
180            &cs_pk,
181            challenge_W,
182            &W_i1.W,
183            &C1::ScalarField::zero(),
184            None,
185        )?;
186        let U_cmE_proof = CS1::prove_with_challenge(
187            &cs_pk,
188            challenge_E,
189            &W_i1.E,
190            &C1::ScalarField::zero(),
191            None,
192        )?;
193
194        Ok(Self::Proof {
195            snark_proof,
196            kzg_proofs: [U_cmW_proof, U_cmE_proof],
197            cmT,
198            kzg_challenges: [challenge_W, challenge_E],
199        })
200    }
201
202    fn verify(
203        vp: Self::VerifierParam,
204        i: C1::ScalarField,
205        z_0: Vec<C1::ScalarField>,
206        z_i: Vec<C1::ScalarField>,
207        running_instance: &Self::CommittedInstance,
208        incoming_instance: &Self::CommittedInstance,
209        proof: &Self::Proof,
210    ) -> Result<bool, Error> {
211        if i <= C1::ScalarField::one() {
212            return Err(Error::NotEnoughSteps);
213        }
214
215        // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
216        let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
217        let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&poseidon_config);
218        let (U, r_bits) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>>::verify(
219            &mut transcript,
220            vp.pp_hash,
221            running_instance,
222            incoming_instance,
223            &proof.cmT,
224        )?;
225        let r = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
226            .ok_or(Error::OutOfBounds)?;
227
228        let (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?;
229        let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?;
230        let (cmT_x, cmT_y) = NonNativeAffineVar::inputize(proof.cmT)?;
231
232        let public_input: Vec<C1::ScalarField> = [
233            vec![vp.pp_hash, i],
234            z_0,
235            z_i,
236            vec![U.u],
237            U.x.clone(),
238            cmE_x,
239            cmE_y,
240            cmW_x,
241            cmW_y,
242            proof.kzg_challenges.to_vec(),
243            vec![
244                proof.kzg_proofs[0].eval, // eval_W
245                proof.kzg_proofs[1].eval, // eval_E
246            ],
247            cmT_x,
248            cmT_y,
249            vec![r],
250        ]
251        .concat();
252
253        let snark_v = S::verify(&vp.snark_vp, &public_input, &proof.snark_proof)
254            .map_err(|e| Error::Other(e.to_string()))?;
255        if !snark_v {
256            return Err(Error::SNARKVerificationFail);
257        }
258
259        // we're at the Ethereum EVM case, so the CS1 is KZG commitments
260        CS1::verify_with_challenge(
261            &vp.cs_vp,
262            proof.kzg_challenges[0],
263            &U.cmW,
264            &proof.kzg_proofs[0],
265        )?;
266        CS1::verify_with_challenge(
267            &vp.cs_vp,
268            proof.kzg_challenges[1],
269            &U.cmE,
270            &proof.kzg_proofs[1],
271        )?;
272
273        Ok(true)
274    }
275}
276
277/// Prepares solidity calldata for calling the NovaDecider contract
278#[allow(clippy::too_many_arguments)]
279pub fn prepare_calldata(
280    function_signature_check: [u8; 4],
281    pp_hash: ark_bn254::Fr,
282    i: ark_bn254::Fr,
283    z_0: Vec<ark_bn254::Fr>,
284    z_i: Vec<ark_bn254::Fr>,
285    running_instance: &CommittedInstance<ark_bn254::G1Projective>,
286    incoming_instance: &CommittedInstance<ark_bn254::G1Projective>,
287    proof: Proof<ark_bn254::G1Projective, KZG<'static, Bn254>, Groth16<Bn254>>,
288) -> Result<Vec<u8>, Error> {
289    // compute the challenge r
290    let poseidon_config = poseidon_canonical_config::<ark_bn254::Fr>();
291    let mut transcript = PoseidonSponge::<ark_bn254::Fr>::new(&poseidon_config);
292    let r_bits = ChallengeGadget::<
293        ark_bn254::G1Projective,
294        CommittedInstance<ark_bn254::G1Projective>,
295    >::get_challenge_native(
296        &mut transcript,
297        pp_hash,
298        running_instance,
299        incoming_instance,
300        Some(&proof.cmT),
301    );
302    let r =
303        ark_bn254::Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).ok_or(Error::OutOfBounds)?;
304
305    Ok(vec![
306        function_signature_check.to_vec(),
307        i.into_bigint().to_bytes_be(), // i
308        z_0.iter()
309            .flat_map(|v| v.into_bigint().to_bytes_be())
310            .collect::<Vec<u8>>(), // z_0
311        z_i.iter()
312            .flat_map(|v| v.into_bigint().to_bytes_be())
313            .collect::<Vec<u8>>(), // z_i
314        point_to_eth_format(running_instance.cmW.into_affine())?, // U_i_cmW
315        point_to_eth_format(running_instance.cmE.into_affine())?, // U_i_cmE
316        running_instance.u.into_bigint().to_bytes_be(), // U_i_u
317        incoming_instance.u.into_bigint().to_bytes_be(), // u_i_u
318        r.into_bigint().to_bytes_be(), // r
319        running_instance
320            .x
321            .iter()
322            .flat_map(|v| v.into_bigint().to_bytes_be())
323            .collect::<Vec<u8>>(), // U_i_x
324        point_to_eth_format(incoming_instance.cmW.into_affine())?, // u_i_cmW
325        incoming_instance
326            .x
327            .iter()
328            .flat_map(|v| v.into_bigint().to_bytes_be())
329            .collect::<Vec<u8>>(), // u_i_x
330        point_to_eth_format(proof.cmT.into_affine())?, // cmT
331        point_to_eth_format(proof.snark_proof.a)?, // pA
332        point2_to_eth_format(proof.snark_proof.b)?, // pB
333        point_to_eth_format(proof.snark_proof.c)?, // pC
334        proof.kzg_challenges[0].into_bigint().to_bytes_be(), // challenge_W
335        proof.kzg_challenges[1].into_bigint().to_bytes_be(), // challenge_E
336        proof.kzg_proofs[0].eval.into_bigint().to_bytes_be(), // eval W
337        proof.kzg_proofs[1].eval.into_bigint().to_bytes_be(), // eval E
338        point_to_eth_format(proof.kzg_proofs[0].proof.into_affine())?, // W kzg_proof
339        point_to_eth_format(proof.kzg_proofs[1].proof.into_affine())?, // E kzg_proof
340    ]
341    .concat())
342}
343
344fn point_to_eth_format<C: AffineRepr>(p: C) -> Result<Vec<u8>, Error>
345where
346    C::BaseField: PrimeField,
347{
348    // the encoding of the additive identity is [0, 0] on the EVM
349    let zero_point = (&C::BaseField::zero(), &C::BaseField::zero());
350    let (x, y) = p.xy().unwrap_or(zero_point);
351
352    Ok([x.into_bigint().to_bytes_be(), y.into_bigint().to_bytes_be()].concat())
353}
354fn point2_to_eth_format(p: ark_bn254::G2Affine) -> Result<Vec<u8>, Error> {
355    let zero_point = (&ark_bn254::Fq2::zero(), &ark_bn254::Fq2::zero());
356    let (x, y) = p.xy().unwrap_or(zero_point);
357
358    Ok([
359        x.c1.into_bigint().to_bytes_be(),
360        x.c0.into_bigint().to_bytes_be(),
361        y.c1.into_bigint().to_bytes_be(),
362        y.c0.into_bigint().to_bytes_be(),
363    ]
364    .concat())
365}
366
367#[cfg(test)]
368pub mod tests {
369    use ark_bn254::{constraints::GVar, Fr, G1Projective as Projective};
370    use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
371    use std::time::Instant;
372
373    use super::*;
374    use crate::commitment::pedersen::Pedersen;
375    use crate::folding::nova::{PreprocessorParam, ProverParams as NovaProverParams};
376    use crate::frontend::utils::CubicFCircuit;
377    use crate::transcript::poseidon::poseidon_canonical_config;
378
379    #[test]
380    fn test_decider() {
381        // use Nova as FoldingScheme
382        type N = Nova<
383            Projective,
384            GVar,
385            Projective2,
386            GVar2,
387            CubicFCircuit<Fr>,
388            KZG<'static, Bn254>,
389            Pedersen<Projective2>,
390            false,
391        >;
392        type D = Decider<
393            Projective,
394            GVar,
395            Projective2,
396            GVar2,
397            CubicFCircuit<Fr>,
398            KZG<'static, Bn254>,
399            Pedersen<Projective2>,
400            Groth16<Bn254>, // here we define the Snark to use in the decider
401            N,              // here we define the FoldingScheme to use
402        >;
403
404        let mut rng = rand::rngs::OsRng;
405        let poseidon_config = poseidon_canonical_config::<Fr>();
406
407        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
408        let z_0 = vec![Fr::from(3_u32)];
409
410        let preprocessor_param = PreprocessorParam::new(poseidon_config, F_circuit);
411        let nova_params = N::preprocess(&mut rng, &preprocessor_param).unwrap();
412
413        let start = Instant::now();
414        let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap();
415        println!("Nova initialized, {:?}", start.elapsed());
416
417        // prepare the Decider prover & verifier params
418        let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
419
420        let start = Instant::now();
421        nova.prove_step(&mut rng, vec![], None).unwrap();
422        println!("prove_step, {:?}", start.elapsed());
423        nova.prove_step(&mut rng, vec![], None).unwrap(); // do a 2nd step
424
425        // decider proof generation
426        let start = Instant::now();
427        let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
428        println!("Decider prove, {:?}", start.elapsed());
429
430        // decider proof verification
431        let start = Instant::now();
432        let verified = D::verify(
433            decider_vp.clone(),
434            nova.i,
435            nova.z_0.clone(),
436            nova.z_i.clone(),
437            &nova.U_i,
438            &nova.u_i,
439            &proof,
440        )
441        .unwrap();
442        assert!(verified);
443        println!("Decider verify, {:?}", start.elapsed());
444
445        // decider proof verification using the deserialized data
446        let verified = D::verify(
447            decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, &proof,
448        )
449        .unwrap();
450        assert!(verified);
451    }
452
453    // Test to check the serialization and deserialization of diverse Decider related parameters.
454    // This test is the same test as `test_decider` but it serializes values and then uses the
455    // deserialized values to continue the checks.
456    #[test]
457    fn test_decider_serialization() {
458        // use Nova as FoldingScheme
459        type N = Nova<
460            Projective,
461            GVar,
462            Projective2,
463            GVar2,
464            CubicFCircuit<Fr>,
465            KZG<'static, Bn254>,
466            Pedersen<Projective2>,
467            false,
468        >;
469        type D = Decider<
470            Projective,
471            GVar,
472            Projective2,
473            GVar2,
474            CubicFCircuit<Fr>,
475            KZG<'static, Bn254>,
476            Pedersen<Projective2>,
477            Groth16<Bn254>, // here we define the Snark to use in the decider
478            N,              // here we define the FoldingScheme to use
479        >;
480
481        let mut rng = rand::rngs::OsRng;
482        let poseidon_config = poseidon_canonical_config::<Fr>();
483
484        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
485        let z_0 = vec![Fr::from(3_u32)];
486
487        let preprocessor_param = PreprocessorParam::new(poseidon_config, F_circuit);
488        let nova_params = N::preprocess(&mut rng, &preprocessor_param).unwrap();
489
490        let start = Instant::now();
491        let nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap();
492        println!("Nova initialized, {:?}", start.elapsed());
493
494        // prepare the Decider prover & verifier params
495        let (decider_pp, decider_vp) =
496            D::preprocess(&mut rng, nova_params.clone(), nova.clone()).unwrap();
497
498        // serialize the Nova params. These params are the trusted setup of the commitment schemes used
499        // (ie. KZG & Pedersen in this case)
500        let mut nova_pp_serialized = vec![];
501        nova_params
502            .0
503            .serialize_compressed(&mut nova_pp_serialized)
504            .unwrap();
505        let mut nova_vp_serialized = vec![];
506        nova_params
507            .1
508            .serialize_compressed(&mut nova_vp_serialized)
509            .unwrap();
510        // deserialize the Nova params. This would be done by the client reading from a file
511        let nova_pp_deserialized = NovaProverParams::<
512            Projective,
513            Projective2,
514            KZG<'static, Bn254>,
515            Pedersen<Projective2>,
516        >::deserialize_compressed(
517            &mut nova_pp_serialized.as_slice()
518        )
519        .unwrap();
520        let nova_vp_deserialized = <N as FoldingScheme<
521            Projective,
522            Projective2,
523            CubicFCircuit<Fr>,
524        >>::vp_deserialize_with_mode(
525            &mut nova_vp_serialized.as_slice(),
526            ark_serialize::Compress::Yes,
527            ark_serialize::Validate::Yes,
528            (), // fcircuit_params
529        )
530        .unwrap();
531
532        // initialize nova again, but from the deserialized parameters
533        let nova_params = (nova_pp_deserialized, nova_vp_deserialized);
534        let mut nova = N::init(&nova_params, F_circuit, z_0).unwrap();
535
536        let start = Instant::now();
537        nova.prove_step(&mut rng, vec![], None).unwrap();
538        println!("prove_step, {:?}", start.elapsed());
539        nova.prove_step(&mut rng, vec![], None).unwrap(); // do a 2nd step
540
541        // decider proof generation
542        let start = Instant::now();
543        let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
544        println!("Decider prove, {:?}", start.elapsed());
545
546        // decider proof verification
547        let start = Instant::now();
548        let verified = D::verify(
549            decider_vp.clone(),
550            nova.i,
551            nova.z_0.clone(),
552            nova.z_i.clone(),
553            &nova.U_i,
554            &nova.u_i,
555            &proof,
556        )
557        .unwrap();
558        assert!(verified);
559        println!("Decider verify, {:?}", start.elapsed());
560
561        // The rest of this test will serialize the data and deserialize it back, and use it to
562        // verify the proof:
563
564        // serialize the verifier_params, proof and public inputs
565        let mut decider_vp_serialized = vec![];
566        decider_vp
567            .serialize_compressed(&mut decider_vp_serialized)
568            .unwrap();
569        let mut proof_serialized = vec![];
570        proof.serialize_compressed(&mut proof_serialized).unwrap();
571        // serialize the public inputs in a single packet
572        let mut public_inputs_serialized = vec![];
573        nova.i
574            .serialize_compressed(&mut public_inputs_serialized)
575            .unwrap();
576        nova.z_0
577            .serialize_compressed(&mut public_inputs_serialized)
578            .unwrap();
579        nova.z_i
580            .serialize_compressed(&mut public_inputs_serialized)
581            .unwrap();
582        nova.U_i
583            .serialize_compressed(&mut public_inputs_serialized)
584            .unwrap();
585        nova.u_i
586            .serialize_compressed(&mut public_inputs_serialized)
587            .unwrap();
588
589        // deserialize back the verifier_params, proof and public inputs
590        let decider_vp_deserialized =
591            VerifierParam::<
592                Projective,
593                <KZG<'static, Bn254> as CommitmentScheme<Projective>>::VerifierParams,
594                <Groth16<Bn254> as SNARK<Fr>>::VerifyingKey,
595            >::deserialize_compressed(&mut decider_vp_serialized.as_slice())
596            .unwrap();
597        let proof_deserialized =
598            Proof::<Projective, KZG<'static, Bn254>, Groth16<Bn254>>::deserialize_compressed(
599                &mut proof_serialized.as_slice(),
600            )
601            .unwrap();
602
603        // deserialize the public inputs from the single packet 'public_inputs_serialized'
604        let mut reader = public_inputs_serialized.as_slice();
605        let i_deserialized = Fr::deserialize_compressed(&mut reader).unwrap();
606        let z_0_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
607        let z_i_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
608        let U_i_deserialized =
609            CommittedInstance::<Projective>::deserialize_compressed(&mut reader).unwrap();
610        let u_i_deserialized =
611            CommittedInstance::<Projective>::deserialize_compressed(&mut reader).unwrap();
612
613        // decider proof verification using the deserialized data
614        let verified = D::verify(
615            decider_vp_deserialized,
616            i_deserialized,
617            z_0_deserialized,
618            z_i_deserialized,
619            &U_i_deserialized,
620            &u_i_deserialized,
621            &proof_deserialized,
622        )
623        .unwrap();
624        assert!(verified);
625    }
626}