github.com/consensys/gnark@v0.11.0/backend/groth16/bn254/solidity.go (about) 1 package groth16 2 3 import ( 4 "bytes" 5 6 "github.com/consensys/gnark-crypto/ecc/bn254/fr" 7 ) 8 9 // solidityTemplate 10 // this is an experimental feature and gnark solidity generator as not been thoroughly tested 11 const solidityTemplate = ` 12 {{- $numPublic := sub (len .Vk.G1.K) 1 }} 13 {{- $numCommitments := len .Vk.PublicAndCommitmentCommitted }} 14 {{- $numWitness := sub $numPublic $numCommitments }} 15 {{- $PublicAndCommitmentCommitted := .Vk.PublicAndCommitmentCommitted }} 16 // SPDX-License-Identifier: MIT 17 18 pragma solidity {{ .Cfg.PragmaVersion }}; 19 20 /// @title Groth16 verifier template. 21 /// @author Remco Bloemen 22 /// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed 23 /// (256 bytes) and compressed (128 bytes) format. A view function is provided 24 /// to compress proofs. 25 /// @notice See <https://2π.com/23/bn254-compression> for further explanation. 26 contract Verifier { 27 28 /// Some of the provided public input values are larger than the field modulus. 29 /// @dev Public input elements are not automatically reduced, as this is can be 30 /// a dangerous source of bugs. 31 error PublicInputNotInField(); 32 33 /// The proof is invalid. 34 /// @dev This can mean that provided Groth16 proof points are not on their 35 /// curves, that pairing equation fails, or that the proof is not for the 36 /// provided public input. 37 error ProofInvalid(); 38 39 {{- if gt $numCommitments 0 }} 40 /// The commitment is invalid 41 /// @dev This can mean that provided commitment points and/or proof of knowledge are not on their 42 /// curves, that pairing equation fails, or that the commitment and/or proof of knowledge is not for the 43 /// commitment key. 44 error CommitmentInvalid(); 45 {{- end }} 46 47 // Addresses of precompiles 48 uint256 constant PRECOMPILE_MODEXP = 0x05; 49 uint256 constant PRECOMPILE_ADD = 0x06; 50 uint256 constant PRECOMPILE_MUL = 0x07; 51 uint256 constant PRECOMPILE_VERIFY = 0x08; 52 53 // Base field Fp order P and scalar field Fr order R. 54 // For BN254 these are computed as follows: 55 // t = 4965661367192848881 56 // P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1 57 // R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1 58 uint256 constant P = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; 59 uint256 constant R = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; 60 61 // Extension field Fp2 = Fp[i] / (i² + 1) 62 // Note: This is the complex extension field of Fp with i² = -1. 63 // Values in Fp2 are represented as a pair of Fp elements (a₀, a₁) as a₀ + a₁⋅i. 64 // Note: The order of Fp2 elements is *opposite* that of the pairing contract, which 65 // expects Fp2 elements in order (a₁, a₀). This is also the order in which 66 // Fp2 elements are encoded in the public interface as this became convention. 67 68 // Constants in Fp 69 uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; 70 uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; 71 uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; 72 73 // Exponents for inversions and square roots mod P 74 uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 75 uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; 76 77 // Groth16 alpha point in G1 78 uint256 constant ALPHA_X = {{ (fpstr .Vk.G1.Alpha.X) }}; 79 uint256 constant ALPHA_Y = {{ (fpstr .Vk.G1.Alpha.Y) }}; 80 81 // Groth16 beta point in G2 in powers of i 82 uint256 constant BETA_NEG_X_0 = {{ (fpstr .Vk.G2.Beta.X.A0) }}; 83 uint256 constant BETA_NEG_X_1 = {{ (fpstr .Vk.G2.Beta.X.A1) }}; 84 uint256 constant BETA_NEG_Y_0 = {{ (fpstr .Vk.G2.Beta.Y.A0) }}; 85 uint256 constant BETA_NEG_Y_1 = {{ (fpstr .Vk.G2.Beta.Y.A1) }}; 86 87 // Groth16 gamma point in G2 in powers of i 88 uint256 constant GAMMA_NEG_X_0 = {{ (fpstr .Vk.G2.Gamma.X.A0) }}; 89 uint256 constant GAMMA_NEG_X_1 = {{ (fpstr .Vk.G2.Gamma.X.A1) }}; 90 uint256 constant GAMMA_NEG_Y_0 = {{ (fpstr .Vk.G2.Gamma.Y.A0) }}; 91 uint256 constant GAMMA_NEG_Y_1 = {{ (fpstr .Vk.G2.Gamma.Y.A1) }}; 92 93 // Groth16 delta point in G2 in powers of i 94 uint256 constant DELTA_NEG_X_0 = {{ (fpstr .Vk.G2.Delta.X.A0) }}; 95 uint256 constant DELTA_NEG_X_1 = {{ (fpstr .Vk.G2.Delta.X.A1) }}; 96 uint256 constant DELTA_NEG_Y_0 = {{ (fpstr .Vk.G2.Delta.Y.A0) }}; 97 uint256 constant DELTA_NEG_Y_1 = {{ (fpstr .Vk.G2.Delta.Y.A1) }}; 98 99 {{- if gt $numCommitments 0 }} 100 // Pedersen G point in G2 in powers of i 101 {{- $cmtVk0 := index .Vk.CommitmentKeys 0 }} 102 uint256 constant PEDERSEN_G_X_0 = {{ (fpstr $cmtVk0.G.X.A0) }}; 103 uint256 constant PEDERSEN_G_X_1 = {{ (fpstr $cmtVk0.G.X.A1) }}; 104 uint256 constant PEDERSEN_G_Y_0 = {{ (fpstr $cmtVk0.G.Y.A0) }}; 105 uint256 constant PEDERSEN_G_Y_1 = {{ (fpstr $cmtVk0.G.Y.A1) }}; 106 107 // Pedersen GSigma point in G2 in powers of i 108 uint256 constant PEDERSEN_GSIGMA_X_0 = {{ (fpstr $cmtVk0.GSigma.X.A0) }}; 109 uint256 constant PEDERSEN_GSIGMA_X_1 = {{ (fpstr $cmtVk0.GSigma.X.A1) }}; 110 uint256 constant PEDERSEN_GSIGMA_Y_0 = {{ (fpstr $cmtVk0.GSigma.Y.A0) }}; 111 uint256 constant PEDERSEN_GSIGMA_Y_1 = {{ (fpstr $cmtVk0.GSigma.Y.A1) }}; 112 {{- end }} 113 114 // Constant and public input points 115 {{- $k0 := index .Vk.G1.K 0}} 116 uint256 constant CONSTANT_X = {{ (fpstr $k0.X) }}; 117 uint256 constant CONSTANT_Y = {{ (fpstr $k0.Y) }}; 118 {{- range $i, $ki := .Vk.G1.K }} 119 {{- if gt $i 0 }} 120 uint256 constant PUB_{{sub $i 1}}_X = {{ (fpstr $ki.X) }}; 121 uint256 constant PUB_{{sub $i 1}}_Y = {{ (fpstr $ki.Y) }}; 122 {{- end }} 123 {{- end }} 124 125 /// Negation in Fp. 126 /// @notice Returns a number x such that a + x = 0 in Fp. 127 /// @notice The input does not need to be reduced. 128 /// @param a the base 129 /// @return x the result 130 function negate(uint256 a) internal pure returns (uint256 x) { 131 unchecked { 132 x = (P - (a % P)) % P; // Modulo is cheaper than branching 133 } 134 } 135 136 /// Exponentiation in Fp. 137 /// @notice Returns a number x such that a ^ e = x in Fp. 138 /// @notice The input does not need to be reduced. 139 /// @param a the base 140 /// @param e the exponent 141 /// @return x the result 142 function exp(uint256 a, uint256 e) internal view returns (uint256 x) { 143 bool success; 144 assembly ("memory-safe") { 145 let f := mload(0x40) 146 mstore(f, 0x20) 147 mstore(add(f, 0x20), 0x20) 148 mstore(add(f, 0x40), 0x20) 149 mstore(add(f, 0x60), a) 150 mstore(add(f, 0x80), e) 151 mstore(add(f, 0xa0), P) 152 success := staticcall(gas(), PRECOMPILE_MODEXP, f, 0xc0, f, 0x20) 153 x := mload(f) 154 } 155 if (!success) { 156 // Exponentiation failed. 157 // Should not happen. 158 revert ProofInvalid(); 159 } 160 } 161 162 /// Invertsion in Fp. 163 /// @notice Returns a number x such that a * x = 1 in Fp. 164 /// @notice The input does not need to be reduced. 165 /// @notice Reverts with ProofInvalid() if the inverse does not exist 166 /// @param a the input 167 /// @return x the solution 168 function invert_Fp(uint256 a) internal view returns (uint256 x) { 169 x = exp(a, EXP_INVERSE_FP); 170 if (mulmod(a, x, P) != 1) { 171 // Inverse does not exist. 172 // Can only happen during G2 point decompression. 173 revert ProofInvalid(); 174 } 175 } 176 177 /// Square root in Fp. 178 /// @notice Returns a number x such that x * x = a in Fp. 179 /// @notice Will revert with InvalidProof() if the input is not a square 180 /// or not reduced. 181 /// @param a the square 182 /// @return x the solution 183 function sqrt_Fp(uint256 a) internal view returns (uint256 x) { 184 x = exp(a, EXP_SQRT_FP); 185 if (mulmod(x, x, P) != a) { 186 // Square root does not exist or a is not reduced. 187 // Happens when G1 point is not on curve. 188 revert ProofInvalid(); 189 } 190 } 191 192 /// Square test in Fp. 193 /// @notice Returns whether a number x exists such that x * x = a in Fp. 194 /// @notice Will revert with InvalidProof() if the input is not a square 195 /// or not reduced. 196 /// @param a the square 197 /// @return x the solution 198 function isSquare_Fp(uint256 a) internal view returns (bool) { 199 uint256 x = exp(a, EXP_SQRT_FP); 200 return mulmod(x, x, P) == a; 201 } 202 203 /// Square root in Fp2. 204 /// @notice Fp2 is the complex extension Fp[i]/(i^2 + 1). The input is 205 /// a0 + a1 ⋅ i and the result is x0 + x1 ⋅ i. 206 /// @notice Will revert with InvalidProof() if 207 /// * the input is not a square, 208 /// * the hint is incorrect, or 209 /// * the input coefficents are not reduced. 210 /// @param a0 The real part of the input. 211 /// @param a1 The imaginary part of the input. 212 /// @param hint A hint which of two possible signs to pick in the equation. 213 /// @return x0 The real part of the square root. 214 /// @return x1 The imaginary part of the square root. 215 function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) { 216 // If this square root reverts there is no solution in Fp2. 217 uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); 218 if (hint) { 219 d = negate(d); 220 } 221 // If this square root reverts there is no solution in Fp2. 222 x0 = sqrt_Fp(mulmod(addmod(a0, d, P), FRACTION_1_2_FP, P)); 223 x1 = mulmod(a1, invert_Fp(mulmod(x0, 2, P)), P); 224 225 // Check result to make sure we found a root. 226 // Note: this also fails if a0 or a1 is not reduced. 227 if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) 228 || a1 != mulmod(2, mulmod(x0, x1, P), P)) { 229 revert ProofInvalid(); 230 } 231 } 232 233 /// Compress a G1 point. 234 /// @notice Reverts with InvalidProof if the coordinates are not reduced 235 /// or if the point is not on the curve. 236 /// @notice The point at infinity is encoded as (0,0) and compressed to 0. 237 /// @param x The X coordinate in Fp. 238 /// @param y The Y coordinate in Fp. 239 /// @return c The compresed point (x with one signal bit). 240 function compress_g1(uint256 x, uint256 y) internal view returns (uint256 c) { 241 if (x >= P || y >= P) { 242 // G1 point not in field. 243 revert ProofInvalid(); 244 } 245 if (x == 0 && y == 0) { 246 // Point at infinity 247 return 0; 248 } 249 250 // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. 251 uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); 252 if (y == y_pos) { 253 return (x << 1) | 0; 254 } else if (y == negate(y_pos)) { 255 return (x << 1) | 1; 256 } else { 257 // G1 point not on curve. 258 revert ProofInvalid(); 259 } 260 } 261 262 /// Decompress a G1 point. 263 /// @notice Reverts with InvalidProof if the input does not represent a valid point. 264 /// @notice The point at infinity is encoded as (0,0) and compressed to 0. 265 /// @param c The compresed point (x with one signal bit). 266 /// @return x The X coordinate in Fp. 267 /// @return y The Y coordinate in Fp. 268 function decompress_g1(uint256 c) internal view returns (uint256 x, uint256 y) { 269 // Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square. 270 // so we can use it to represent the point at infinity. 271 if (c == 0) { 272 // Point at infinity as encoded in EIP196 and EIP197. 273 return (0, 0); 274 } 275 bool negate_point = c & 1 == 1; 276 x = c >> 1; 277 if (x >= P) { 278 // G1 x coordinate not in field. 279 revert ProofInvalid(); 280 } 281 282 // Note: (x³ + 3) is irreducible in Fp, so it can not be zero and therefore 283 // y can not be zero. 284 // Note: sqrt_Fp reverts if there is no solution, i.e. the point is not on the curve. 285 y = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); 286 if (negate_point) { 287 y = negate(y); 288 } 289 } 290 291 /// Compress a G2 point. 292 /// @notice Reverts with InvalidProof if the coefficients are not reduced 293 /// or if the point is not on the curve. 294 /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) 295 /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). 296 /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). 297 /// @param x0 The real part of the X coordinate. 298 /// @param x1 The imaginary poart of the X coordinate. 299 /// @param y0 The real part of the Y coordinate. 300 /// @param y1 The imaginary part of the Y coordinate. 301 /// @return c0 The first half of the compresed point (x0 with two signal bits). 302 /// @return c1 The second half of the compressed point (x1 unmodified). 303 function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) 304 internal view returns (uint256 c0, uint256 c1) { 305 if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { 306 // G2 point not in field. 307 revert ProofInvalid(); 308 } 309 if ((x0 | x1 | y0 | y1) == 0) { 310 // Point at infinity 311 return (0, 0); 312 } 313 314 // Compute y^2 315 // Note: shadowing variables and scoping to avoid stack-to-deep. 316 uint256 y0_pos; 317 uint256 y1_pos; 318 { 319 uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); 320 uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); 321 uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); 322 y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); 323 y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); 324 } 325 326 // Determine hint bit 327 // If this sqrt fails the x coordinate is not on the curve. 328 bool hint; 329 { 330 uint256 d = sqrt_Fp(addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P)); 331 hint = !isSquare_Fp(mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P)); 332 } 333 334 // Recover y 335 (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); 336 if (y0 == y0_pos && y1 == y1_pos) { 337 c0 = (x0 << 2) | (hint ? 2 : 0) | 0; 338 c1 = x1; 339 } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { 340 c0 = (x0 << 2) | (hint ? 2 : 0) | 1; 341 c1 = x1; 342 } else { 343 // G1 point not on curve. 344 revert ProofInvalid(); 345 } 346 } 347 348 /// Decompress a G2 point. 349 /// @notice Reverts with InvalidProof if the input does not represent a valid point. 350 /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) 351 /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). 352 /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). 353 /// @param c0 The first half of the compresed point (x0 with two signal bits). 354 /// @param c1 The second half of the compressed point (x1 unmodified). 355 /// @return x0 The real part of the X coordinate. 356 /// @return x1 The imaginary poart of the X coordinate. 357 /// @return y0 The real part of the Y coordinate. 358 /// @return y1 The imaginary part of the Y coordinate. 359 function decompress_g2(uint256 c0, uint256 c1) 360 internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { 361 // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. 362 // so we can use it to represent the point at infinity. 363 if (c0 == 0 && c1 == 0) { 364 // Point at infinity as encoded in EIP197. 365 return (0, 0, 0, 0); 366 } 367 bool negate_point = c0 & 1 == 1; 368 bool hint = c0 & 2 == 2; 369 x0 = c0 >> 2; 370 x1 = c1; 371 if (x0 >= P || x1 >= P) { 372 // G2 x0 or x1 coefficient not in field. 373 revert ProofInvalid(); 374 } 375 376 uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); 377 uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); 378 uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); 379 380 y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); 381 y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); 382 383 // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. 384 // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. 385 // But y0 or y1 may still independently be zero. 386 (y0, y1) = sqrt_Fp2(y0, y1, hint); 387 if (negate_point) { 388 y0 = negate(y0); 389 y1 = negate(y1); 390 } 391 } 392 393 /// Compute the public input linear combination. 394 /// @notice Reverts with PublicInputNotInField if the input is not in the field. 395 /// @notice Computes the multi-scalar-multiplication of the public input 396 /// elements and the verification key including the constant term. 397 /// @param input The public inputs. These are elements of the scalar field Fr. 398 {{- if gt $numCommitments 0 }} 399 /// @param publicCommitments public inputs generated from pedersen commitments. 400 /// @param commitments The Pedersen commitments from the proof. 401 {{- end }} 402 /// @return x The X coordinate of the resulting G1 point. 403 /// @return y The Y coordinate of the resulting G1 point. 404 {{- if eq $numCommitments 0 }} 405 function publicInputMSM(uint256[{{$numWitness}}] calldata input) 406 {{- else }} 407 function publicInputMSM( 408 uint256[{{$numWitness}}] calldata input, 409 uint256[{{$numCommitments}}] memory publicCommitments, 410 uint256[{{mul 2 $numCommitments}}] memory commitments 411 ) 412 {{- end }} 413 internal view returns (uint256 x, uint256 y) { 414 // Note: The ECMUL precompile does not reject unreduced values, so we check this. 415 // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the 416 // code-size is in the PUB_ constants. 417 // ECMUL has input (x, y, scalar) and output (x', y'). 418 // ECADD has input (x1, y1, x2, y2) and output (x', y'). 419 // We reduce commitments(if any) with constants as the first point argument to ECADD. 420 // We call them such that ecmul output is already in the second point 421 // argument to ECADD so we can have a tight loop. 422 bool success = true; 423 assembly ("memory-safe") { 424 let f := mload(0x40) 425 let g := add(f, 0x40) 426 let s 427 mstore(f, CONSTANT_X) 428 mstore(add(f, 0x20), CONSTANT_Y) 429 {{- if gt $numCommitments 0 }} 430 {{- if eq $numWitness 1 }} 431 mstore(g, mload(commitments)) 432 mstore(add(g, 0x20), mload(add(commitments, 0x20))) 433 {{- else }} 434 success := and(success, staticcall(gas(), PRECOMPILE_ADD, commitments, {{mul 0x40 $numCommitments}}, g, 0x40)) 435 {{- end }} 436 success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) 437 {{- end }} 438 {{- range $i := intRange $numPublic }} 439 mstore(g, PUB_{{$i}}_X) 440 mstore(add(g, 0x20), PUB_{{$i}}_Y) 441 {{- if eq $i 0 }} 442 s := calldataload(input) 443 {{- else if lt $i $numWitness }} 444 s := calldataload(add(input, {{mul $i 0x20}})) 445 {{- else if eq $i $numWitness }} 446 s := mload(publicCommitments) 447 {{- else}} 448 s := mload(add(publicCommitments, {{mul 0x20 (sub $i $numWitness)}})) 449 {{- end }} 450 mstore(add(g, 0x40), s) 451 success := and(success, lt(s, R)) 452 success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) 453 success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) 454 {{- end }} 455 456 x := mload(f) 457 y := mload(add(f, 0x20)) 458 } 459 if (!success) { 460 // Either Public input not in field, or verification key invalid. 461 // We assume the contract is correctly generated, so the verification key is valid. 462 revert PublicInputNotInField(); 463 } 464 } 465 466 /// Compress a proof. 467 /// @notice Will revert with InvalidProof if the curve points are invalid, 468 /// but does not verify the proof itself. 469 /// @param proof The uncompressed Groth16 proof. Elements are in the same order as for 470 /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197. 471 {{- if gt $numCommitments 0 }} 472 /// @param commitments Pedersen commitments from the proof. 473 /// @param commitmentPok proof of knowledge for the Pedersen commitments. 474 {{- end }} 475 /// @return compressed The compressed proof. Elements are in the same order as for 476 /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. 477 {{- if gt $numCommitments 0 }} 478 /// @return compressedCommitments compressed Pedersen commitments from the proof. 479 /// @return compressedCommitmentPok compressed proof of knowledge for the Pedersen commitments. 480 {{- end }} 481 {{- if eq $numCommitments 0 }} 482 function compressProof(uint256[8] calldata proof) 483 public view returns (uint256[4] memory compressed) { 484 {{- else }} 485 function compressProof( 486 uint256[8] calldata proof, 487 uint256[{{mul 2 $numCommitments}}] calldata commitments, 488 uint256[2] calldata commitmentPok 489 ) 490 public view returns ( 491 uint256[4] memory compressed, 492 uint256[{{$numCommitments}}] memory compressedCommitments, 493 uint256 compressedCommitmentPok 494 ) { 495 {{- end }} 496 compressed[0] = compress_g1(proof[0], proof[1]); 497 (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); 498 compressed[3] = compress_g1(proof[6], proof[7]); 499 {{- if gt $numCommitments 0 }} 500 {{- range $i := intRange $numCommitments }} 501 compressedCommitments[{{$i}}] = compress_g1(commitments[{{mul 2 $i}}], commitments[{{sum (mul 2 $i) 1}}]); 502 {{- end }} 503 compressedCommitmentPok = compress_g1(commitmentPok[0], commitmentPok[1]); 504 {{- end }} 505 } 506 507 /// Verify a Groth16 proof with compressed points. 508 /// @notice Reverts with InvalidProof if the proof is invalid or 509 /// with PublicInputNotInField the public input is not reduced. 510 /// @notice There is no return value. If the function does not revert, the 511 /// proof was successfully verified. 512 /// @param compressedProof the points (A, B, C) in compressed format 513 /// matching the output of compressProof. 514 {{- if gt $numCommitments 0 }} 515 /// @param compressedCommitments compressed Pedersen commitments from the proof. 516 /// @param compressedCommitmentPok compressed proof of knowledge for the Pedersen commitments. 517 {{- end }} 518 /// @param input the public input field elements in the scalar field Fr. 519 /// Elements must be reduced. 520 function verifyCompressedProof( 521 uint256[4] calldata compressedProof, 522 {{- if gt $numCommitments 0}} 523 uint256[{{$numCommitments}}] calldata compressedCommitments, 524 uint256 compressedCommitmentPok, 525 {{- end }} 526 uint256[{{$numWitness}}] calldata input 527 ) public view { 528 {{- if gt $numCommitments 0 }} 529 uint256[{{$numCommitments}}] memory publicCommitments; 530 uint256[{{mul 2 $numCommitments}}] memory commitments; 531 {{- end }} 532 uint256[24] memory pairings; 533 534 {{- if gt $numCommitments 0 }} 535 { 536 {{- if eq $numCommitments 1 }} 537 (commitments[0], commitments[1]) = decompress_g1(compressedCommitments[0]); 538 {{- else }} 539 // TODO: We can fold commitments into a single point for more efficient verification (https://github.com/Consensys/gnark/issues/1095) 540 for (uint256 i = 0; i < {{$numCommitments}}; i++) { 541 (commitments[2*i], commitments[2*i+1]) = decompress_g1(compressedCommitments[i]); 542 } 543 {{- end}} 544 (uint256 Px, uint256 Py) = decompress_g1(compressedCommitmentPok); 545 546 uint256[] memory publicAndCommitmentCommitted; 547 {{- range $i := intRange $numCommitments }} 548 {{- $pcIndex := index $PublicAndCommitmentCommitted $i }} 549 {{- if gt (len $pcIndex) 0 }} 550 publicAndCommitmentCommitted = new uint256[]({{(len $pcIndex)}}); 551 assembly ("memory-safe") { 552 let publicAndCommitmentCommittedOffset := add(publicAndCommitmentCommitted, 0x20) 553 {{- $segment_start := index $pcIndex 0 }} 554 {{- $segment_end := index $pcIndex 0 }} 555 {{- $l := 0 }} 556 {{- range $k := intRange (sub (len $pcIndex) 1) }} 557 {{- $next := index $pcIndex (sum $k 1) }} 558 {{- if ne $next (sum $segment_end 1) }} 559 calldatacopy(add(publicAndCommitmentCommittedOffset, {{mul $l 0x20}}), add(input, {{mul 0x20 (sub $segment_start 1)}}), {{mul 0x20 (sum 1 (sub $segment_end $segment_start))}}) 560 {{- $segment_start = $next }} 561 {{- $l = (sum $k 1) }} 562 {{- end }} 563 {{- $segment_end = $next }} 564 {{- end }} 565 calldatacopy(add(publicAndCommitmentCommittedOffset, {{mul $l 0x20}}), add(input, {{mul 0x20 (sub $segment_start 1)}}), {{mul 0x20 (sum 1 (sub $segment_end $segment_start))}}) 566 } 567 {{- end }} 568 569 publicCommitments[{{$i}}] = uint256( 570 {{ hashFnName }}( 571 abi.encodePacked( 572 commitments[{{mul $i 2}}], 573 commitments[{{sum (mul $i 2) 1}}], 574 publicAndCommitmentCommitted 575 ) 576 ) 577 ) % R; 578 {{- end }} 579 // Commitments 580 pairings[ 0] = commitments[0]; 581 pairings[ 1] = commitments[1]; 582 pairings[ 2] = PEDERSEN_GSIGMA_X_1; 583 pairings[ 3] = PEDERSEN_GSIGMA_X_0; 584 pairings[ 4] = PEDERSEN_GSIGMA_Y_1; 585 pairings[ 5] = PEDERSEN_GSIGMA_Y_0; 586 pairings[ 6] = Px; 587 pairings[ 7] = Py; 588 pairings[ 8] = PEDERSEN_G_X_1; 589 pairings[ 9] = PEDERSEN_G_X_0; 590 pairings[10] = PEDERSEN_G_Y_1; 591 pairings[11] = PEDERSEN_G_Y_0; 592 593 // Verify pedersen commitments 594 bool success; 595 assembly ("memory-safe") { 596 let f := mload(0x40) 597 598 success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x180, f, 0x20) 599 success := and(success, mload(f)) 600 } 601 if (!success) { 602 revert CommitmentInvalid(); 603 } 604 } 605 {{- end }} 606 607 { 608 (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); 609 (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2(compressedProof[2], compressedProof[1]); 610 (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); 611 {{- if eq $numCommitments 0 }} 612 (uint256 Lx, uint256 Ly) = publicInputMSM(input); 613 {{- else }} 614 (uint256 Lx, uint256 Ly) = publicInputMSM( 615 input, 616 publicCommitments, 617 commitments 618 ); 619 {{- end}} 620 621 // Verify the pairing 622 // Note: The precompile expects the F2 coefficients in big-endian order. 623 // Note: The pairing precompile rejects unreduced values, so we won't check that here. 624 // e(A, B) 625 pairings[ 0] = Ax; 626 pairings[ 1] = Ay; 627 pairings[ 2] = Bx1; 628 pairings[ 3] = Bx0; 629 pairings[ 4] = By1; 630 pairings[ 5] = By0; 631 // e(C, -δ) 632 pairings[ 6] = Cx; 633 pairings[ 7] = Cy; 634 pairings[ 8] = DELTA_NEG_X_1; 635 pairings[ 9] = DELTA_NEG_X_0; 636 pairings[10] = DELTA_NEG_Y_1; 637 pairings[11] = DELTA_NEG_Y_0; 638 // e(α, -β) 639 pairings[12] = ALPHA_X; 640 pairings[13] = ALPHA_Y; 641 pairings[14] = BETA_NEG_X_1; 642 pairings[15] = BETA_NEG_X_0; 643 pairings[16] = BETA_NEG_Y_1; 644 pairings[17] = BETA_NEG_Y_0; 645 // e(L_pub, -γ) 646 pairings[18] = Lx; 647 pairings[19] = Ly; 648 pairings[20] = GAMMA_NEG_X_1; 649 pairings[21] = GAMMA_NEG_X_0; 650 pairings[22] = GAMMA_NEG_Y_1; 651 pairings[23] = GAMMA_NEG_Y_0; 652 653 // Check pairing equation. 654 bool success; 655 uint256[1] memory output; 656 assembly ("memory-safe") { 657 success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x300, output, 0x20) 658 } 659 if (!success || output[0] != 1) { 660 // Either proof or verification key invalid. 661 // We assume the contract is correctly generated, so the verification key is valid. 662 revert ProofInvalid(); 663 } 664 } 665 } 666 667 /// Verify an uncompressed Groth16 proof. 668 /// @notice Reverts with InvalidProof if the proof is invalid or 669 /// with PublicInputNotInField the public input is not reduced. 670 /// @notice There is no return value. If the function does not revert, the 671 /// proof was successfully verified. 672 /// @param proof the points (A, B, C) in EIP-197 format matching the output 673 /// of compressProof. 674 {{- if gt $numCommitments 0 }} 675 /// @param commitments the Pedersen commitments from the proof. 676 /// @param commitmentPok the proof of knowledge for the Pedersen commitments. 677 {{- end }} 678 /// @param input the public input field elements in the scalar field Fr. 679 /// Elements must be reduced. 680 function verifyProof( 681 uint256[8] calldata proof, 682 {{- if gt $numCommitments 0}} 683 uint256[{{mul 2 $numCommitments}}] calldata commitments, 684 uint256[2] calldata commitmentPok, 685 {{- end }} 686 uint256[{{$numWitness}}] calldata input 687 ) public view { 688 {{- if eq $numCommitments 0 }} 689 (uint256 x, uint256 y) = publicInputMSM(input); 690 {{- else }} 691 // HashToField 692 uint256[{{$numCommitments}}] memory publicCommitments; 693 uint256[] memory publicAndCommitmentCommitted; 694 {{- range $i := intRange $numCommitments }} 695 {{- $pcIndex := index $PublicAndCommitmentCommitted $i }} 696 {{- if gt (len $pcIndex) 0 }} 697 publicAndCommitmentCommitted = new uint256[]({{(len $pcIndex)}}); 698 assembly ("memory-safe") { 699 let publicAndCommitmentCommittedOffset := add(publicAndCommitmentCommitted, 0x20) 700 {{- $segment_start := index $pcIndex 0 }} 701 {{- $segment_end := index $pcIndex 0 }} 702 {{- $l := 0 }} 703 {{- range $k := intRange (sub (len $pcIndex) 1) }} 704 {{- $next := index $pcIndex (sum $k 1) }} 705 {{- if ne $next (sum $segment_end 1) }} 706 calldatacopy(add(publicAndCommitmentCommittedOffset, {{mul $l 0x20}}), add(input, {{mul 0x20 (sub $segment_start 1)}}), {{mul 0x20 (sum 1 (sub $segment_end $segment_start))}}) 707 {{- $segment_start = $next }} 708 {{- $l = (sum $k 1) }} 709 {{- end }} 710 {{- $segment_end = $next }} 711 {{- end }} 712 calldatacopy(add(publicAndCommitmentCommittedOffset, {{mul $l 0x20}}), add(input, {{mul 0x20 (sub $segment_start 1)}}), {{mul 0x20 (sum 1 (sub $segment_end $segment_start))}}) 713 } 714 {{- end }} 715 716 publicCommitments[{{$i}}] = uint256( 717 {{ hashFnName }}( 718 abi.encodePacked( 719 commitments[{{mul $i 2}}], 720 commitments[{{sum (mul $i 2) 1}}], 721 publicAndCommitmentCommitted 722 ) 723 ) 724 ) % R; 725 {{- end }} 726 727 // Verify pedersen commitments 728 bool success; 729 assembly ("memory-safe") { 730 let f := mload(0x40) 731 732 calldatacopy(f, commitments, 0x40) // Copy Commitments 733 mstore(add(f, 0x40), PEDERSEN_GSIGMA_X_1) 734 mstore(add(f, 0x60), PEDERSEN_GSIGMA_X_0) 735 mstore(add(f, 0x80), PEDERSEN_GSIGMA_Y_1) 736 mstore(add(f, 0xa0), PEDERSEN_GSIGMA_Y_0) 737 calldatacopy(add(f, 0xc0), commitmentPok, 0x40) 738 mstore(add(f, 0x100), PEDERSEN_G_X_1) 739 mstore(add(f, 0x120), PEDERSEN_G_X_0) 740 mstore(add(f, 0x140), PEDERSEN_G_Y_1) 741 mstore(add(f, 0x160), PEDERSEN_G_Y_0) 742 743 success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x180, f, 0x20) 744 success := and(success, mload(f)) 745 } 746 if (!success) { 747 revert CommitmentInvalid(); 748 } 749 750 (uint256 x, uint256 y) = publicInputMSM( 751 input, 752 publicCommitments, 753 commitments 754 ); 755 {{- end }} 756 757 // Note: The precompile expects the F2 coefficients in big-endian order. 758 // Note: The pairing precompile rejects unreduced values, so we won't check that here. 759 760 {{- if eq $numCommitments 0 }} 761 bool success; 762 {{- end }} 763 assembly ("memory-safe") { 764 let f := mload(0x40) // Free memory pointer. 765 766 // Copy points (A, B, C) to memory. They are already in correct encoding. 767 // This is pairing e(A, B) and G1 of e(C, -δ). 768 calldatacopy(f, proof, 0x100) 769 770 // Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory. 771 // OPT: This could be better done using a single codecopy, but 772 // Solidity (unlike standalone Yul) doesn't provide a way to 773 // to do this. 774 mstore(add(f, 0x100), DELTA_NEG_X_1) 775 mstore(add(f, 0x120), DELTA_NEG_X_0) 776 mstore(add(f, 0x140), DELTA_NEG_Y_1) 777 mstore(add(f, 0x160), DELTA_NEG_Y_0) 778 mstore(add(f, 0x180), ALPHA_X) 779 mstore(add(f, 0x1a0), ALPHA_Y) 780 mstore(add(f, 0x1c0), BETA_NEG_X_1) 781 mstore(add(f, 0x1e0), BETA_NEG_X_0) 782 mstore(add(f, 0x200), BETA_NEG_Y_1) 783 mstore(add(f, 0x220), BETA_NEG_Y_0) 784 mstore(add(f, 0x240), x) 785 mstore(add(f, 0x260), y) 786 mstore(add(f, 0x280), GAMMA_NEG_X_1) 787 mstore(add(f, 0x2a0), GAMMA_NEG_X_0) 788 mstore(add(f, 0x2c0), GAMMA_NEG_Y_1) 789 mstore(add(f, 0x2e0), GAMMA_NEG_Y_0) 790 791 // Check pairing equation. 792 success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x300, f, 0x20) 793 // Also check returned value (both are either 1 or 0). 794 success := and(success, mload(f)) 795 } 796 if (!success) { 797 // Either proof or verification key invalid. 798 // We assume the contract is correctly generated, so the verification key is valid. 799 revert ProofInvalid(); 800 } 801 } 802 } 803 ` 804 805 // MarshalSolidity converts a proof to a byte array that can be used in a 806 // Solidity contract. 807 func (proof *Proof) MarshalSolidity() []byte { 808 var buf bytes.Buffer 809 _, err := proof.WriteRawTo(&buf) 810 if err != nil { 811 panic(err) 812 } 813 814 // If there are no commitments, we can return only Ar | Bs | Krs 815 if len(proof.Commitments) > 0 { 816 return buf.Bytes() 817 } else { 818 return buf.Bytes()[:8*fr.Bytes] 819 } 820 }