1use crate::{
4 constants::NUM_HASH_BITS,
5 digest::{DigestComputer, SimpleDigestible},
6 errors::NovaError,
7 frontend::{
8 r1cs::{NovaShape, NovaWitness},
9 shape_cs::ShapeCS,
10 solver::SatisfyingAssignment,
11 ConstraintSystem, SynthesisError,
12 },
13 gadgets::utils::{base_as_scalar, scalar_as_base},
14 r1cs::{
15 CommitmentKeyHint, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance,
16 RelaxedR1CSWitness,
17 },
18 traits::{
19 circuit::{StepCircuit, TrivialCircuit},
20 commitment::CommitmentEngineTrait,
21 snark::RelaxedR1CSSNARKTrait,
22 AbsorbInROTrait, Engine, ROConstants, ROConstantsCircuit, ROTrait,
23 },
24 CommitmentKey, DerandKey,
25};
26use core::marker::PhantomData;
27use ff::Field;
28use once_cell::sync::OnceCell;
29use rand_core::OsRng;
30use serde::{Deserialize, Serialize};
31
32mod circuit;
33pub mod nifs;
34
35use circuit::{NovaAugmentedCircuit, NovaAugmentedCircuitInputs};
36use nifs::{NIFSRelaxed, NIFS};
37
38#[derive(Serialize, Deserialize)]
40#[serde(bound = "")]
41pub struct PublicParams<E1, E2, C>
42where
43 E1: Engine<Base = <E2 as Engine>::Scalar>,
44 E2: Engine<Base = <E1 as Engine>::Scalar>,
45 C: StepCircuit<E1::Scalar>,
46{
47 F_arity: usize,
48
49 ro_consts_primary: ROConstants<E1>,
50 ro_consts_circuit_primary: ROConstantsCircuit<E2>,
51
52 ro_consts_secondary: ROConstants<E2>,
53 ro_consts_circuit_secondary: ROConstantsCircuit<E1>,
54
55 ck_primary: CommitmentKey<E1>,
56 r1cs_shape_primary: R1CSShape<E1>,
57
58 ck_secondary: CommitmentKey<E2>,
59 r1cs_shape_secondary: R1CSShape<E2>,
60
61 #[serde(skip, default = "OnceCell::new")]
62 digest: OnceCell<E1::Scalar>,
63 _p: PhantomData<C>,
64}
65
66impl<E1, E2, C> SimpleDigestible for PublicParams<E1, E2, C>
67where
68 E1: Engine<Base = <E2 as Engine>::Scalar>,
69 E2: Engine<Base = <E1 as Engine>::Scalar>,
70 C: StepCircuit<E1::Scalar>,
71{
72}
73
74impl<E1, E2, C> PublicParams<E1, E2, C>
75where
76 E1: Engine<Base = <E2 as Engine>::Scalar>,
77 E2: Engine<Base = <E1 as Engine>::Scalar>,
78 C: StepCircuit<E1::Scalar>,
79{
80 pub fn setup(
126 c: &C,
127 ck_hint1: &CommitmentKeyHint<E1>,
128 ck_hint2: &CommitmentKeyHint<E2>,
129 ) -> Result<Self, NovaError> {
130 let ro_consts_primary: ROConstants<E1> = ROConstants::<E1>::default();
131 let ro_consts_secondary: ROConstants<E2> = ROConstants::<E2>::default();
132
133 let F_arity = c.arity();
134
135 let ro_consts_circuit_primary: ROConstantsCircuit<E2> = ROConstantsCircuit::<E2>::default();
137 let ro_consts_circuit_secondary: ROConstantsCircuit<E1> = ROConstantsCircuit::<E1>::default();
138
139 let circuit_primary: NovaAugmentedCircuit<'_, E2, C> =
141 NovaAugmentedCircuit::new(true, None, c, ro_consts_circuit_primary.clone());
142 let mut cs: ShapeCS<E1> = ShapeCS::new();
143 let _ = circuit_primary.synthesize(&mut cs)?;
144 let r1cs_shape_primary = cs.r1cs_shape()?;
145 let ck_primary = R1CSShape::commitment_key(&[&r1cs_shape_primary], &[ck_hint1])?;
146
147 let tc = TrivialCircuit::<E2::Scalar>::default();
149 let circuit_secondary: NovaAugmentedCircuit<'_, E1, _> =
150 NovaAugmentedCircuit::new(false, None, &tc, ro_consts_circuit_secondary.clone());
151 let mut cs: ShapeCS<E2> = ShapeCS::new();
152 let _ = circuit_secondary.synthesize(&mut cs)?;
153 let r1cs_shape_secondary = cs.r1cs_shape()?;
154 let ck_secondary = R1CSShape::commitment_key(&[&r1cs_shape_secondary], &[ck_hint2])?;
155
156 if r1cs_shape_primary.num_io != 2 || r1cs_shape_secondary.num_io != 2 {
159 return Err(NovaError::InvalidStepCircuitIO);
160 }
161
162 let pp = PublicParams {
163 F_arity,
164
165 ro_consts_primary,
166 ro_consts_circuit_primary,
167
168 ro_consts_secondary,
169 ro_consts_circuit_secondary,
170
171 ck_primary,
172 r1cs_shape_primary,
173
174 ck_secondary,
175 r1cs_shape_secondary,
176
177 digest: OnceCell::new(),
178 _p: Default::default(),
179 };
180
181 let _ = pp.digest();
183
184 Ok(pp)
185 }
186
187 #[cfg(feature = "io")]
222 pub fn setup_with_ptau_dir(
223 c: &C,
224 ck_hint1: &CommitmentKeyHint<E1>,
225 ck_hint2: &CommitmentKeyHint<E2>,
226 ptau_dir: &std::path::Path,
227 ) -> Result<Self, NovaError>
228 where
229 E1::GE: crate::provider::traits::PairingGroup,
230 {
231 let ro_consts_primary: ROConstants<E1> = ROConstants::<E1>::default();
232 let ro_consts_secondary: ROConstants<E2> = ROConstants::<E2>::default();
233
234 let F_arity = c.arity();
235
236 let ro_consts_circuit_primary: ROConstantsCircuit<E2> = ROConstantsCircuit::<E2>::default();
237 let ro_consts_circuit_secondary: ROConstantsCircuit<E1> = ROConstantsCircuit::<E1>::default();
238
239 let circuit_primary: NovaAugmentedCircuit<'_, E2, C> =
241 NovaAugmentedCircuit::new(true, None, c, ro_consts_circuit_primary.clone());
242 let mut cs: ShapeCS<E1> = ShapeCS::new();
243 let _ = circuit_primary.synthesize(&mut cs)?;
244 let r1cs_shape_primary = cs.r1cs_shape()?;
245
246 let ck_primary =
248 R1CSShape::commitment_key_from_ptau_dir(&[&r1cs_shape_primary], &[ck_hint1], ptau_dir)?;
249
250 let tc = TrivialCircuit::<E2::Scalar>::default();
252 let circuit_secondary: NovaAugmentedCircuit<'_, E1, _> =
253 NovaAugmentedCircuit::new(false, None, &tc, ro_consts_circuit_secondary.clone());
254 let mut cs: ShapeCS<E2> = ShapeCS::new();
255 let _ = circuit_secondary.synthesize(&mut cs)?;
256 let r1cs_shape_secondary = cs.r1cs_shape()?;
257
258 let ck_secondary = R1CSShape::commitment_key(&[&r1cs_shape_secondary], &[ck_hint2])?;
260
261 if r1cs_shape_primary.num_io != 2 || r1cs_shape_secondary.num_io != 2 {
264 return Err(NovaError::InvalidStepCircuitIO);
265 }
266
267 let pp = PublicParams {
268 F_arity,
269
270 ro_consts_primary,
271 ro_consts_circuit_primary,
272
273 ro_consts_secondary,
274 ro_consts_circuit_secondary,
275
276 ck_primary,
277 r1cs_shape_primary,
278
279 ck_secondary,
280 r1cs_shape_secondary,
281
282 digest: OnceCell::new(),
283 _p: Default::default(),
284 };
285
286 let _ = pp.digest();
288
289 Ok(pp)
290 }
291
292 pub fn digest(&self) -> E1::Scalar {
294 self
295 .digest
296 .get_or_try_init(|| DigestComputer::new(self).digest())
297 .cloned()
298 .expect("Failure in retrieving digest")
299 }
300
301 pub const fn num_constraints(&self) -> (usize, usize) {
303 (
304 self.r1cs_shape_primary.num_cons,
305 self.r1cs_shape_secondary.num_cons,
306 )
307 }
308
309 pub const fn num_variables(&self) -> (usize, usize) {
311 (
312 self.r1cs_shape_primary.num_vars,
313 self.r1cs_shape_secondary.num_vars,
314 )
315 }
316}
317
318#[derive(Clone, Debug, Serialize, Deserialize)]
320#[serde(bound = "")]
321pub struct RecursiveSNARK<E1, E2, C>
322where
323 E1: Engine<Base = <E2 as Engine>::Scalar>,
324 E2: Engine<Base = <E1 as Engine>::Scalar>,
325 C: StepCircuit<E1::Scalar>,
326{
327 z0: Vec<E1::Scalar>,
328
329 r_W_primary: RelaxedR1CSWitness<E1>,
330 r_U_primary: RelaxedR1CSInstance<E1>,
331 ri_primary: E1::Scalar,
332
333 r_W_secondary: RelaxedR1CSWitness<E2>,
334 r_U_secondary: RelaxedR1CSInstance<E2>,
335 ri_secondary: E2::Scalar,
336
337 l_w_secondary: R1CSWitness<E2>,
338 l_u_secondary: R1CSInstance<E2>,
339
340 i: usize,
341
342 zi: Vec<E1::Scalar>,
343
344 _p: PhantomData<C>,
345}
346
347impl<E1, E2, C> RecursiveSNARK<E1, E2, C>
348where
349 E1: Engine<Base = <E2 as Engine>::Scalar>,
350 E2: Engine<Base = <E1 as Engine>::Scalar>,
351 C: StepCircuit<E1::Scalar>,
352{
353 pub fn new(pp: &PublicParams<E1, E2, C>, c: &C, z0: &[E1::Scalar]) -> Result<Self, NovaError> {
355 if z0.len() != pp.F_arity {
356 return Err(NovaError::InvalidInitialInputLength);
357 }
358
359 let ri_primary = E1::Scalar::random(&mut OsRng);
360 let ri_secondary = E2::Scalar::random(&mut OsRng);
361
362 let mut cs_primary = SatisfyingAssignment::<E1>::new();
364 let inputs_primary: NovaAugmentedCircuitInputs<E2> = NovaAugmentedCircuitInputs::new(
365 scalar_as_base::<E1>(pp.digest()),
366 E1::Scalar::ZERO,
367 z0.to_vec(),
368 None,
369 None,
370 None,
371 ri_primary, None,
373 None,
374 );
375
376 let circuit_primary: NovaAugmentedCircuit<'_, E2, C> = NovaAugmentedCircuit::new(
377 true,
378 Some(inputs_primary),
379 c,
380 pp.ro_consts_circuit_primary.clone(),
381 );
382 let zi_primary = circuit_primary.synthesize(&mut cs_primary)?;
383 let (u_primary, w_primary) =
384 cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?;
385
386 let mut cs_secondary = SatisfyingAssignment::<E2>::new();
388 let inputs_secondary: NovaAugmentedCircuitInputs<E1> = NovaAugmentedCircuitInputs::new(
389 pp.digest(),
390 E2::Scalar::ZERO,
391 vec![E2::Scalar::ZERO],
392 None,
393 None,
394 None,
395 ri_secondary, Some(u_primary.clone()),
397 None,
398 );
399 let tc = TrivialCircuit::<E2::Scalar>::default();
400 let circuit_secondary: NovaAugmentedCircuit<'_, E1, _> = NovaAugmentedCircuit::new(
401 false,
402 Some(inputs_secondary),
403 &tc,
404 pp.ro_consts_circuit_secondary.clone(),
405 );
406 let _ = circuit_secondary.synthesize(&mut cs_secondary)?;
407 let (u_secondary, w_secondary) =
408 cs_secondary.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)?;
409
410 let l_w_primary = w_primary;
412 let l_u_primary = u_primary;
413 let r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary);
414 let r_U_primary =
415 RelaxedR1CSInstance::from_r1cs_instance(&pp.ck_primary, &pp.r1cs_shape_primary, &l_u_primary);
416
417 let l_w_secondary = w_secondary;
419 let l_u_secondary = u_secondary;
420 let r_W_secondary = RelaxedR1CSWitness::<E2>::default(&pp.r1cs_shape_secondary);
421 let r_U_secondary =
422 RelaxedR1CSInstance::<E2>::default(&pp.ck_secondary, &pp.r1cs_shape_secondary);
423
424 if zi_primary.len() != pp.F_arity {
425 return Err(NovaError::InvalidStepOutputLength);
426 }
427
428 let zi_primary = zi_primary
429 .iter()
430 .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing))
431 .collect::<Result<Vec<<E1 as Engine>::Scalar>, _>>()?;
432
433 Ok(Self {
434 z0: z0.to_vec(),
435
436 r_W_primary,
437 r_U_primary,
438 ri_primary,
439
440 r_W_secondary,
441 r_U_secondary,
442 ri_secondary,
443
444 l_w_secondary,
445 l_u_secondary,
446
447 i: 0,
448
449 zi: zi_primary,
450
451 _p: Default::default(),
452 })
453 }
454
455 pub fn prove_step(&mut self, pp: &PublicParams<E1, E2, C>, c: &C) -> Result<(), NovaError> {
457 if self.i == 0 {
459 self.i = 1;
460 return Ok(());
461 }
462
463 let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove(
465 &pp.ck_secondary,
466 &pp.ro_consts_secondary,
467 &scalar_as_base::<E1>(pp.digest()),
468 &pp.r1cs_shape_secondary,
469 &self.r_U_secondary,
470 &self.r_W_secondary,
471 &self.l_u_secondary,
472 &self.l_w_secondary,
473 )?;
474
475 let r_next_primary = E1::Scalar::random(&mut OsRng);
476
477 let mut cs_primary = SatisfyingAssignment::<E1>::new();
478 let inputs_primary: NovaAugmentedCircuitInputs<E2> = NovaAugmentedCircuitInputs::new(
479 scalar_as_base::<E1>(pp.digest()),
480 E1::Scalar::from(self.i as u64),
481 self.z0.to_vec(),
482 Some(self.zi.clone()),
483 Some(self.r_U_secondary.clone()),
484 Some(self.ri_primary),
485 r_next_primary,
486 Some(self.l_u_secondary.clone()),
487 Some(nifs_secondary.comm_T),
488 );
489
490 let circuit_primary: NovaAugmentedCircuit<'_, E2, C> = NovaAugmentedCircuit::new(
491 true,
492 Some(inputs_primary),
493 c,
494 pp.ro_consts_circuit_primary.clone(),
495 );
496 let zi_primary = circuit_primary.synthesize(&mut cs_primary)?;
497
498 let (l_u_primary, l_w_primary) =
499 cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?;
500
501 let (nifs_primary, (r_U_primary, r_W_primary)) = NIFS::prove(
503 &pp.ck_primary,
504 &pp.ro_consts_primary,
505 &pp.digest(),
506 &pp.r1cs_shape_primary,
507 &self.r_U_primary,
508 &self.r_W_primary,
509 &l_u_primary,
510 &l_w_primary,
511 )?;
512
513 let r_next_secondary = E2::Scalar::random(&mut OsRng);
514
515 let mut cs_secondary = SatisfyingAssignment::<E2>::new();
516 let inputs_secondary: NovaAugmentedCircuitInputs<E1> = NovaAugmentedCircuitInputs::new(
517 pp.digest(),
518 E2::Scalar::from(self.i as u64),
519 vec![E2::Scalar::ZERO],
520 Some(vec![E2::Scalar::ZERO]),
521 Some(self.r_U_primary.clone()),
522 Some(self.ri_secondary),
523 r_next_secondary,
524 Some(l_u_primary),
525 Some(nifs_primary.comm_T),
526 );
527
528 let tc = TrivialCircuit::<E2::Scalar>::default();
529 let circuit_secondary: NovaAugmentedCircuit<'_, E1, _> = NovaAugmentedCircuit::new(
530 false,
531 Some(inputs_secondary),
532 &tc,
533 pp.ro_consts_circuit_secondary.clone(),
534 );
535 let _ = circuit_secondary.synthesize(&mut cs_secondary)?;
536
537 let (l_u_secondary, l_w_secondary) = cs_secondary
538 .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)
539 .map_err(|_e| NovaError::UnSat {
540 reason: "Unable to generate a satisfying witness on the secondary curve".to_string(),
541 })?;
542
543 self.zi = zi_primary
545 .iter()
546 .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing))
547 .collect::<Result<Vec<<E1 as Engine>::Scalar>, _>>()?;
548
549 self.l_u_secondary = l_u_secondary;
550 self.l_w_secondary = l_w_secondary;
551
552 self.r_U_primary = r_U_primary;
553 self.r_W_primary = r_W_primary;
554
555 self.i += 1;
556
557 self.r_U_secondary = r_U_secondary;
558 self.r_W_secondary = r_W_secondary;
559
560 self.ri_primary = r_next_primary;
561 self.ri_secondary = r_next_secondary;
562
563 Ok(())
564 }
565
566 pub fn verify(
568 &self,
569 pp: &PublicParams<E1, E2, C>,
570 num_steps: usize,
571 z0: &[E1::Scalar],
572 ) -> Result<Vec<E1::Scalar>, NovaError> {
573 let is_num_steps_zero = num_steps == 0;
575
576 let is_num_steps_not_match = self.i != num_steps;
578
579 let is_inputs_not_match = self.z0 != z0;
581
582 let is_instance_has_two_outputs = self.l_u_secondary.X.len() != 2
584 || self.r_U_primary.X.len() != 2
585 || self.r_U_secondary.X.len() != 2;
586
587 if is_num_steps_zero
588 || is_num_steps_not_match
589 || is_inputs_not_match
590 || is_instance_has_two_outputs
591 {
592 return Err(NovaError::ProofVerifyError {
593 reason: "Invalid number of steps or inputs".to_string(),
594 });
595 }
596
597 let (hash_primary, hash_secondary) = {
599 let mut hasher = <E2 as Engine>::RO::new(pp.ro_consts_secondary.clone());
600 hasher.absorb(pp.digest());
601 hasher.absorb(E1::Scalar::from(num_steps as u64));
602 for e in z0 {
603 hasher.absorb(*e);
604 }
605 for e in &self.zi {
606 hasher.absorb(*e);
607 }
608 self.r_U_secondary.absorb_in_ro(&mut hasher);
609 hasher.absorb(self.ri_primary);
610
611 let mut hasher2 = <E1 as Engine>::RO::new(pp.ro_consts_primary.clone());
612 hasher2.absorb(scalar_as_base::<E1>(pp.digest()));
613 hasher2.absorb(E2::Scalar::from(num_steps as u64));
614 hasher2.absorb(E2::Scalar::ZERO);
615 hasher2.absorb(E2::Scalar::ZERO);
616 self.r_U_primary.absorb_in_ro(&mut hasher2);
617 hasher2.absorb(self.ri_secondary);
618
619 (
620 hasher.squeeze(NUM_HASH_BITS, false),
621 hasher2.squeeze(NUM_HASH_BITS, false),
622 )
623 };
624
625 if hash_primary != scalar_as_base::<E2>(self.l_u_secondary.X[0])
626 || hash_secondary != self.l_u_secondary.X[1]
627 {
628 return Err(NovaError::ProofVerifyError {
629 reason: "Invalid output hash in R1CS instances".to_string(),
630 });
631 }
632
633 let (res_r_primary, (res_r_secondary, res_l_secondary)) = rayon::join(
635 || {
636 pp.r1cs_shape_primary
637 .is_sat_relaxed(&pp.ck_primary, &self.r_U_primary, &self.r_W_primary)
638 },
639 || {
640 rayon::join(
641 || {
642 pp.r1cs_shape_secondary.is_sat_relaxed(
643 &pp.ck_secondary,
644 &self.r_U_secondary,
645 &self.r_W_secondary,
646 )
647 },
648 || {
649 pp.r1cs_shape_secondary.is_sat(
650 &pp.ck_secondary,
651 &self.l_u_secondary,
652 &self.l_w_secondary,
653 )
654 },
655 )
656 },
657 );
658
659 res_r_primary?;
661 res_r_secondary?;
662 res_l_secondary?;
663
664 Ok(self.zi.clone())
665 }
666
667 pub fn outputs(&self) -> &[E1::Scalar] {
669 &self.zi
670 }
671
672 pub fn num_steps(&self) -> usize {
674 self.i
675 }
676}
677
678#[derive(Clone, Debug, Serialize, Deserialize)]
680#[serde(bound = "")]
681pub struct ProverKey<E1, E2, C, S1, S2>
682where
683 E1: Engine<Base = <E2 as Engine>::Scalar>,
684 E2: Engine<Base = <E1 as Engine>::Scalar>,
685 C: StepCircuit<E1::Scalar>,
686 S1: RelaxedR1CSSNARKTrait<E1>,
687 S2: RelaxedR1CSSNARKTrait<E2>,
688{
689 pk_primary: S1::ProverKey,
690 pk_secondary: S2::ProverKey,
691 _p: PhantomData<C>,
692}
693
694#[derive(Clone, Serialize, Deserialize)]
696#[serde(bound = "")]
697pub struct VerifierKey<E1, E2, C, S1, S2>
698where
699 E1: Engine<Base = <E2 as Engine>::Scalar>,
700 E2: Engine<Base = <E1 as Engine>::Scalar>,
701 C: StepCircuit<E1::Scalar>,
702 S1: RelaxedR1CSSNARKTrait<E1>,
703 S2: RelaxedR1CSSNARKTrait<E2>,
704{
705 F_arity: usize,
706 ro_consts_primary: ROConstants<E1>,
707 ro_consts_secondary: ROConstants<E2>,
708 pp_digest: E1::Scalar,
709 vk_primary: S1::VerifierKey,
710 vk_secondary: S2::VerifierKey,
711 dk_primary: DerandKey<E1>,
712 dk_secondary: DerandKey<E2>,
713 _p: PhantomData<C>,
714}
715
716#[derive(Clone, Serialize, Deserialize)]
718#[serde(bound = "")]
719pub struct CompressedSNARK<E1, E2, C, S1, S2>
720where
721 E1: Engine<Base = <E2 as Engine>::Scalar>,
722 E2: Engine<Base = <E1 as Engine>::Scalar>,
723 C: StepCircuit<E1::Scalar>,
724 S1: RelaxedR1CSSNARKTrait<E1>,
725 S2: RelaxedR1CSSNARKTrait<E2>,
726{
727 r_U_secondary: RelaxedR1CSInstance<E2>,
728 ri_secondary: E2::Scalar,
729 l_u_secondary: R1CSInstance<E2>,
730 nifs_Uf_secondary: NIFS<E2>,
731
732 l_ur_secondary: RelaxedR1CSInstance<E2>,
733 nifs_Un_secondary: NIFSRelaxed<E2>,
734
735 r_U_primary: RelaxedR1CSInstance<E1>,
736 ri_primary: E1::Scalar,
737 l_ur_primary: RelaxedR1CSInstance<E1>,
738 nifs_Un_primary: NIFSRelaxed<E1>,
739
740 wit_blind_r_Wn_primary: E1::Scalar,
741 err_blind_r_Wn_primary: E1::Scalar,
742 wit_blind_r_Wn_secondary: E2::Scalar,
743 err_blind_r_Wn_secondary: E2::Scalar,
744
745 snark_primary: S1,
746 snark_secondary: S2,
747
748 zn: Vec<E1::Scalar>,
749
750 _p: PhantomData<C>,
751}
752
753impl<E1, E2, C, S1, S2> CompressedSNARK<E1, E2, C, S1, S2>
754where
755 E1: Engine<Base = <E2 as Engine>::Scalar>,
756 E2: Engine<Base = <E1 as Engine>::Scalar>,
757 C: StepCircuit<E1::Scalar>,
758 S1: RelaxedR1CSSNARKTrait<E1>,
759 S2: RelaxedR1CSSNARKTrait<E2>,
760{
761 pub fn setup(
763 pp: &PublicParams<E1, E2, C>,
764 ) -> Result<(ProverKey<E1, E2, C, S1, S2>, VerifierKey<E1, E2, C, S1, S2>), NovaError> {
765 let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary)?;
766 let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary)?;
767
768 let pk = ProverKey {
769 pk_primary,
770 pk_secondary,
771 _p: Default::default(),
772 };
773
774 let vk = VerifierKey {
775 F_arity: pp.F_arity,
776 ro_consts_primary: pp.ro_consts_primary.clone(),
777 ro_consts_secondary: pp.ro_consts_secondary.clone(),
778 pp_digest: pp.digest(),
779 vk_primary,
780 vk_secondary,
781 dk_primary: E1::CE::derand_key(&pp.ck_primary),
782 dk_secondary: E2::CE::derand_key(&pp.ck_secondary),
783 _p: Default::default(),
784 };
785
786 Ok((pk, vk))
787 }
788
789 pub fn prove(
791 pp: &PublicParams<E1, E2, C>,
792 pk: &ProverKey<E1, E2, C, S1, S2>,
793 recursive_snark: &RecursiveSNARK<E1, E2, C>,
794 ) -> Result<Self, NovaError> {
795 let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = NIFS::prove(
799 &pp.ck_secondary,
800 &pp.ro_consts_secondary,
801 &scalar_as_base::<E1>(pp.digest()),
802 &pp.r1cs_shape_secondary,
803 &recursive_snark.r_U_secondary,
804 &recursive_snark.r_W_secondary,
805 &recursive_snark.l_u_secondary,
806 &recursive_snark.l_w_secondary,
807 )?;
808
809 let (l_ur_secondary, l_wr_secondary) = pp
811 .r1cs_shape_secondary
812 .sample_random_instance_witness(&pp.ck_secondary)?;
813
814 let (nifs_Un_secondary, (r_Un_secondary, r_Wn_secondary)) = NIFSRelaxed::prove(
815 &pp.ck_secondary,
816 &pp.ro_consts_secondary,
817 &scalar_as_base::<E1>(pp.digest()),
818 &pp.r1cs_shape_secondary,
819 &r_Uf_secondary,
820 &r_Wf_secondary,
821 &l_ur_secondary,
822 &l_wr_secondary,
823 )?;
824
825 let (l_ur_primary, l_wr_primary) = pp
827 .r1cs_shape_primary
828 .sample_random_instance_witness(&pp.ck_primary)?;
829
830 let (nifs_Un_primary, (r_Un_primary, r_Wn_primary)) = NIFSRelaxed::prove(
831 &pp.ck_primary,
832 &pp.ro_consts_primary,
833 &pp.digest(),
834 &pp.r1cs_shape_primary,
835 &recursive_snark.r_U_primary,
836 &recursive_snark.r_W_primary,
837 &l_ur_primary,
838 &l_wr_primary,
839 )?;
840
841 let (derandom_r_Wn_primary, wit_blind_r_Wn_primary, err_blind_r_Wn_primary) =
843 r_Wn_primary.derandomize();
844 let derandom_r_Un_primary = r_Un_primary.derandomize(
845 &E1::CE::derand_key(&pp.ck_primary),
846 &wit_blind_r_Wn_primary,
847 &err_blind_r_Wn_primary,
848 );
849
850 let (derandom_r_Wn_secondary, wit_blind_r_Wn_secondary, err_blind_r_Wn_secondary) =
851 r_Wn_secondary.derandomize();
852 let derandom_r_Un_secondary = r_Un_secondary.derandomize(
853 &E2::CE::derand_key(&pp.ck_secondary),
854 &wit_blind_r_Wn_secondary,
855 &err_blind_r_Wn_secondary,
856 );
857
858 let (snark_primary, snark_secondary) = rayon::join(
860 || {
861 S1::prove(
862 &pp.ck_primary,
863 &pk.pk_primary,
864 &pp.r1cs_shape_primary,
865 &derandom_r_Un_primary,
866 &derandom_r_Wn_primary,
867 )
868 },
869 || {
870 S2::prove(
871 &pp.ck_secondary,
872 &pk.pk_secondary,
873 &pp.r1cs_shape_secondary,
874 &derandom_r_Un_secondary,
875 &derandom_r_Wn_secondary,
876 )
877 },
878 );
879
880 Ok(Self {
881 r_U_secondary: recursive_snark.r_U_secondary.clone(),
882 ri_secondary: recursive_snark.ri_secondary,
883 l_u_secondary: recursive_snark.l_u_secondary.clone(),
884 nifs_Uf_secondary: nifs_Uf_secondary.clone(),
885
886 l_ur_secondary: l_ur_secondary.clone(),
887 nifs_Un_secondary: nifs_Un_secondary.clone(),
888
889 r_U_primary: recursive_snark.r_U_primary.clone(),
890 ri_primary: recursive_snark.ri_primary,
891 l_ur_primary: l_ur_primary.clone(),
892 nifs_Un_primary: nifs_Un_primary.clone(),
893
894 wit_blind_r_Wn_primary,
895 err_blind_r_Wn_primary,
896 wit_blind_r_Wn_secondary,
897 err_blind_r_Wn_secondary,
898
899 snark_primary: snark_primary?,
900 snark_secondary: snark_secondary?,
901
902 zn: recursive_snark.zi.clone(),
903
904 _p: Default::default(),
905 })
906 }
907
908 pub fn verify(
910 &self,
911 vk: &VerifierKey<E1, E2, C, S1, S2>,
912 num_steps: usize,
913 z0: &[E1::Scalar],
914 ) -> Result<Vec<E1::Scalar>, NovaError> {
915 if num_steps == 0 {
917 return Err(NovaError::ProofVerifyError {
918 reason: "Number of steps cannot be zero".to_string(),
919 });
920 }
921
922 if self.l_u_secondary.X.len() != 2
924 || self.r_U_primary.X.len() != 2
925 || self.r_U_secondary.X.len() != 2
926 || self.l_ur_primary.X.len() != 2
927 || self.l_ur_secondary.X.len() != 2
928 {
929 return Err(NovaError::ProofVerifyError {
930 reason: "Invalid number of outputs in R1CS instances".to_string(),
931 });
932 }
933
934 let (hash_primary, hash_secondary) = {
936 let mut hasher = <E2 as Engine>::RO::new(vk.ro_consts_secondary.clone());
937 hasher.absorb(vk.pp_digest);
938 hasher.absorb(E1::Scalar::from(num_steps as u64));
939 for e in z0 {
940 hasher.absorb(*e);
941 }
942 for e in &self.zn {
943 hasher.absorb(*e);
944 }
945 self.r_U_secondary.absorb_in_ro(&mut hasher);
946 hasher.absorb(self.ri_primary);
947
948 let mut hasher2 = <E1 as Engine>::RO::new(vk.ro_consts_primary.clone());
949 hasher2.absorb(scalar_as_base::<E1>(vk.pp_digest));
950 hasher2.absorb(E2::Scalar::from(num_steps as u64));
951 hasher2.absorb(E2::Scalar::ZERO);
952 hasher2.absorb(E2::Scalar::ZERO);
953 self.r_U_primary.absorb_in_ro(&mut hasher2);
954 hasher2.absorb(self.ri_secondary);
955
956 (
957 hasher.squeeze(NUM_HASH_BITS, false),
958 hasher2.squeeze(NUM_HASH_BITS, false),
959 )
960 };
961
962 if hash_primary != base_as_scalar::<E1>(self.l_u_secondary.X[0])
963 || hash_secondary != self.l_u_secondary.X[1]
964 {
965 return Err(NovaError::ProofVerifyError {
966 reason: "Invalid output hash in R1CS instances".to_string(),
967 });
968 }
969
970 let r_Uf_secondary = self.nifs_Uf_secondary.verify(
972 &vk.ro_consts_secondary,
973 &scalar_as_base::<E1>(vk.pp_digest),
974 &self.r_U_secondary,
975 &self.l_u_secondary,
976 )?;
977
978 let r_Un_secondary = self.nifs_Un_secondary.verify(
980 &vk.ro_consts_secondary,
981 &scalar_as_base::<E1>(vk.pp_digest),
982 &r_Uf_secondary,
983 &self.l_ur_secondary,
984 )?;
985
986 let r_Un_primary = self.nifs_Un_primary.verify(
988 &vk.ro_consts_primary,
989 &vk.pp_digest,
990 &self.r_U_primary,
991 &self.l_ur_primary,
992 )?;
993
994 let derandom_r_Un_primary = r_Un_primary.derandomize(
996 &vk.dk_primary,
997 &self.wit_blind_r_Wn_primary,
998 &self.err_blind_r_Wn_primary,
999 );
1000 let derandom_r_Un_secondary = r_Un_secondary.derandomize(
1001 &vk.dk_secondary,
1002 &self.wit_blind_r_Wn_secondary,
1003 &self.err_blind_r_Wn_secondary,
1004 );
1005
1006 let (res_primary, res_secondary) = rayon::join(
1009 || {
1010 self
1011 .snark_primary
1012 .verify(&vk.vk_primary, &derandom_r_Un_primary)
1013 },
1014 || {
1015 self
1016 .snark_secondary
1017 .verify(&vk.vk_secondary, &derandom_r_Un_secondary)
1018 },
1019 );
1020
1021 res_primary?;
1022 res_secondary?;
1023
1024 Ok(self.zn.clone())
1025 }
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030 use super::*;
1031 use crate::{
1032 frontend::{num::AllocatedNum, ConstraintSystem, SynthesisError},
1033 provider::{
1034 pedersen::CommitmentKeyExtTrait, traits::DlogGroup, Bn256EngineIPA, Bn256EngineKZG,
1035 GrumpkinEngine, PallasEngine, Secp256k1Engine, Secq256k1Engine, VestaEngine,
1036 },
1037 traits::{circuit::TrivialCircuit, evaluation::EvaluationEngineTrait, snark::default_ck_hint},
1038 };
1039 use core::{fmt::Write, marker::PhantomData};
1040 use expect_test::{expect, Expect};
1041 use ff::PrimeField;
1042
1043 type EE<E> = crate::provider::ipa_pc::EvaluationEngine<E>;
1044 type EEPrime<E> = crate::provider::hyperkzg::EvaluationEngine<E>;
1045 type S<E, EE> = crate::spartan::snark::RelaxedR1CSSNARK<E, EE>;
1046 type SPrime<E, EE> = crate::spartan::ppsnark::RelaxedR1CSSNARK<E, EE>;
1047
1048 #[derive(Clone, Debug, Default)]
1049 struct CubicCircuit<F: PrimeField> {
1050 _p: PhantomData<F>,
1051 }
1052
1053 impl<F: PrimeField> StepCircuit<F> for CubicCircuit<F> {
1054 fn arity(&self) -> usize {
1055 1
1056 }
1057
1058 fn synthesize<CS: ConstraintSystem<F>>(
1059 &self,
1060 cs: &mut CS,
1061 z: &[AllocatedNum<F>],
1062 ) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
1063 let x = &z[0];
1065 let x_sq = x.square(cs.namespace(|| "x_sq"))?;
1066 let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), x)?;
1067 let y = AllocatedNum::alloc(cs.namespace(|| "y"), || {
1068 Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64))
1069 })?;
1070
1071 cs.enforce(
1072 || "y = x^3 + x + 5",
1073 |lc| {
1074 lc + x_cu.get_variable()
1075 + x.get_variable()
1076 + CS::one()
1077 + CS::one()
1078 + CS::one()
1079 + CS::one()
1080 + CS::one()
1081 },
1082 |lc| lc + CS::one(),
1083 |lc| lc + y.get_variable(),
1084 );
1085
1086 Ok(vec![y])
1087 }
1088 }
1089
1090 impl<F: PrimeField> CubicCircuit<F> {
1091 fn output(&self, z: &[F]) -> Vec<F> {
1092 vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)]
1093 }
1094 }
1095
1096 fn test_pp_digest_with<E1, E2, C>(circuit: &C, expected: &Expect)
1097 where
1098 E1: Engine<Base = <E2 as Engine>::Scalar>,
1099 E2: Engine<Base = <E1 as Engine>::Scalar>,
1100 E1::GE: DlogGroup,
1101 E2::GE: DlogGroup,
1102 C: StepCircuit<E1::Scalar>,
1103 <E1::CE as CommitmentEngineTrait<E1>>::CommitmentKey: CommitmentKeyExtTrait<E1>,
1105 <E2::CE as CommitmentEngineTrait<E2>>::CommitmentKey: CommitmentKeyExtTrait<E2>,
1106 {
1107 let ck_hint1 = &*SPrime::<E1, EE<E1>>::ck_floor();
1109 let ck_hint2 = &*SPrime::<E2, EE<E2>>::ck_floor();
1110 let pp = PublicParams::<E1, E2, C>::setup(circuit, ck_hint1, ck_hint2).unwrap();
1111
1112 let digest_str = pp
1113 .digest()
1114 .to_repr()
1115 .as_ref()
1116 .iter()
1117 .fold(String::new(), |mut output, b| {
1118 let _ = write!(output, "{b:02x}");
1119 output
1120 });
1121 expected.assert_eq(&digest_str);
1122 }
1123
1124 #[test]
1125 fn test_pp_digest() {
1126 test_pp_digest_with::<PallasEngine, VestaEngine, _>(
1127 &TrivialCircuit::<_>::default(),
1128 &expect!["9e459d7e9450cce023a7adad2e4552170170b031e75b65333713ea161a294503"],
1129 );
1130
1131 test_pp_digest_with::<Bn256EngineIPA, GrumpkinEngine, _>(
1132 &TrivialCircuit::<_>::default(),
1133 &expect!["aa8f0e6d8be0c4028ddf7a7a733cf8d1ceeff408d6ec9a8aefa815b2668b2402"],
1134 );
1135
1136 test_pp_digest_with::<Secp256k1Engine, Secq256k1Engine, _>(
1137 &TrivialCircuit::<_>::default(),
1138 &expect!["719f988d15984fe6e838dd6267455265e1081beeb3b7313594a26d702afe5702"],
1139 );
1140 }
1141
1142 fn test_ivc_trivial_with<E1, E2>()
1143 where
1144 E1: Engine<Base = <E2 as Engine>::Scalar>,
1145 E2: Engine<Base = <E1 as Engine>::Scalar>,
1146 {
1147 let test_circuit = TrivialCircuit::<<E1 as Engine>::Scalar>::default();
1148
1149 let pp = PublicParams::<E1, E2, TrivialCircuit<<E1 as Engine>::Scalar>>::setup(
1151 &test_circuit,
1152 &*default_ck_hint(),
1153 &*default_ck_hint(),
1154 )
1155 .unwrap();
1156
1157 let num_steps = 1;
1158
1159 let mut recursive_snark =
1161 RecursiveSNARK::new(&pp, &test_circuit, &[<E1 as Engine>::Scalar::ZERO]).unwrap();
1162
1163 let res = recursive_snark.prove_step(&pp, &test_circuit);
1164
1165 assert!(res.is_ok());
1166
1167 let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1169 assert!(res.is_ok());
1170 }
1171
1172 #[test]
1173 fn test_ivc_trivial() {
1174 test_ivc_trivial_with::<PallasEngine, VestaEngine>();
1175 test_ivc_trivial_with::<Bn256EngineKZG, GrumpkinEngine>();
1176 test_ivc_trivial_with::<Secp256k1Engine, Secq256k1Engine>();
1177 }
1178
1179 fn test_ivc_nontrivial_with<E1, E2>()
1180 where
1181 E1: Engine<Base = <E2 as Engine>::Scalar>,
1182 E2: Engine<Base = <E1 as Engine>::Scalar>,
1183 {
1184 let circuit = CubicCircuit::default();
1185
1186 let pp = PublicParams::<E1, E2, CubicCircuit<E1::Scalar>>::setup(
1188 &circuit,
1189 &*default_ck_hint(),
1190 &*default_ck_hint(),
1191 )
1192 .unwrap();
1193
1194 let num_steps = 3;
1195
1196 let mut recursive_snark = RecursiveSNARK::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::new(
1198 &pp,
1199 &circuit,
1200 &[<E1 as Engine>::Scalar::ZERO],
1201 )
1202 .unwrap();
1203
1204 for i in 0..num_steps {
1205 let res = recursive_snark.prove_step(&pp, &circuit);
1206 assert!(res.is_ok());
1207
1208 let res = recursive_snark.verify(&pp, i + 1, &[<E1 as Engine>::Scalar::ZERO]);
1210 assert!(res.is_ok());
1211 }
1212
1213 let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1215 assert!(res.is_ok());
1216
1217 let zn = res.unwrap();
1218
1219 let mut zn_direct = vec![<E1 as Engine>::Scalar::ZERO];
1221 for _i in 0..num_steps {
1222 zn_direct = circuit.clone().output(&zn_direct);
1223 }
1224 assert_eq!(zn, zn_direct);
1225 assert_eq!(zn, vec![E1::Scalar::from(2460515u64)]);
1226 }
1227
1228 #[test]
1229 fn test_ivc_nontrivial() {
1230 test_ivc_nontrivial_with::<PallasEngine, VestaEngine>();
1231 test_ivc_nontrivial_with::<Bn256EngineKZG, GrumpkinEngine>();
1232 test_ivc_nontrivial_with::<Secp256k1Engine, Secq256k1Engine>();
1233 }
1234
1235 fn test_ivc_nontrivial_with_compression_with<E1, E2, EE1, EE2>()
1236 where
1237 E1: Engine<Base = <E2 as Engine>::Scalar>,
1238 E2: Engine<Base = <E1 as Engine>::Scalar>,
1239 EE1: EvaluationEngineTrait<E1>,
1240 EE2: EvaluationEngineTrait<E2>,
1241 {
1242 let circuit = CubicCircuit::default();
1243
1244 let pp = PublicParams::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::setup(
1246 &circuit,
1247 &*default_ck_hint(),
1248 &*default_ck_hint(),
1249 )
1250 .unwrap();
1251
1252 let num_steps = 3;
1253
1254 let mut recursive_snark = RecursiveSNARK::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::new(
1256 &pp,
1257 &circuit,
1258 &[<E1 as Engine>::Scalar::ZERO],
1259 )
1260 .unwrap();
1261
1262 for _i in 0..num_steps {
1263 let res = recursive_snark.prove_step(&pp, &circuit);
1264 assert!(res.is_ok());
1265 }
1266
1267 let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1269 assert!(res.is_ok());
1270
1271 let zn = res.unwrap();
1272
1273 let mut zn_direct = vec![<E1 as Engine>::Scalar::ZERO];
1275 for _i in 0..num_steps {
1276 zn_direct = circuit.clone().output(&zn_direct);
1277 }
1278 assert_eq!(zn, zn_direct);
1279 assert_eq!(zn, vec![<E1 as Engine>::Scalar::from(2460515u64)]);
1280
1281 let (pk, vk) = CompressedSNARK::<_, _, _, S<E1, EE1>, S<E2, EE2>>::setup(&pp).unwrap();
1283
1284 let res = CompressedSNARK::<_, _, _, S<E1, EE1>, S<E2, EE2>>::prove(&pp, &pk, &recursive_snark);
1286 assert!(res.is_ok());
1287 let compressed_snark = res.unwrap();
1288
1289 let res = compressed_snark.verify(&vk, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1291 assert!(res.is_ok());
1292 }
1293
1294 #[test]
1295 fn test_ivc_nontrivial_with_compression() {
1296 test_ivc_nontrivial_with_compression_with::<PallasEngine, VestaEngine, EE<_>, EE<_>>();
1297 test_ivc_nontrivial_with_compression_with::<Bn256EngineKZG, GrumpkinEngine, EEPrime<_>, EE<_>>(
1298 );
1299 test_ivc_nontrivial_with_compression_with::<Secp256k1Engine, Secq256k1Engine, EE<_>, EE<_>>();
1300
1301 test_ivc_nontrivial_with_spark_compression_with::<
1302 Bn256EngineKZG,
1303 GrumpkinEngine,
1304 crate::provider::hyperkzg::EvaluationEngine<_>,
1305 EE<_>,
1306 >();
1307 }
1308
1309 fn test_ivc_nontrivial_with_spark_compression_with<E1, E2, EE1, EE2>()
1310 where
1311 E1: Engine<Base = <E2 as Engine>::Scalar>,
1312 E2: Engine<Base = <E1 as Engine>::Scalar>,
1313 EE1: EvaluationEngineTrait<E1>,
1314 EE2: EvaluationEngineTrait<E2>,
1315 {
1316 let circuit = CubicCircuit::default();
1317
1318 let pp = PublicParams::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::setup(
1320 &circuit,
1321 &*SPrime::<E1, EE1>::ck_floor(),
1322 &*SPrime::<E2, EE2>::ck_floor(),
1323 )
1324 .unwrap();
1325
1326 let num_steps = 3;
1327
1328 let mut recursive_snark = RecursiveSNARK::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::new(
1330 &pp,
1331 &circuit,
1332 &[<E1 as Engine>::Scalar::ZERO],
1333 )
1334 .unwrap();
1335
1336 for _i in 0..num_steps {
1337 let res = recursive_snark.prove_step(&pp, &circuit);
1338 assert!(res.is_ok());
1339 }
1340
1341 let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1343 assert!(res.is_ok());
1344
1345 let zn = res.unwrap();
1346
1347 let mut zn_direct = vec![<E1 as Engine>::Scalar::ZERO];
1349 for _i in 0..num_steps {
1350 zn_direct = CubicCircuit::default().output(&zn_direct);
1351 }
1352 assert_eq!(zn, zn_direct);
1353 assert_eq!(zn, vec![<E1 as Engine>::Scalar::from(2460515u64)]);
1354
1355 let (pk, vk) =
1358 CompressedSNARK::<_, _, _, SPrime<E1, EE1>, SPrime<E2, EE2>>::setup(&pp).unwrap();
1359
1360 let res = CompressedSNARK::<_, _, _, SPrime<E1, EE1>, SPrime<E2, EE2>>::prove(
1362 &pp,
1363 &pk,
1364 &recursive_snark,
1365 );
1366 assert!(res.is_ok());
1367 let compressed_snark = res.unwrap();
1368
1369 let res = compressed_snark.verify(&vk, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1371 assert!(res.is_ok());
1372 }
1373
1374 #[test]
1375 fn test_ivc_nontrivial_with_spark_compression() {
1376 test_ivc_nontrivial_with_spark_compression_with::<PallasEngine, VestaEngine, EE<_>, EE<_>>();
1377 test_ivc_nontrivial_with_spark_compression_with::<
1378 Bn256EngineKZG,
1379 GrumpkinEngine,
1380 EEPrime<_>,
1381 EE<_>,
1382 >();
1383 test_ivc_nontrivial_with_spark_compression_with::<Secp256k1Engine, Secq256k1Engine, EE<_>, EE<_>>(
1384 );
1385 }
1386
1387 fn test_ivc_nondet_with_compression_with<E1, E2, EE1, EE2>()
1388 where
1389 E1: Engine<Base = <E2 as Engine>::Scalar>,
1390 E2: Engine<Base = <E1 as Engine>::Scalar>,
1391 EE1: EvaluationEngineTrait<E1>,
1392 EE2: EvaluationEngineTrait<E2>,
1393 {
1394 #[derive(Clone, Debug)]
1396 struct FifthRootCheckingCircuit<F: PrimeField> {
1397 y: F,
1398 }
1399
1400 impl<F: PrimeField> FifthRootCheckingCircuit<F> {
1401 fn new(num_steps: usize) -> (Vec<F>, Vec<Self>) {
1402 let mut powers = Vec::new();
1403 let rng = &mut rand::rngs::OsRng;
1404 let mut seed = F::random(rng);
1405 for _i in 0..num_steps + 1 {
1406 seed *= seed.clone().square().square();
1407
1408 powers.push(Self { y: seed });
1409 }
1410
1411 let roots = powers.into_iter().rev().collect::<Vec<Self>>();
1413 (vec![roots[0].y], roots[1..].to_vec())
1414 }
1415 }
1416
1417 impl<F> StepCircuit<F> for FifthRootCheckingCircuit<F>
1418 where
1419 F: PrimeField,
1420 {
1421 fn arity(&self) -> usize {
1422 1
1423 }
1424
1425 fn synthesize<CS: ConstraintSystem<F>>(
1426 &self,
1427 cs: &mut CS,
1428 z: &[AllocatedNum<F>],
1429 ) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
1430 let x = &z[0];
1431
1432 let y = AllocatedNum::alloc_infallible(cs.namespace(|| "y"), || self.y);
1434
1435 let y_sq = y.square(cs.namespace(|| "y_sq"))?;
1437 let y_quad = y_sq.square(cs.namespace(|| "y_quad"))?;
1438 let y_pow_5 = y_quad.mul(cs.namespace(|| "y_fifth"), &y)?;
1439
1440 cs.enforce(
1441 || "y^5 = x",
1442 |lc| lc + y_pow_5.get_variable(),
1443 |lc| lc + CS::one(),
1444 |lc| lc + x.get_variable(),
1445 );
1446
1447 Ok(vec![y])
1448 }
1449 }
1450
1451 let circuit = FifthRootCheckingCircuit {
1452 y: <E1 as Engine>::Scalar::ZERO,
1453 };
1454
1455 let pp = PublicParams::<E1, E2, FifthRootCheckingCircuit<<E1 as Engine>::Scalar>>::setup(
1457 &circuit,
1458 &*default_ck_hint(),
1459 &*default_ck_hint(),
1460 )
1461 .unwrap();
1462
1463 let num_steps = 3;
1464
1465 let (z0, roots) = FifthRootCheckingCircuit::new(num_steps);
1467
1468 let mut recursive_snark: RecursiveSNARK<
1470 E1,
1471 E2,
1472 FifthRootCheckingCircuit<<E1 as Engine>::Scalar>,
1473 > = RecursiveSNARK::<E1, E2, FifthRootCheckingCircuit<<E1 as Engine>::Scalar>>::new(
1474 &pp, &roots[0], &z0,
1475 )
1476 .unwrap();
1477
1478 for circuit in roots.iter().take(num_steps) {
1479 let res = recursive_snark.prove_step(&pp, circuit);
1480 assert!(res.is_ok());
1481 }
1482
1483 let res = recursive_snark.verify(&pp, num_steps, &z0);
1485 assert!(res.is_ok());
1486
1487 let (pk, vk) = CompressedSNARK::<_, _, _, S<E1, EE1>, S<E2, EE2>>::setup(&pp).unwrap();
1489
1490 let res = CompressedSNARK::<_, _, _, S<E1, EE1>, S<E2, EE2>>::prove(&pp, &pk, &recursive_snark);
1492 assert!(res.is_ok());
1493 let compressed_snark = res.unwrap();
1494
1495 let res = compressed_snark.verify(&vk, num_steps, &z0);
1497 assert!(res.is_ok());
1498 }
1499
1500 #[test]
1501 fn test_ivc_nondet_with_compression() {
1502 test_ivc_nondet_with_compression_with::<PallasEngine, VestaEngine, EE<_>, EE<_>>();
1503 test_ivc_nondet_with_compression_with::<Bn256EngineKZG, GrumpkinEngine, EEPrime<_>, EE<_>>();
1504 test_ivc_nondet_with_compression_with::<Secp256k1Engine, Secq256k1Engine, EE<_>, EE<_>>();
1505 }
1506
1507 fn test_ivc_base_with<E1, E2>()
1508 where
1509 E1: Engine<Base = <E2 as Engine>::Scalar>,
1510 E2: Engine<Base = <E1 as Engine>::Scalar>,
1511 {
1512 let test_circuit1 = CubicCircuit::<<E1 as Engine>::Scalar>::default();
1513
1514 let pp = PublicParams::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::setup(
1516 &test_circuit1,
1517 &*default_ck_hint(),
1518 &*default_ck_hint(),
1519 )
1520 .unwrap();
1521
1522 let num_steps = 1;
1523
1524 let mut recursive_snark = RecursiveSNARK::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::new(
1526 &pp,
1527 &test_circuit1,
1528 &[<E1 as Engine>::Scalar::ZERO],
1529 )
1530 .unwrap();
1531
1532 let res = recursive_snark.prove_step(&pp, &test_circuit1);
1534
1535 assert!(res.is_ok());
1536
1537 let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1539 assert!(res.is_ok());
1540
1541 let zn = res.unwrap();
1542
1543 assert_eq!(zn, vec![<E1 as Engine>::Scalar::from(5u64)]);
1544 }
1545
1546 #[test]
1547 fn test_ivc_base() {
1548 test_ivc_base_with::<PallasEngine, VestaEngine>();
1549 test_ivc_base_with::<Bn256EngineKZG, GrumpkinEngine>();
1550 test_ivc_base_with::<Secp256k1Engine, Secq256k1Engine>();
1551 }
1552
1553 fn test_setup_with<E1, E2>()
1554 where
1555 E1: Engine<Base = <E2 as Engine>::Scalar>,
1556 E2: Engine<Base = <E1 as Engine>::Scalar>,
1557 {
1558 #[derive(Clone, Debug, Default)]
1559 struct CircuitWithInputize<F: PrimeField> {
1560 _p: PhantomData<F>,
1561 }
1562
1563 impl<F: PrimeField> StepCircuit<F> for CircuitWithInputize<F> {
1564 fn arity(&self) -> usize {
1565 1
1566 }
1567
1568 fn synthesize<CS: ConstraintSystem<F>>(
1569 &self,
1570 cs: &mut CS,
1571 z: &[AllocatedNum<F>],
1572 ) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
1573 let x = &z[0];
1574 let y = x.square(cs.namespace(|| "x_sq"))?;
1575 y.inputize(cs.namespace(|| "y"))?; Ok(vec![y])
1577 }
1578 }
1579
1580 let circuit = CircuitWithInputize::<<E1 as Engine>::Scalar>::default();
1582 let pp = PublicParams::<E1, E2, CircuitWithInputize<E1::Scalar>>::setup(
1583 &circuit,
1584 &*default_ck_hint(),
1585 &*default_ck_hint(),
1586 );
1587 assert!(pp.is_err());
1588 assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO));
1589
1590 let circuit = CircuitWithInputize::<E1::Scalar>::default();
1591 let pp = PublicParams::<E1, E2, CircuitWithInputize<E1::Scalar>>::setup(
1592 &circuit,
1593 &*default_ck_hint(),
1594 &*default_ck_hint(),
1595 );
1596 assert!(pp.is_err());
1597 assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO));
1598 }
1599
1600 #[test]
1601 fn test_setup() {
1602 test_setup_with::<Bn256EngineKZG, GrumpkinEngine>();
1603 }
1604
1605 #[test]
1617 #[ignore]
1618 #[cfg(feature = "io")]
1619 fn test_ivc_with_ppot_files() {
1620 use std::path::Path;
1621
1622 type E1 = Bn256EngineKZG;
1623 type E2 = GrumpkinEngine;
1624
1625 let circuit_primary = CubicCircuit::<<E1 as Engine>::Scalar>::default();
1626
1627 let ptau_dir = Path::new("/tmp/pruned_ptau");
1629
1630 if !ptau_dir.exists() {
1631 eprintln!(
1632 "PPOT directory not found at {:?}. \n\
1633 To run this test:\n\
1634 1. Create the directory: mkdir -p /tmp/pruned_ptau\n\
1635 2. Download or generate pruned PPOT files:\n\
1636 cargo run --example ppot_prune --features io -- --power 20 --output /tmp/pruned_ptau\n\
1637 3. Run the test: cargo test --release --features io test_ivc_with_ppot_files -- --ignored",
1638 ptau_dir
1639 );
1640 return;
1641 }
1642
1643 let pp = PublicParams::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::setup_with_ptau_dir(
1645 &circuit_primary,
1646 &*default_ck_hint(),
1647 &*default_ck_hint(),
1648 ptau_dir,
1649 )
1650 .expect("Failed to setup with PPOT files");
1651
1652 let _ = pp.digest();
1654
1655 let z0_primary = vec![<E1 as Engine>::Scalar::from(3u64)];
1657 let mut recursive_snark = RecursiveSNARK::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::new(
1658 &pp,
1659 &circuit_primary,
1660 &z0_primary,
1661 )
1662 .expect("Failed to create recursive SNARK");
1663
1664 let num_steps = 3;
1666 for _ in 0..num_steps {
1667 recursive_snark
1668 .prove_step(&pp, &circuit_primary)
1669 .expect("Failed to prove step");
1670 }
1671
1672 let z0_primary = vec![<E1 as Engine>::Scalar::from(3u64)];
1674 recursive_snark
1675 .verify(&pp, num_steps, &z0_primary)
1676 .expect("Failed to verify recursive SNARK");
1677
1678 println!(
1679 "Successfully proved and verified {} steps using PPOT files from {:?}",
1680 num_steps, ptau_dir
1681 );
1682 }
1683}