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 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
79 C2: CurveGroup,
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::{PreprocessorParam, ProverParams as NovaProverParams};
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 = <N as FoldingScheme<
492 Projective,
493 Projective2,
494 CubicFCircuit<Fr>,
495 >>::vp_deserialize_with_mode(
496 &mut nova_vp_serialized.as_slice(),
497 ark_serialize::Compress::Yes,
498 ark_serialize::Validate::Yes,
499 (), // fcircuit_params
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}