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