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