You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

232 lines
7.2 KiB

  1. //! Demonstrates how to use Nova to produce a recursive proof of the correct execution of
  2. //! iterations of the MinRoot function, thereby realizing a Nova-based verifiable delay function (VDF).
  3. //! We currently execute a single iteration of the MinRoot function per step of Nova's recursion.
  4. type G1 = pasta_curves::pallas::Point;
  5. type G2 = pasta_curves::vesta::Point;
  6. type S1 = nova_snark::spartan_with_ipa_pc::RelaxedR1CSSNARK<G1>;
  7. type S2 = nova_snark::spartan_with_ipa_pc::RelaxedR1CSSNARK<G2>;
  8. use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
  9. use ff::PrimeField;
  10. use generic_array::typenum::U2;
  11. use neptune::{
  12. circuit::poseidon_hash,
  13. poseidon::{Poseidon, PoseidonConstants},
  14. Strength,
  15. };
  16. use nova_snark::{
  17. traits::{Group, StepCircuit},
  18. CompressedSNARK, PublicParams, RecursiveSNARK,
  19. };
  20. use num_bigint::BigUint;
  21. use std::marker::PhantomData;
  22. // A trivial test circuit that we will use on the secondary curve
  23. #[derive(Clone, Debug)]
  24. struct TrivialTestCircuit<F: PrimeField> {
  25. _p: PhantomData<F>,
  26. }
  27. impl<F> StepCircuit<F> for TrivialTestCircuit<F>
  28. where
  29. F: PrimeField,
  30. {
  31. fn synthesize<CS: ConstraintSystem<F>>(
  32. &self,
  33. _cs: &mut CS,
  34. z: AllocatedNum<F>,
  35. ) -> Result<AllocatedNum<F>, SynthesisError> {
  36. Ok(z)
  37. }
  38. fn compute(&self, z: &F) -> F {
  39. *z
  40. }
  41. }
  42. #[derive(Clone, Debug)]
  43. struct MinRootCircuit<F: PrimeField> {
  44. x_i: F,
  45. y_i: F,
  46. x_i_plus_1: F,
  47. y_i_plus_1: F,
  48. pc: PoseidonConstants<F, U2>,
  49. }
  50. impl<F: PrimeField> MinRootCircuit<F> {
  51. // produces a sample non-deterministic advice, executing one invocation of MinRoot per step
  52. fn new(num_steps: usize, x_0: &F, y_0: &F, pc: &PoseidonConstants<F, U2>) -> (F, Vec<Self>) {
  53. // although this code is written generically, it is tailored to Pallas' scalar field
  54. // (p - 3 / 5)
  55. let exp = BigUint::parse_bytes(
  56. b"23158417847463239084714197001737581570690445185553317903743794198714690358477",
  57. 10,
  58. )
  59. .unwrap();
  60. let mut res = Vec::new();
  61. let mut x_i = *x_0;
  62. let mut y_i = *y_0;
  63. for _i in 0..num_steps {
  64. let x_i_plus_1 = (x_i + y_i).pow_vartime(exp.to_u64_digits()); // computes the fifth root of x_i + y_i
  65. // sanity check
  66. let sq = x_i_plus_1 * x_i_plus_1;
  67. let quad = sq * sq;
  68. let fifth = quad * x_i_plus_1;
  69. debug_assert_eq!(fifth, x_i + y_i);
  70. let y_i_plus_1 = x_i;
  71. res.push(Self {
  72. x_i,
  73. y_i,
  74. x_i_plus_1,
  75. y_i_plus_1,
  76. pc: pc.clone(),
  77. });
  78. x_i = x_i_plus_1;
  79. y_i = y_i_plus_1;
  80. }
  81. let z0 = Poseidon::<F, U2>::new_with_preimage(&[*x_0, *y_0], pc).hash();
  82. (z0, res)
  83. }
  84. }
  85. impl<F> StepCircuit<F> for MinRootCircuit<F>
  86. where
  87. F: PrimeField,
  88. {
  89. fn synthesize<CS: ConstraintSystem<F>>(
  90. &self,
  91. cs: &mut CS,
  92. z: AllocatedNum<F>,
  93. ) -> Result<AllocatedNum<F>, SynthesisError> {
  94. // Allocate four variables for holding non-deterministic advice: x_i, y_i, x_i_plus_1, y_i_plus_1
  95. let x_i = AllocatedNum::alloc(cs.namespace(|| "x_i"), || Ok(self.x_i))?;
  96. let y_i = AllocatedNum::alloc(cs.namespace(|| "y_i"), || Ok(self.y_i))?;
  97. let x_i_plus_1 = AllocatedNum::alloc(cs.namespace(|| "x_i_plus_1"), || Ok(self.x_i_plus_1))?;
  98. // check that z = hash(x_i, y_i), where z is an output from the prior step
  99. let z_hash = poseidon_hash(
  100. cs.namespace(|| "input hash"),
  101. vec![x_i.clone(), y_i.clone()],
  102. &self.pc,
  103. )?;
  104. cs.enforce(
  105. || "z =? z_hash",
  106. |lc| lc + z_hash.get_variable(),
  107. |lc| lc + CS::one(),
  108. |lc| lc + z.get_variable(),
  109. );
  110. // check the following conditions hold:
  111. // (i) x_i_plus_1 = (x_i + y_i)^{1/5}, which can be more easily checked with x_i_plus_1^5 = x_i + y_i
  112. // (ii) y_i_plus_1 = x_i
  113. // (1) constraints for condition (i) are below
  114. // (2) constraints for condition (ii) is avoided because we just used x_i wherever y_i_plus_1 is used
  115. let x_i_plus_1_sq = x_i_plus_1.square(cs.namespace(|| "x_i_plus_1_sq"))?;
  116. let x_i_plus_1_quad = x_i_plus_1_sq.square(cs.namespace(|| "x_i_plus_1_quad"))?;
  117. let x_i_plus_1_pow_5 = x_i_plus_1_quad.mul(cs.namespace(|| "x_i_plus_1_pow_5"), &x_i_plus_1)?;
  118. cs.enforce(
  119. || "x_i_plus_1_pow_5 = x_i + y_i",
  120. |lc| lc + x_i_plus_1_pow_5.get_variable(),
  121. |lc| lc + CS::one(),
  122. |lc| lc + x_i.get_variable() + y_i.get_variable(),
  123. );
  124. // return hash(x_i_plus_1, y_i_plus_1) since Nova circuits expect a single output
  125. poseidon_hash(
  126. cs.namespace(|| "output hash"),
  127. vec![x_i_plus_1, x_i.clone()],
  128. &self.pc,
  129. )
  130. }
  131. fn compute(&self, z: &F) -> F {
  132. // sanity check
  133. let z_hash = Poseidon::<F, U2>::new_with_preimage(&[self.x_i, self.y_i], &self.pc).hash();
  134. debug_assert_eq!(z, &z_hash);
  135. // compute output hash using advice
  136. Poseidon::<F, U2>::new_with_preimage(&[self.x_i_plus_1, self.y_i_plus_1], &self.pc).hash()
  137. }
  138. }
  139. fn main() {
  140. let pc = PoseidonConstants::<<G1 as Group>::Scalar, U2>::new_with_strength(Strength::Standard);
  141. let circuit_primary = MinRootCircuit {
  142. x_i: <G1 as Group>::Scalar::zero(),
  143. y_i: <G1 as Group>::Scalar::zero(),
  144. x_i_plus_1: <G1 as Group>::Scalar::zero(),
  145. y_i_plus_1: <G1 as Group>::Scalar::zero(),
  146. pc: pc.clone(),
  147. };
  148. let circuit_secondary = TrivialTestCircuit {
  149. _p: Default::default(),
  150. };
  151. // produce public parameters
  152. let pp = PublicParams::<
  153. G1,
  154. G2,
  155. MinRootCircuit<<G1 as Group>::Scalar>,
  156. TrivialTestCircuit<<G2 as Group>::Scalar>,
  157. >::setup(circuit_primary, circuit_secondary.clone());
  158. // produce non-deterministic advice
  159. let num_steps = 3;
  160. let (z0_primary, minroot_circuits) = MinRootCircuit::new(
  161. num_steps,
  162. &<G1 as Group>::Scalar::zero(),
  163. &<G1 as Group>::Scalar::one(),
  164. &pc,
  165. );
  166. let z0_secondary = <G2 as Group>::Scalar::zero();
  167. type C1 = MinRootCircuit<<G1 as Group>::Scalar>;
  168. type C2 = TrivialTestCircuit<<G2 as Group>::Scalar>;
  169. // produce a recursive SNARK
  170. println!("Generating a RecursiveSNARK...");
  171. let mut recursive_snark: Option<RecursiveSNARK<G1, G2, C1, C2>> = None;
  172. for (i, circuit_primary) in minroot_circuits.iter().take(num_steps).enumerate() {
  173. let res = RecursiveSNARK::prove_step(
  174. &pp,
  175. recursive_snark,
  176. circuit_primary.clone(),
  177. circuit_secondary.clone(),
  178. z0_primary,
  179. z0_secondary,
  180. );
  181. assert!(res.is_ok());
  182. println!("RecursiveSNARK::prove_step {}: {:?}", i, res.is_ok());
  183. recursive_snark = Some(res.unwrap());
  184. }
  185. assert!(recursive_snark.is_some());
  186. let recursive_snark = recursive_snark.unwrap();
  187. // verify the recursive SNARK
  188. println!("Verifying a RecursiveSNARK...");
  189. let res = recursive_snark.verify(&pp, num_steps, z0_primary, z0_secondary);
  190. println!("RecursiveSNARK::verify: {:?}", res.is_ok());
  191. assert!(res.is_ok());
  192. // produce a compressed SNARK
  193. println!("Generating a CompressedSNARK...");
  194. let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &recursive_snark);
  195. println!("CompressedSNARK::prove: {:?}", res.is_ok());
  196. assert!(res.is_ok());
  197. let compressed_snark = res.unwrap();
  198. // verify the compressed SNARK
  199. println!("Verifying a CompressedSNARK...");
  200. let res = compressed_snark.verify(&pp, num_steps, z0_primary, z0_secondary);
  201. println!("CompressedSNARK::verify: {:?}", res.is_ok());
  202. assert!(res.is_ok());
  203. }