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