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  }