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