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.

349 lines
11 KiB

  1. //! This module implements a non-interactive folding scheme
  2. #![allow(non_snake_case)]
  3. #![allow(clippy::type_complexity)]
  4. use super::{
  5. commitments::CompressedCommitment,
  6. constants::NUM_CHALLENGE_BITS,
  7. errors::NovaError,
  8. r1cs::{R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness},
  9. traits::{AbsorbInROTrait, Group, ROTrait},
  10. };
  11. use core::marker::PhantomData;
  12. /// A SNARK that holds the proof of a step of an incremental computation
  13. #[allow(clippy::upper_case_acronyms)]
  14. #[derive(Clone, Debug)]
  15. pub struct NIFS<G: Group> {
  16. pub(crate) comm_T: CompressedCommitment<G::CompressedGroupElement>,
  17. _p: PhantomData<G>,
  18. }
  19. type ROConstants<G> =
  20. <<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants;
  21. impl<G: Group> NIFS<G> {
  22. /// Takes as input a Relaxed R1CS instance-witness tuple `(U1, W1)` and
  23. /// an R1CS instance-witness tuple `(U2, W2)` with the same structure `shape`
  24. /// and defined with respect to the same `gens`, and outputs
  25. /// a folded Relaxed R1CS instance-witness tuple `(U, W)` of the same shape `shape`,
  26. /// with the guarantee that the folded witness `W` satisfies the folded instance `U`
  27. /// if and only if `W1` satisfies `U1` and `W2` satisfies `U2`.
  28. pub fn prove(
  29. gens: &R1CSGens<G>,
  30. ro_consts: &ROConstants<G>,
  31. S: &R1CSShape<G>,
  32. U1: &RelaxedR1CSInstance<G>,
  33. W1: &RelaxedR1CSWitness<G>,
  34. U2: &R1CSInstance<G>,
  35. W2: &R1CSWitness<G>,
  36. ) -> Result<(NIFS<G>, (RelaxedR1CSInstance<G>, RelaxedR1CSWitness<G>)), NovaError> {
  37. // initialize a new RO
  38. let mut ro = G::RO::new(ro_consts.clone());
  39. // append S to the transcript
  40. S.absorb_in_ro(&mut ro);
  41. // append U1 and U2 to transcript
  42. U1.absorb_in_ro(&mut ro);
  43. U2.absorb_in_ro(&mut ro);
  44. // compute a commitment to the cross-term
  45. let (T, comm_T) = S.commit_T(gens, U1, W1, U2, W2)?;
  46. // append `comm_T` to the transcript and obtain a challenge
  47. comm_T.absorb_in_ro(&mut ro);
  48. // compute a challenge from the RO
  49. let r = ro.squeeze(NUM_CHALLENGE_BITS);
  50. // fold the instance using `r` and `comm_T`
  51. let U = U1.fold(U2, &comm_T, &r)?;
  52. // fold the witness using `r` and `T`
  53. let W = W1.fold(W2, &T, &r)?;
  54. // return the folded instance and witness
  55. Ok((
  56. Self {
  57. comm_T: comm_T.compress(),
  58. _p: Default::default(),
  59. },
  60. (U, W),
  61. ))
  62. }
  63. /// Takes as input a relaxed R1CS instance `U1` and and R1CS instance `U2`
  64. /// with the same shape and defined with respect to the same parameters,
  65. /// and outputs a folded instance `U` with the same shape,
  66. /// with the guarantee that the folded instance `U`
  67. /// if and only if `U1` and `U2` are satisfiable.
  68. pub fn verify(
  69. &self,
  70. ro_consts: &ROConstants<G>,
  71. S: &R1CSShape<G>,
  72. U1: &RelaxedR1CSInstance<G>,
  73. U2: &R1CSInstance<G>,
  74. ) -> Result<RelaxedR1CSInstance<G>, NovaError> {
  75. // initialize a new RO
  76. let mut ro = G::RO::new(ro_consts.clone());
  77. // append S to the transcript
  78. S.absorb_in_ro(&mut ro);
  79. // append U1 and U2 to transcript
  80. U1.absorb_in_ro(&mut ro);
  81. U2.absorb_in_ro(&mut ro);
  82. // append `comm_T` to the transcript and obtain a challenge
  83. let comm_T = self.comm_T.decompress()?;
  84. comm_T.absorb_in_ro(&mut ro);
  85. // compute a challenge from the RO
  86. let r = ro.squeeze(NUM_CHALLENGE_BITS);
  87. // fold the instance using `r` and `comm_T`
  88. let U = U1.fold(U2, &comm_T, &r)?;
  89. // return the folded instance
  90. Ok(U)
  91. }
  92. }
  93. #[cfg(test)]
  94. mod tests {
  95. use super::*;
  96. use crate::traits::{Group, ROConstantsTrait};
  97. use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
  98. use ff::{Field, PrimeField};
  99. use rand::rngs::OsRng;
  100. type S = pasta_curves::pallas::Scalar;
  101. type G = pasta_curves::pallas::Point;
  102. fn synthesize_tiny_r1cs_bellperson<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
  103. cs: &mut CS,
  104. x_val: Option<Scalar>,
  105. ) -> Result<(), SynthesisError> {
  106. // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output.
  107. let x = AllocatedNum::alloc(cs.namespace(|| "x"), || Ok(x_val.unwrap()))?;
  108. let _ = x.inputize(cs.namespace(|| "x is input"));
  109. let x_sq = x.square(cs.namespace(|| "x_sq"))?;
  110. let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), &x)?;
  111. let y = AllocatedNum::alloc(cs.namespace(|| "y"), || {
  112. Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + Scalar::from(5u64))
  113. })?;
  114. let _ = y.inputize(cs.namespace(|| "y is output"));
  115. cs.enforce(
  116. || "y = x^3 + x + 5",
  117. |lc| {
  118. lc + x_cu.get_variable()
  119. + x.get_variable()
  120. + CS::one()
  121. + CS::one()
  122. + CS::one()
  123. + CS::one()
  124. + CS::one()
  125. },
  126. |lc| lc + CS::one(),
  127. |lc| lc + y.get_variable(),
  128. );
  129. Ok(())
  130. }
  131. #[test]
  132. fn test_tiny_r1cs_bellperson() {
  133. use crate::bellperson::{
  134. r1cs::{NovaShape, NovaWitness},
  135. shape_cs::ShapeCS,
  136. solver::SatisfyingAssignment,
  137. };
  138. // First create the shape
  139. let mut cs: ShapeCS<G> = ShapeCS::new();
  140. let _ = synthesize_tiny_r1cs_bellperson(&mut cs, None);
  141. let shape = cs.r1cs_shape();
  142. let gens = cs.r1cs_gens();
  143. let ro_consts =
  144. <<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants::new();
  145. // Now get the instance and assignment for one instance
  146. let mut cs: SatisfyingAssignment<G> = SatisfyingAssignment::new();
  147. let _ = synthesize_tiny_r1cs_bellperson(&mut cs, Some(S::from(5)));
  148. let (U1, W1) = cs.r1cs_instance_and_witness(&shape, &gens).unwrap();
  149. // Make sure that the first instance is satisfiable
  150. assert!(shape.is_sat(&gens, &U1, &W1).is_ok());
  151. // Now get the instance and assignment for second instance
  152. let mut cs: SatisfyingAssignment<G> = SatisfyingAssignment::new();
  153. let _ = synthesize_tiny_r1cs_bellperson(&mut cs, Some(S::from(135)));
  154. let (U2, W2) = cs.r1cs_instance_and_witness(&shape, &gens).unwrap();
  155. // Make sure that the second instance is satisfiable
  156. assert!(shape.is_sat(&gens, &U2, &W2).is_ok());
  157. // execute a sequence of folds
  158. execute_sequence(&gens, &ro_consts, &shape, &U1, &W1, &U2, &W2);
  159. }
  160. fn execute_sequence(
  161. gens: &R1CSGens<G>,
  162. ro_consts: &<<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants,
  163. shape: &R1CSShape<G>,
  164. U1: &R1CSInstance<G>,
  165. W1: &R1CSWitness<G>,
  166. U2: &R1CSInstance<G>,
  167. W2: &R1CSWitness<G>,
  168. ) {
  169. // produce a default running instance
  170. let mut r_W = RelaxedR1CSWitness::default(shape);
  171. let mut r_U = RelaxedR1CSInstance::default(gens, shape);
  172. // produce a step SNARK with (W1, U1) as the first incoming witness-instance pair
  173. let res = NIFS::prove(gens, ro_consts, shape, &r_U, &r_W, U1, W1);
  174. assert!(res.is_ok());
  175. let (nifs, (_U, W)) = res.unwrap();
  176. // verify the step SNARK with U1 as the first incoming instance
  177. let res = nifs.verify(ro_consts, shape, &r_U, U1);
  178. assert!(res.is_ok());
  179. let U = res.unwrap();
  180. assert_eq!(U, _U);
  181. // update the running witness and instance
  182. r_W = W;
  183. r_U = U;
  184. // produce a step SNARK with (W2, U2) as the second incoming witness-instance pair
  185. let res = NIFS::prove(gens, ro_consts, shape, &r_U, &r_W, U2, W2);
  186. assert!(res.is_ok());
  187. let (nifs, (_U, W)) = res.unwrap();
  188. // verify the step SNARK with U1 as the first incoming instance
  189. let res = nifs.verify(ro_consts, shape, &r_U, U2);
  190. assert!(res.is_ok());
  191. let U = res.unwrap();
  192. assert_eq!(U, _U);
  193. // update the running witness and instance
  194. r_W = W;
  195. r_U = U;
  196. // check if the running instance is satisfiable
  197. assert!(shape.is_sat_relaxed(gens, &r_U, &r_W).is_ok());
  198. }
  199. #[test]
  200. fn test_tiny_r1cs() {
  201. let one = S::one();
  202. let (num_cons, num_vars, num_io, A, B, C) = {
  203. let num_cons = 4;
  204. let num_vars = 4;
  205. let num_io = 2;
  206. // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output.
  207. // The R1CS for this problem consists of the following constraints:
  208. // `I0 * I0 - Z0 = 0`
  209. // `Z0 * I0 - Z1 = 0`
  210. // `(Z1 + I0) * 1 - Z2 = 0`
  211. // `(Z2 + 5) * 1 - I1 = 0`
  212. // Relaxed R1CS is a set of three sparse matrices (A B C), where there is a row for every
  213. // constraint and a column for every entry in z = (vars, u, inputs)
  214. // An R1CS instance is satisfiable iff:
  215. // Az \circ Bz = u \cdot Cz + E, where z = (vars, 1, inputs)
  216. let mut A: Vec<(usize, usize, S)> = Vec::new();
  217. let mut B: Vec<(usize, usize, S)> = Vec::new();
  218. let mut C: Vec<(usize, usize, S)> = Vec::new();
  219. // constraint 0 entries in (A,B,C)
  220. // `I0 * I0 - Z0 = 0`
  221. A.push((0, num_vars + 1, one));
  222. B.push((0, num_vars + 1, one));
  223. C.push((0, 0, one));
  224. // constraint 1 entries in (A,B,C)
  225. // `Z0 * I0 - Z1 = 0`
  226. A.push((1, 0, one));
  227. B.push((1, num_vars + 1, one));
  228. C.push((1, 1, one));
  229. // constraint 2 entries in (A,B,C)
  230. // `(Z1 + I0) * 1 - Z2 = 0`
  231. A.push((2, 1, one));
  232. A.push((2, num_vars + 1, one));
  233. B.push((2, num_vars, one));
  234. C.push((2, 2, one));
  235. // constraint 3 entries in (A,B,C)
  236. // `(Z2 + 5) * 1 - I1 = 0`
  237. A.push((3, 2, one));
  238. A.push((3, num_vars, one + one + one + one + one));
  239. B.push((3, num_vars, one));
  240. C.push((3, num_vars + 2, one));
  241. (num_cons, num_vars, num_io, A, B, C)
  242. };
  243. // create a shape object
  244. let S = {
  245. let res = R1CSShape::new(num_cons, num_vars, num_io, &A, &B, &C);
  246. assert!(res.is_ok());
  247. res.unwrap()
  248. };
  249. // generate generators and ro constants
  250. let gens = R1CSGens::new(num_cons, num_vars);
  251. let ro_consts =
  252. <<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants::new();
  253. let rand_inst_witness_generator =
  254. |gens: &R1CSGens<G>, I: &S| -> (S, R1CSInstance<G>, R1CSWitness<G>) {
  255. let i0 = *I;
  256. // compute a satisfying (vars, X) tuple
  257. let (O, vars, X) = {
  258. let z0 = i0 * i0; // constraint 0
  259. let z1 = i0 * z0; // constraint 1
  260. let z2 = z1 + i0; // constraint 2
  261. let i1 = z2 + one + one + one + one + one; // constraint 3
  262. // store the witness and IO for the instance
  263. let W = vec![z0, z1, z2, S::zero()];
  264. let X = vec![i0, i1];
  265. (i1, W, X)
  266. };
  267. let W = {
  268. let res = R1CSWitness::new(&S, &vars);
  269. assert!(res.is_ok());
  270. res.unwrap()
  271. };
  272. let U = {
  273. let comm_W = W.commit(gens);
  274. let res = R1CSInstance::new(&S, &comm_W, &X);
  275. assert!(res.is_ok());
  276. res.unwrap()
  277. };
  278. // check that generated instance is satisfiable
  279. assert!(S.is_sat(gens, &U, &W).is_ok());
  280. (O, U, W)
  281. };
  282. let mut csprng: OsRng = OsRng;
  283. let I = S::random(&mut csprng); // the first input is picked randomly for the first instance
  284. let (O, U1, W1) = rand_inst_witness_generator(&gens, &I);
  285. let (_O, U2, W2) = rand_inst_witness_generator(&gens, &O);
  286. // execute a sequence of folds
  287. execute_sequence(&gens, &ro_consts, &S, &U1, &W1, &U2, &W2);
  288. }
  289. }