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