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.
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;
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::utils::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) =
465 D::preprocess(&mut rng, nova_params.clone(), nova.clone()).unwrap();
466
467 // serialize the Nova params. These params are the trusted setup of the commitment schemes used
468 // (ie. KZG & Pedersen in this case)
469 let mut nova_pp_serialized = vec![];
470 nova_params
471 .0
472 .serialize_compressed(&mut nova_pp_serialized)
473 .unwrap();
474 let mut nova_vp_serialized = vec![];
475 nova_params
476 .1
477 .serialize_compressed(&mut nova_vp_serialized)
478 .unwrap();
479 // deserialize the Nova params. This would be done by the client reading from a file
480 let nova_pp_deserialized = NovaProverParams::<
481 Projective,
482 Projective2,
483 KZG<'static, Bn254>,
484 Pedersen<Projective2>,
485 >::deserialize_compressed(
486 &mut nova_pp_serialized.as_slice()
487 )
488 .unwrap();
489 let nova_vp_deserialized = NovaVerifierParams::<
490 Projective,
491 Projective2,
492 KZG<'static, Bn254>,
493 Pedersen<Projective2>,
494 >::deserialize_compressed(
495 &mut nova_vp_serialized.as_slice()
496 )
497 .unwrap();
498
499 // initialize nova again, but from the deserialized parameters
500 let nova_params = (nova_pp_deserialized, nova_vp_deserialized);
501 let mut nova = N::init(&nova_params, F_circuit, z_0).unwrap();
502
503 let start = Instant::now();
504 nova.prove_step(&mut rng, vec![], None).unwrap();
505 println!("prove_step, {:?}", start.elapsed());
506 nova.prove_step(&mut rng, vec![], None).unwrap(); // do a 2nd step
507
508 // decider proof generation
509 let start = Instant::now();
510 let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
511 println!("Decider prove, {:?}", start.elapsed());
512
513 // decider proof verification
514 let start = Instant::now();
515 let verified = D::verify(
516 decider_vp.clone(),
517 nova.i.clone(),
518 nova.z_0.clone(),
519 nova.z_i.clone(),
520 &nova.U_i,
521 &nova.u_i,
522 &proof,
523 )
524 .unwrap();
525 assert!(verified);
526 println!("Decider verify, {:?}", start.elapsed());
527
528 // The rest of this test will serialize the data and deserialize it back, and use it to
529 // verify the proof:
530
531 // serialize the verifier_params, proof and public inputs
532 let mut decider_vp_serialized = vec![];
533 decider_vp
534 .serialize_compressed(&mut decider_vp_serialized)
535 .unwrap();
536 let mut proof_serialized = vec![];
537 proof.serialize_compressed(&mut proof_serialized).unwrap();
538 // serialize the public inputs in a single packet
539 let mut public_inputs_serialized = vec![];
540 nova.i
541 .serialize_compressed(&mut public_inputs_serialized)
542 .unwrap();
543 nova.z_0
544 .serialize_compressed(&mut public_inputs_serialized)
545 .unwrap();
546 nova.z_i
547 .serialize_compressed(&mut public_inputs_serialized)
548 .unwrap();
549 nova.U_i
550 .serialize_compressed(&mut public_inputs_serialized)
551 .unwrap();
552 nova.u_i
553 .serialize_compressed(&mut public_inputs_serialized)
554 .unwrap();
555
556 // deserialize back the verifier_params, proof and public inputs
557 let decider_vp_deserialized =
558 VerifierParam::<
559 Projective,
560 <KZG<'static, Bn254> as CommitmentScheme<Projective>>::VerifierParams,
561 <Groth16<Bn254> as SNARK<Fr>>::VerifyingKey,
562 >::deserialize_compressed(&mut decider_vp_serialized.as_slice())
563 .unwrap();
564 let proof_deserialized =
565 Proof::<Projective, KZG<'static, Bn254>, Groth16<Bn254>>::deserialize_compressed(
566 &mut proof_serialized.as_slice(),
567 )
568 .unwrap();
569
570 // deserialize the public inputs from the single packet 'public_inputs_serialized'
571 let mut reader = public_inputs_serialized.as_slice();
572 let i_deserialized = Fr::deserialize_compressed(&mut reader).unwrap();
573 let z_0_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
574 let z_i_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
575 let U_i_deserialized =
576 CommittedInstance::<Projective>::deserialize_compressed(&mut reader).unwrap();
577 let u_i_deserialized =
578 CommittedInstance::<Projective>::deserialize_compressed(&mut reader).unwrap();
579
580 // decider proof verification using the deserialized data
581 let verified = D::verify(
582 decider_vp_deserialized,
583 i_deserialized,
584 z_0_deserialized,
585 z_i_deserialized,
586 &U_i_deserialized,
587 &u_i_deserialized,
588 &proof_deserialized,
589 )
590 .unwrap();
591 assert!(verified);
592 }
593}