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}