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.

271 lines
9.9 KiB

Add solidity groth16, kzg10 and final decider verifiers in a dedicated workspace (#70) * change: Refactor structure into workspace * chore: Add empty readme * change: Transform repo into workspace * add: Create folding-verifier-solidity crate * add: Include askama.toml for `sol` extension escaper * add: Jordi's old Groth16 verifier .sol template and adapt it * tmp: create simple template struct to test * Update FoldingSchemes trait, fit Nova+CycleFold - update lib.rs's `FoldingScheme` trait interface - fit Nova+CycleFold into the `FoldingScheme` trait - refactor `src/nova/*` * chore: add serialization assets for testing Now we include an `assets` folder with a serialized proof & vk for tests * Add `examples` dir, with Nova's `FoldingScheme` example * polishing * expose poseidon_test_config outside tests * change: Refactor structure into workspace * chore: Add empty readme * change: Transform repo into workspace * add: Create folding-verifier-solidity crate * add: Include askama.toml for `sol` extension escaper * add: Jordi's old Groth16 verifier .sol template and adapt it * tmp: create simple template struct to test * feat: templating kzg working * chore: add emv and revm * feat: start evm file * chore: add ark-poly-commit * chore: move `commitment` to `folding-schemes` * chore: update `.gitignore` to ignore generated contracts * chore: update template with bn254 lib on it (avoids import), update for loop to account for whitespaces * refactor: update template with no lib * feat: add evm deploy code, compile and create kzg verifier * chore: update `Cargo.toml` to have `folding-schemes` available with verifiers * feat: start kzg prove and verify with sol * chore: compute crs from kzg prover * feat: evm kzg verification passing * tmp * change: Swap order of G2 coordinates within the template * Update way to serialize proof with correct order * chore: update `Cargo.toml` * chore: add revm * chore: add `save_solidity` * refactor: verifiers in dedicated mod * refactor: have dedicated `utils` module * chore: expose modules * chore: update verifier for kzg * chore: rename templates * fix: look for binary using also name of contract * refactor: generate groth16 proof for sha256 pre-image, generate groth16 template with verifying key * chore: template renaming * fix: switch circuit for circuit that simply adds * feat: generates test data on the fly * feat: update to latest groth16 verifier * refactor: rename folder, update `.gitignore` * chore: update `Cargo.toml` * chore: update templates extension to indicate that they are templates * chore: rename templates, both files and structs * fix: template inheritance working * feat: template spdx and pragma statements * feat: decider verifier compiles, update test for kzg10 and groth16 templates * feat: parameterize which size of the crs should be stored on the contract * chore: add comment on how the groth16 and kzg10 proofs will be linked together * chore: cargo clippy run * chore: cargo clippy tests * chore: cargo fmt * refactor: remove unused lifetime parameter * chore: end merge * chore: move examples to `folding-schemes` workspace * get latest main changes * fix: temp fix clippy warnings, will remove lints once not used in tests only * fix: cargo clippy lint added on `code_size` * fix: update path to test circuit and add step for installing solc * chore: remove `save_solidity` steps * fix: the borrowed expression implements the required traits * chore: update `Cargo.toml` * chore: remove extra `[patch.crates-io]` * fix: update to patch at the workspace level and add comment explaining this * refactor: correct `staticcall` with valid input/output sizes and change return syntax for pairing * refactor: expose modules and remove `dead_code` calls * chore: update `README.md`, add additional comments on `kzg10` template and update `groth16` template comments * chore: be clearer on attributions on `kzg10` --------- Co-authored-by: CPerezz <c.perezbaro@gmail.com> Co-authored-by: arnaucube <root@arnaucube.com>
9 months ago
Add solidity groth16, kzg10 and final decider verifiers in a dedicated workspace (#70) * change: Refactor structure into workspace * chore: Add empty readme * change: Transform repo into workspace * add: Create folding-verifier-solidity crate * add: Include askama.toml for `sol` extension escaper * add: Jordi's old Groth16 verifier .sol template and adapt it * tmp: create simple template struct to test * Update FoldingSchemes trait, fit Nova+CycleFold - update lib.rs's `FoldingScheme` trait interface - fit Nova+CycleFold into the `FoldingScheme` trait - refactor `src/nova/*` * chore: add serialization assets for testing Now we include an `assets` folder with a serialized proof & vk for tests * Add `examples` dir, with Nova's `FoldingScheme` example * polishing * expose poseidon_test_config outside tests * change: Refactor structure into workspace * chore: Add empty readme * change: Transform repo into workspace * add: Create folding-verifier-solidity crate * add: Include askama.toml for `sol` extension escaper * add: Jordi's old Groth16 verifier .sol template and adapt it * tmp: create simple template struct to test * feat: templating kzg working * chore: add emv and revm * feat: start evm file * chore: add ark-poly-commit * chore: move `commitment` to `folding-schemes` * chore: update `.gitignore` to ignore generated contracts * chore: update template with bn254 lib on it (avoids import), update for loop to account for whitespaces * refactor: update template with no lib * feat: add evm deploy code, compile and create kzg verifier * chore: update `Cargo.toml` to have `folding-schemes` available with verifiers * feat: start kzg prove and verify with sol * chore: compute crs from kzg prover * feat: evm kzg verification passing * tmp * change: Swap order of G2 coordinates within the template * Update way to serialize proof with correct order * chore: update `Cargo.toml` * chore: add revm * chore: add `save_solidity` * refactor: verifiers in dedicated mod * refactor: have dedicated `utils` module * chore: expose modules * chore: update verifier for kzg * chore: rename templates * fix: look for binary using also name of contract * refactor: generate groth16 proof for sha256 pre-image, generate groth16 template with verifying key * chore: template renaming * fix: switch circuit for circuit that simply adds * feat: generates test data on the fly * feat: update to latest groth16 verifier * refactor: rename folder, update `.gitignore` * chore: update `Cargo.toml` * chore: update templates extension to indicate that they are templates * chore: rename templates, both files and structs * fix: template inheritance working * feat: template spdx and pragma statements * feat: decider verifier compiles, update test for kzg10 and groth16 templates * feat: parameterize which size of the crs should be stored on the contract * chore: add comment on how the groth16 and kzg10 proofs will be linked together * chore: cargo clippy run * chore: cargo clippy tests * chore: cargo fmt * refactor: remove unused lifetime parameter * chore: end merge * chore: move examples to `folding-schemes` workspace * get latest main changes * fix: temp fix clippy warnings, will remove lints once not used in tests only * fix: cargo clippy lint added on `code_size` * fix: update path to test circuit and add step for installing solc * chore: remove `save_solidity` steps * fix: the borrowed expression implements the required traits * chore: update `Cargo.toml` * chore: remove extra `[patch.crates-io]` * fix: update to patch at the workspace level and add comment explaining this * refactor: correct `staticcall` with valid input/output sizes and change return syntax for pairing * refactor: expose modules and remove `dead_code` calls * chore: update `README.md`, add additional comments on `kzg10` template and update `groth16` template comments * chore: be clearer on attributions on `kzg10` --------- Co-authored-by: CPerezz <c.perezbaro@gmail.com> Co-authored-by: arnaucube <root@arnaucube.com>
9 months ago
  1. /**
  2. * @author Privacy and Scaling Explorations team - pse.dev
  3. * @dev Contains utility functions for ops in BN254; in G_1 mostly.
  4. * @notice Forked from https://github.com/weijiekoh/libkzg/tree/master.
  5. * Among others, a few of the changes we did on this fork were:
  6. * - Templating the pragma version
  7. * - Removing type wrappers and use uints instead
  8. * - Performing changes on arg types
  9. * - Update some of the `require` statements
  10. * - Use the bn254 scalar field instead of checking for overflow on the babyjub prime
  11. * - In batch checking, we compute auxiliary polynomials and their commitments at the same time.
  12. */
  13. contract KZG10Verifier {
  14. // prime of field F_p over which y^2 = x^3 + 3 is defined
  15. uint256 public constant BN254_PRIME_FIELD =
  16. 21888242871839275222246405745257275088696311157297823662689037894645226208583;
  17. uint256 public constant BN254_SCALAR_FIELD =
  18. 21888242871839275222246405745257275088548364400416034343698204186575808495617;
  19. /**
  20. * @notice Performs scalar multiplication in G_1.
  21. * @param p G_1 point to multiply
  22. * @param s Scalar to multiply by
  23. * @return r G_1 point p multiplied by scalar s
  24. */
  25. function mulScalar(uint256[2] memory p, uint256 s) internal view returns (uint256[2] memory r) {
  26. uint256[3] memory input;
  27. input[0] = p[0];
  28. input[1] = p[1];
  29. input[2] = s;
  30. bool success;
  31. assembly {
  32. success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40)
  33. switch success
  34. case 0 { invalid() }
  35. }
  36. require(success, "bn254: scalar mul failed");
  37. }
  38. /**
  39. * @notice Negates a point in G_1.
  40. * @param p G_1 point to negate
  41. * @return uint256[2] G_1 point -p
  42. */
  43. function negate(uint256[2] memory p) internal pure returns (uint256[2] memory) {
  44. if (p[0] == 0 && p[1] == 0) {
  45. return p;
  46. }
  47. return [p[0], BN254_PRIME_FIELD - (p[1] % BN254_PRIME_FIELD)];
  48. }
  49. /**
  50. * @notice Adds two points in G_1.
  51. * @param p1 G_1 point 1
  52. * @param p2 G_1 point 2
  53. * @return r G_1 point p1 + p2
  54. */
  55. function add(uint256[2] memory p1, uint256[2] memory p2) internal view returns (uint256[2] memory r) {
  56. bool success;
  57. uint256[4] memory input = [p1[0], p1[1], p2[0], p2[1]];
  58. assembly {
  59. success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40)
  60. switch success
  61. case 0 { invalid() }
  62. }
  63. require(success, "bn254: point add failed");
  64. }
  65. /**
  66. * @notice Computes the pairing check e(p1, p2) * e(p3, p4) == 1
  67. * @dev Note that G_2 points a*i + b are encoded as two elements of F_p, (a, b)
  68. * @param a_1 G_1 point 1
  69. * @param a_2 G_2 point 1
  70. * @param b_1 G_1 point 2
  71. * @param b_2 G_2 point 2
  72. * @return result true if pairing check is successful
  73. */
  74. function pairing(uint256[2] memory a_1, uint256[2][2] memory a_2, uint256[2] memory b_1, uint256[2][2] memory b_2)
  75. internal
  76. view
  77. returns (bool result)
  78. {
  79. uint256[12] memory input = [
  80. a_1[0],
  81. a_1[1],
  82. a_2[0][1], // imaginary part first
  83. a_2[0][0],
  84. a_2[1][1], // imaginary part first
  85. a_2[1][0],
  86. b_1[0],
  87. b_1[1],
  88. b_2[0][1], // imaginary part first
  89. b_2[0][0],
  90. b_2[1][1], // imaginary part first
  91. b_2[1][0]
  92. ];
  93. uint256[1] memory out;
  94. bool success;
  95. assembly {
  96. success := staticcall(sub(gas(), 2000), 8, input, 0x180, out, 0x20)
  97. switch success
  98. case 0 { invalid() }
  99. }
  100. require(success, "bn254: pairing failed");
  101. return out[0] == 1;
  102. }
  103. uint256[2] G_1 = [
  104. {{ g1.0[0] }},
  105. {{ g1.0[1] }}
  106. ];
  107. uint256[2][2] G_2 = [
  108. [
  109. {{ g2.0[0][0] }},
  110. {{ g2.0[0][1] }}
  111. ],
  112. [
  113. {{ g2.0[1][0] }},
  114. {{ g2.0[1][1] }}
  115. ]
  116. ];
  117. uint256[2][2] VK = [
  118. [
  119. {{ vk.0[0][0] }},
  120. {{ vk.0[0][1] }}
  121. ],
  122. [
  123. {{ vk.0[1][0] }},
  124. {{ vk.0[1][1] }}
  125. ]
  126. ];
  127. uint256[2][{{ g1_crs_len }}] G1_CRS = [
  128. {%- for (i, point) in g1_crs.iter().enumerate() %}
  129. [
  130. {{ point.0[0] }},
  131. {{ point.0[1] }}
  132. {% if loop.last -%}
  133. ]
  134. {%- else -%}
  135. ],
  136. {%- endif -%}
  137. {% endfor -%}
  138. ];
  139. /**
  140. * @notice Verifies a single point evaluation proof. Function name follows `ark-poly`.
  141. * @dev To avoid ops in G_2, we slightly tweak how the verification is done.
  142. * @param c G_1 point commitment to polynomial.
  143. * @param pi G_1 point proof.
  144. * @param x Value to prove evaluation of polynomial at.
  145. * @param y Evaluation poly(x).
  146. * @return result Indicates if KZG proof is correct.
  147. */
  148. function check(uint256[2] calldata c, uint256[2] calldata pi, uint256 x, uint256 y)
  149. public
  150. view
  151. returns (bool result)
  152. {
  153. //
  154. // we want to:
  155. // 1. avoid gas intensive ops in G2
  156. // 2. format the pairing check in line with what the evm opcode expects.
  157. //
  158. // we can do this by tweaking the KZG check to be:
  159. //
  160. // e(pi, vk - x * g2) = e(c - y * g1, g2) [initial check]
  161. // e(pi, vk - x * g2) * e(c - y * g1, g2)^{-1} = 1
  162. // e(pi, vk - x * g2) * e(-c + y * g1, g2) = 1 [bilinearity of pairing for all subsequent steps]
  163. // e(pi, vk) * e(pi, -x * g2) * e(-c + y * g1, g2) = 1
  164. // e(pi, vk) * e(-x * pi, g2) * e(-c + y * g1, g2) = 1
  165. // e(pi, vk) * e(x * -pi - c + y * g1, g2) = 1 [done]
  166. // |_ rhs_pairing _|
  167. //
  168. uint256[2] memory rhs_pairing =
  169. add(mulScalar(negate(pi), x), add(negate(c), mulScalar(G_1, y)));
  170. return pairing(pi, VK, rhs_pairing, G_2);
  171. }
  172. function evalPolyAt(uint256[] memory _coefficients, uint256 _index) public pure returns (uint256) {
  173. uint256 m = BN254_SCALAR_FIELD;
  174. uint256 result = 0;
  175. uint256 powerOfX = 1;
  176. for (uint256 i = 0; i < _coefficients.length; i++) {
  177. uint256 coeff = _coefficients[i];
  178. assembly {
  179. result := addmod(result, mulmod(powerOfX, coeff, m), m)
  180. powerOfX := mulmod(powerOfX, _index, m)
  181. }
  182. }
  183. return result;
  184. }
  185. /**
  186. * @notice Ensures that z(x) == 0 and l(x) == y for all x in x_vals and y in y_vals. It returns the commitment to z(x) and l(x).
  187. * @param z_coeffs coefficients of the zero polynomial z(x) = (x - x_1)(x - x_2)...(x - x_n).
  188. * @param l_coeffs coefficients of the lagrange polynomial l(x).
  189. * @param x_vals x values to evaluate the polynomials at.
  190. * @param y_vals y values to which l(x) should evaluate to.
  191. * @return uint256[2] commitment to z(x).
  192. * @return uint256[2] commitment to l(x).
  193. */
  194. function checkAndCommitAuxPolys(
  195. uint256[] memory z_coeffs,
  196. uint256[] memory l_coeffs,
  197. uint256[] memory x_vals,
  198. uint256[] memory y_vals
  199. ) public view returns (uint256[2] memory, uint256[2] memory) {
  200. // z(x) is of degree len(x_vals), it is a product of linear polynomials (x - x_i)
  201. // l(x) is of degree len(x_vals) - 1
  202. uint256[2] memory z_commit;
  203. uint256[2] memory l_commit;
  204. for (uint256 i = 0; i < x_vals.length; i++) {
  205. z_commit = add(z_commit, mulScalar(G1_CRS[i], z_coeffs[i])); // update commitment to z(x)
  206. l_commit = add(l_commit, mulScalar(G1_CRS[i], l_coeffs[i])); // update commitment to l(x)
  207. uint256 eval_z = evalPolyAt(z_coeffs, x_vals[i]);
  208. uint256 eval_l = evalPolyAt(l_coeffs, x_vals[i]);
  209. require(eval_z == 0, "checkAndCommitAuxPolys: wrong zero poly");
  210. require(eval_l == y_vals[i], "checkAndCommitAuxPolys: wrong lagrange poly");
  211. }
  212. // z(x) has len(x_vals) + 1 coeffs, we add to the commitment the last coeff of z(x)
  213. z_commit = add(z_commit, mulScalar(G1_CRS[z_coeffs.length - 1], z_coeffs[z_coeffs.length - 1]));
  214. return (z_commit, l_commit);
  215. }
  216. /**
  217. * @notice Verifies a batch of point evaluation proofs. Function name follows `ark-poly`.
  218. * @dev To avoid ops in G_2, we slightly tweak how the verification is done.
  219. * @param c G1 point commitment to polynomial.
  220. * @param pi G2 point proof.
  221. * @param x_vals Values to prove evaluation of polynomial at.
  222. * @param y_vals Evaluation poly(x).
  223. * @param l_coeffs Coefficients of the lagrange polynomial.
  224. * @param z_coeffs Coefficients of the zero polynomial z(x) = (x - x_1)(x - x_2)...(x - x_n).
  225. * @return result Indicates if KZG proof is correct.
  226. */
  227. function batchCheck(
  228. uint256[2] calldata c,
  229. uint256[2][2] calldata pi,
  230. uint256[] calldata x_vals,
  231. uint256[] calldata y_vals,
  232. uint256[] calldata l_coeffs,
  233. uint256[] calldata z_coeffs
  234. ) public view returns (bool result) {
  235. //
  236. // we want to:
  237. // 1. avoid gas intensive ops in G2
  238. // 2. format the pairing check in line with what the evm opcode expects.
  239. //
  240. // we can do this by tweaking the KZG check to be:
  241. //
  242. // e(z(r) * g1, pi) * e(g1, l(r) * g2) = e(c, g2) [initial check]
  243. // e(z(r) * g1, pi) * e(l(r) * g1, g2) * e(c, g2)^{-1} = 1 [bilinearity of pairing]
  244. // e(z(r) * g1, pi) * e(l(r) * g1 - c, g2) = 1 [done]
  245. //
  246. (uint256[2] memory z_commit, uint256[2] memory l_commit) =
  247. checkAndCommitAuxPolys(z_coeffs, l_coeffs, x_vals, y_vals);
  248. uint256[2] memory neg_commit = negate(c);
  249. return pairing(z_commit, pi, add(l_commit, neg_commit), G_2);
  250. }
  251. }