From 88bdb5d7461c2bdc78d84ac51b8f024068270a4f Mon Sep 17 00:00:00 2001 From: John Guibas Date: Tue, 31 Dec 2024 10:52:20 -0800 Subject: [PATCH] add v4 contracts --- .../v4.0.0-rc.3/SP1VerifierGroth16.s.sol | 38 + .../deploy/v4.0.0-rc.3/SP1VerifierPlonk.s.sol | 38 + contracts/src/v4.0.0-rc.3/Groth16Verifier.sol | 540 +++++++ contracts/src/v4.0.0-rc.3/PlonkVerifier.sol | 1341 +++++++++++++++++ .../src/v4.0.0-rc.3/SP1VerifierGroth16.sol | 59 + .../src/v4.0.0-rc.3/SP1VerifierPlonk.sol | 61 + 6 files changed, 2077 insertions(+) create mode 100644 contracts/script/deploy/v4.0.0-rc.3/SP1VerifierGroth16.s.sol create mode 100644 contracts/script/deploy/v4.0.0-rc.3/SP1VerifierPlonk.s.sol create mode 100644 contracts/src/v4.0.0-rc.3/Groth16Verifier.sol create mode 100644 contracts/src/v4.0.0-rc.3/PlonkVerifier.sol create mode 100644 contracts/src/v4.0.0-rc.3/SP1VerifierGroth16.sol create mode 100644 contracts/src/v4.0.0-rc.3/SP1VerifierPlonk.sol diff --git a/contracts/script/deploy/v4.0.0-rc.3/SP1VerifierGroth16.s.sol b/contracts/script/deploy/v4.0.0-rc.3/SP1VerifierGroth16.s.sol new file mode 100644 index 0000000..9042dfa --- /dev/null +++ b/contracts/script/deploy/v4.0.0-rc.3/SP1VerifierGroth16.s.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {BaseScript} from "../../utils/Base.s.sol"; +import {SP1Verifier} from "../../../src/v4.0.0-rc.3/SP1VerifierGroth16.sol"; +import {SP1VerifierGateway} from "../../../src/SP1VerifierGateway.sol"; +import {ISP1VerifierWithHash} from "../../../src/ISP1Verifier.sol"; + +contract SP1VerifierScript is BaseScript { + string internal constant KEY = "V4_0_0_RC_3_SP1_VERIFIER_GROTH16"; + + function run() external multichain(KEY) broadcaster { + // Read config + bytes32 CREATE2_SALT = readBytes32("CREATE2_SALT"); + address SP1_VERIFIER_GATEWAY = readAddress("SP1_VERIFIER_GATEWAY_GROTH16"); + + // Deploy contract + address verifier = address(new SP1Verifier{salt: CREATE2_SALT}()); + + // Add the verifier to the gateway + SP1VerifierGateway gateway = SP1VerifierGateway(SP1_VERIFIER_GATEWAY); + gateway.addRoute(verifier); + + // Write address + writeAddress(KEY, verifier); + } + + function freeze() external multichain(KEY) broadcaster { + // Read config + address SP1_VERIFIER_GATEWAY = readAddress("SP1_VERIFIER_GATEWAY_GROTH16"); + address SP1_VERIFIER = readAddress(KEY); + + // Freeze the verifier on the gateway + SP1VerifierGateway gateway = SP1VerifierGateway(SP1_VERIFIER_GATEWAY); + bytes4 selector = bytes4(ISP1VerifierWithHash(SP1_VERIFIER).VERIFIER_HASH()); + gateway.freezeRoute(selector); + } +} diff --git a/contracts/script/deploy/v4.0.0-rc.3/SP1VerifierPlonk.s.sol b/contracts/script/deploy/v4.0.0-rc.3/SP1VerifierPlonk.s.sol new file mode 100644 index 0000000..436b719 --- /dev/null +++ b/contracts/script/deploy/v4.0.0-rc.3/SP1VerifierPlonk.s.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {BaseScript} from "../../utils/Base.s.sol"; +import {SP1Verifier} from "../../../src/v4.0.0-rc.3/SP1VerifierPlonk.sol"; +import {SP1VerifierGateway} from "../../../src/SP1VerifierGateway.sol"; +import {ISP1VerifierWithHash} from "../../../src/ISP1Verifier.sol"; + +contract SP1VerifierScript is BaseScript { + string internal constant KEY = "V4_0_0_RC_3_SP1_VERIFIER_PLONK"; + + function run() external multichain(KEY) broadcaster { + // Read config + bytes32 CREATE2_SALT = readBytes32("CREATE2_SALT"); + address SP1_VERIFIER_GATEWAY = readAddress("SP1_VERIFIER_GATEWAY_PLONK"); + + // Deploy contract + address verifier = address(new SP1Verifier{salt: CREATE2_SALT}()); + + // Add the verifier to the gateway + SP1VerifierGateway gateway = SP1VerifierGateway(SP1_VERIFIER_GATEWAY); + gateway.addRoute(verifier); + + // Write address + writeAddress(KEY, verifier); + } + + function freeze() external multichain(KEY) broadcaster { + // Read config + address SP1_VERIFIER_GATEWAY = readAddress("SP1_VERIFIER_GATEWAY_PLONK"); + address SP1_VERIFIER = readAddress(KEY); + + // Freeze the verifier on the gateway + SP1VerifierGateway gateway = SP1VerifierGateway(SP1_VERIFIER_GATEWAY); + bytes4 selector = bytes4(ISP1VerifierWithHash(SP1_VERIFIER).VERIFIER_HASH()); + gateway.freezeRoute(selector); + } +} diff --git a/contracts/src/v4.0.0-rc.3/Groth16Verifier.sol b/contracts/src/v4.0.0-rc.3/Groth16Verifier.sol new file mode 100644 index 0000000..f354d4f --- /dev/null +++ b/contracts/src/v4.0.0-rc.3/Groth16Verifier.sol @@ -0,0 +1,540 @@ + +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +/// @title Groth16 verifier template. +/// @author Remco Bloemen +/// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed +/// (256 bytes) and compressed (128 bytes) format. A view function is provided +/// to compress proofs. +/// @notice See for further explanation. +contract Groth16Verifier { + + /// Some of the provided public input values are larger than the field modulus. + /// @dev Public input elements are not automatically reduced, as this is can be + /// a dangerous source of bugs. + error PublicInputNotInField(); + + /// The proof is invalid. + /// @dev This can mean that provided Groth16 proof points are not on their + /// curves, that pairing equation fails, or that the proof is not for the + /// provided public input. + error ProofInvalid(); + + // Addresses of precompiles + uint256 constant PRECOMPILE_MODEXP = 0x05; + uint256 constant PRECOMPILE_ADD = 0x06; + uint256 constant PRECOMPILE_MUL = 0x07; + uint256 constant PRECOMPILE_VERIFY = 0x08; + + // Base field Fp order P and scalar field Fr order R. + // For BN254 these are computed as follows: + // t = 4965661367192848881 + // P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1 + // R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1 + uint256 constant P = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 constant R = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + + // Extension field Fp2 = Fp[i] / (i² + 1) + // Note: This is the complex extension field of Fp with i² = -1. + // Values in Fp2 are represented as a pair of Fp elements (a₀, a₁) as a₀ + a₁⋅i. + // Note: The order of Fp2 elements is *opposite* that of the pairing contract, which + // expects Fp2 elements in order (a₁, a₀). This is also the order in which + // Fp2 elements are encoded in the public interface as this became convention. + + // Constants in Fp + uint256 constant FRACTION_1_2_FP = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + + // Exponents for inversions and square roots mod P + uint256 constant EXP_INVERSE_FP = 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; + + // Groth16 alpha point in G1 + uint256 constant ALPHA_X = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant ALPHA_Y = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + + // Groth16 beta point in G2 in powers of i + uint256 constant BETA_NEG_X_0 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant BETA_NEG_X_1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant BETA_NEG_Y_0 = 11383000245469012944693504663162918391286475477077232690815866754273895001727; + uint256 constant BETA_NEG_Y_1 = 41207766310529818958173054109690360505148424997958324311878202295167071904; + + // Groth16 gamma point in G2 in powers of i + uint256 constant GAMMA_NEG_X_0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant GAMMA_NEG_X_1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant GAMMA_NEG_Y_0 = 13392588948715843804641432497768002650278120570034223513918757245338268106653; + uint256 constant GAMMA_NEG_Y_1 = 17805874995975841540914202342111839520379459829704422454583296818431106115052; + + // Groth16 delta point in G2 in powers of i + uint256 constant DELTA_NEG_X_0 = 19629295988673812457237747993086053613709181874324227239033635205670891327060; + uint256 constant DELTA_NEG_X_1 = 17270349666695681994109533429817368669497520119106681015856196115021033411091; + uint256 constant DELTA_NEG_Y_0 = 14281790459332470419125837541415772351574094165485379719795056490664770278727; + uint256 constant DELTA_NEG_Y_1 = 12217031863885588059779845498016696484811402332435719653934590968575679828494; + + // Constant and public input points + uint256 constant CONSTANT_X = 6712036353136249806951869451908368653566549662781372756321174254690599374583; + uint256 constant CONSTANT_Y = 18149145036868871064182651529802275370638950642742152190925800889169295968585; + uint256 constant PUB_0_X = 12384021290558951773126140100379496012525836638155233096890881157449062205923; + uint256 constant PUB_0_Y = 16530732960917040406371332977337573092100509754908292717547628595948196259098; + uint256 constant PUB_1_X = 18749839173537272836199384751191600551090725238737491530604969678014545165197; + uint256 constant PUB_1_Y = 1828450848853234449784725988911172793808451038026258152543319358376349553777; + + /// Negation in Fp. + /// @notice Returns a number x such that a + x = 0 in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @return x the result + function negate(uint256 a) internal pure returns (uint256 x) { + unchecked { + x = (P - (a % P)) % P; // Modulo is cheaper than branching + } + } + + /// Exponentiation in Fp. + /// @notice Returns a number x such that a ^ e = x in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @param e the exponent + /// @return x the result + function exp(uint256 a, uint256 e) internal view returns (uint256 x) { + bool success; + assembly ("memory-safe") { + let f := mload(0x40) + mstore(f, 0x20) + mstore(add(f, 0x20), 0x20) + mstore(add(f, 0x40), 0x20) + mstore(add(f, 0x60), a) + mstore(add(f, 0x80), e) + mstore(add(f, 0xa0), P) + success := staticcall(gas(), PRECOMPILE_MODEXP, f, 0xc0, f, 0x20) + x := mload(f) + } + if (!success) { + // Exponentiation failed. + // Should not happen. + revert ProofInvalid(); + } + } + + /// Invertsion in Fp. + /// @notice Returns a number x such that a * x = 1 in Fp. + /// @notice The input does not need to be reduced. + /// @notice Reverts with ProofInvalid() if the inverse does not exist + /// @param a the input + /// @return x the solution + function invert_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_INVERSE_FP); + if (mulmod(a, x, P) != 1) { + // Inverse does not exist. + // Can only happen during G2 point decompression. + revert ProofInvalid(); + } + } + + /// Square root in Fp. + /// @notice Returns a number x such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function sqrt_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_SQRT_FP); + if (mulmod(x, x, P) != a) { + // Square root does not exist or a is not reduced. + // Happens when G1 point is not on curve. + revert ProofInvalid(); + } + } + + /// Square test in Fp. + /// @notice Returns whether a number x exists such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function isSquare_Fp(uint256 a) internal view returns (bool) { + uint256 x = exp(a, EXP_SQRT_FP); + return mulmod(x, x, P) == a; + } + + /// Square root in Fp2. + /// @notice Fp2 is the complex extension Fp[i]/(i^2 + 1). The input is + /// a0 + a1 ⋅ i and the result is x0 + x1 ⋅ i. + /// @notice Will revert with InvalidProof() if + /// * the input is not a square, + /// * the hint is incorrect, or + /// * the input coefficents are not reduced. + /// @param a0 The real part of the input. + /// @param a1 The imaginary part of the input. + /// @param hint A hint which of two possible signs to pick in the equation. + /// @return x0 The real part of the square root. + /// @return x1 The imaginary part of the square root. + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) internal view returns (uint256 x0, uint256 x1) { + // If this square root reverts there is no solution in Fp2. + uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); + if (hint) { + d = negate(d); + } + // If this square root reverts there is no solution in Fp2. + x0 = sqrt_Fp(mulmod(addmod(a0, d, P), FRACTION_1_2_FP, P)); + x1 = mulmod(a1, invert_Fp(mulmod(x0, 2, P)), P); + + // Check result to make sure we found a root. + // Note: this also fails if a0 or a1 is not reduced. + if (a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P)) { + revert ProofInvalid(); + } + } + + /// Compress a G1 point. + /// @notice Reverts with InvalidProof if the coordinates are not reduced + /// or if the point is not on the curve. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param x The X coordinate in Fp. + /// @param y The Y coordinate in Fp. + /// @return c The compresed point (x with one signal bit). + function compress_g1(uint256 x, uint256 y) internal view returns (uint256 c) { + if (x >= P || y >= P) { + // G1 point not in field. + revert ProofInvalid(); + } + if (x == 0 && y == 0) { + // Point at infinity + return 0; + } + + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. + uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (y == y_pos) { + return (x << 1) | 0; + } else if (y == negate(y_pos)) { + return (x << 1) | 1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } + } + + /// Decompress a G1 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param c The compresed point (x with one signal bit). + /// @return x The X coordinate in Fp. + /// @return y The Y coordinate in Fp. + function decompress_g1(uint256 c) internal view returns (uint256 x, uint256 y) { + // Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square. + // so we can use it to represent the point at infinity. + if (c == 0) { + // Point at infinity as encoded in EIP196 and EIP197. + return (0, 0); + } + bool negate_point = c & 1 == 1; + x = c >> 1; + if (x >= P) { + // G1 x coordinate not in field. + revert ProofInvalid(); + } + + // Note: (x³ + 3) is irreducible in Fp, so it can not be zero and therefore + // y can not be zero. + // Note: sqrt_Fp reverts if there is no solution, i.e. the point is not on the curve. + y = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (negate_point) { + y = negate(y); + } + } + + /// Compress a G2 point. + /// @notice Reverts with InvalidProof if the coefficients are not reduced + /// or if the point is not on the curve. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param x0 The real part of the X coordinate. + /// @param x1 The imaginary poart of the X coordinate. + /// @param y0 The real part of the Y coordinate. + /// @param y1 The imaginary part of the Y coordinate. + /// @return c0 The first half of the compresed point (x0 with two signal bits). + /// @return c1 The second half of the compressed point (x1 unmodified). + function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) + internal view returns (uint256 c0, uint256 c1) { + if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { + // G2 point not in field. + revert ProofInvalid(); + } + if ((x0 | x1 | y0 | y1) == 0) { + // Point at infinity + return (0, 0); + } + + // Compute y^2 + // Note: shadowing variables and scoping to avoid stack-to-deep. + uint256 y0_pos; + uint256 y1_pos; + { + uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); + y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + } + + // Determine hint bit + // If this sqrt fails the x coordinate is not on the curve. + bool hint; + { + uint256 d = sqrt_Fp(addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P)); + hint = !isSquare_Fp(mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P)); + } + + // Recover y + (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); + if (y0 == y0_pos && y1 == y1_pos) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c1 = x1; + } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c1 = x1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } + } + + /// Decompress a G2 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param c0 The first half of the compresed point (x0 with two signal bits). + /// @param c1 The second half of the compressed point (x1 unmodified). + /// @return x0 The real part of the X coordinate. + /// @return x1 The imaginary poart of the X coordinate. + /// @return y0 The real part of the Y coordinate. + /// @return y1 The imaginary part of the Y coordinate. + function decompress_g2(uint256 c0, uint256 c1) + internal view returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) { + // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. + // so we can use it to represent the point at infinity. + if (c0 == 0 && c1 == 0) { + // Point at infinity as encoded in EIP197. + return (0, 0, 0, 0); + } + bool negate_point = c0 & 1 == 1; + bool hint = c0 & 2 == 2; + x0 = c0 >> 2; + x1 = c1; + if (x0 >= P || x1 >= P) { + // G2 x0 or x1 coefficient not in field. + revert ProofInvalid(); + } + + uint256 n3ab = mulmod(mulmod(x0, x1, P), P-3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); + + y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + + // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. + // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. + // But y0 or y1 may still independently be zero. + (y0, y1) = sqrt_Fp2(y0, y1, hint); + if (negate_point) { + y0 = negate(y0); + y1 = negate(y1); + } + } + + /// Compute the public input linear combination. + /// @notice Reverts with PublicInputNotInField if the input is not in the field. + /// @notice Computes the multi-scalar-multiplication of the public input + /// elements and the verification key including the constant term. + /// @param input The public inputs. These are elements of the scalar field Fr. + /// @return x The X coordinate of the resulting G1 point. + /// @return y The Y coordinate of the resulting G1 point. + function publicInputMSM(uint256[2] calldata input) + internal view returns (uint256 x, uint256 y) { + // Note: The ECMUL precompile does not reject unreduced values, so we check this. + // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the + // code-size is in the PUB_ constants. + // ECMUL has input (x, y, scalar) and output (x', y'). + // ECADD has input (x1, y1, x2, y2) and output (x', y'). + // We reduce commitments(if any) with constants as the first point argument to ECADD. + // We call them such that ecmul output is already in the second point + // argument to ECADD so we can have a tight loop. + bool success = true; + assembly ("memory-safe") { + let f := mload(0x40) + let g := add(f, 0x40) + let s + mstore(f, CONSTANT_X) + mstore(add(f, 0x20), CONSTANT_Y) + mstore(g, PUB_0_X) + mstore(add(g, 0x20), PUB_0_Y) + s := calldataload(input) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_1_X) + mstore(add(g, 0x20), PUB_1_Y) + s := calldataload(add(input, 32)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + + x := mload(f) + y := mload(add(f, 0x20)) + } + if (!success) { + // Either Public input not in field, or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert PublicInputNotInField(); + } + } + + /// Compress a proof. + /// @notice Will revert with InvalidProof if the curve points are invalid, + /// but does not verify the proof itself. + /// @param proof The uncompressed Groth16 proof. Elements are in the same order as for + /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197. + /// @return compressed The compressed proof. Elements are in the same order as for + /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. + function compressProof(uint256[8] calldata proof) + public view returns (uint256[4] memory compressed) { + compressed[0] = compress_g1(proof[0], proof[1]); + (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); + compressed[3] = compress_g1(proof[6], proof[7]); + } + + /// Verify a Groth16 proof with compressed points. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was successfully verified. + /// @param compressedProof the points (A, B, C) in compressed format + /// matching the output of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyCompressedProof( + uint256[4] calldata compressedProof, + uint256[2] calldata input + ) public view { + uint256[24] memory pairings; + + { + (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = decompress_g2(compressedProof[2], compressedProof[1]); + (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); + (uint256 Lx, uint256 Ly) = publicInputMSM(input); + + // Verify the pairing + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + // e(A, B) + pairings[ 0] = Ax; + pairings[ 1] = Ay; + pairings[ 2] = Bx1; + pairings[ 3] = Bx0; + pairings[ 4] = By1; + pairings[ 5] = By0; + // e(C, -δ) + pairings[ 6] = Cx; + pairings[ 7] = Cy; + pairings[ 8] = DELTA_NEG_X_1; + pairings[ 9] = DELTA_NEG_X_0; + pairings[10] = DELTA_NEG_Y_1; + pairings[11] = DELTA_NEG_Y_0; + // e(α, -β) + pairings[12] = ALPHA_X; + pairings[13] = ALPHA_Y; + pairings[14] = BETA_NEG_X_1; + pairings[15] = BETA_NEG_X_0; + pairings[16] = BETA_NEG_Y_1; + pairings[17] = BETA_NEG_Y_0; + // e(L_pub, -γ) + pairings[18] = Lx; + pairings[19] = Ly; + pairings[20] = GAMMA_NEG_X_1; + pairings[21] = GAMMA_NEG_X_0; + pairings[22] = GAMMA_NEG_Y_1; + pairings[23] = GAMMA_NEG_Y_0; + + // Check pairing equation. + bool success; + uint256[1] memory output; + assembly ("memory-safe") { + success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x300, output, 0x20) + } + if (!success || output[0] != 1) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } + } + } + + /// Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was successfully verified. + /// @param proof the points (A, B, C) in EIP-197 format matching the output + /// of compressProof. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function Verify( + uint256[8] calldata proof, + uint256[2] calldata input + ) public view { + (uint256 x, uint256 y) = publicInputMSM(input); + + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + bool success; + assembly ("memory-safe") { + let f := mload(0x40) // Free memory pointer. + + // Copy points (A, B, C) to memory. They are already in correct encoding. + // This is pairing e(A, B) and G1 of e(C, -δ). + calldatacopy(f, proof, 0x100) + + // Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory. + // OPT: This could be better done using a single codecopy, but + // Solidity (unlike standalone Yul) doesn't provide a way to + // to do this. + mstore(add(f, 0x100), DELTA_NEG_X_1) + mstore(add(f, 0x120), DELTA_NEG_X_0) + mstore(add(f, 0x140), DELTA_NEG_Y_1) + mstore(add(f, 0x160), DELTA_NEG_Y_0) + mstore(add(f, 0x180), ALPHA_X) + mstore(add(f, 0x1a0), ALPHA_Y) + mstore(add(f, 0x1c0), BETA_NEG_X_1) + mstore(add(f, 0x1e0), BETA_NEG_X_0) + mstore(add(f, 0x200), BETA_NEG_Y_1) + mstore(add(f, 0x220), BETA_NEG_Y_0) + mstore(add(f, 0x240), x) + mstore(add(f, 0x260), y) + mstore(add(f, 0x280), GAMMA_NEG_X_1) + mstore(add(f, 0x2a0), GAMMA_NEG_X_0) + mstore(add(f, 0x2c0), GAMMA_NEG_Y_1) + mstore(add(f, 0x2e0), GAMMA_NEG_Y_0) + + // Check pairing equation. + success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x300, f, 0x20) + // Also check returned value (both are either 1 or 0). + success := and(success, mload(f)) + } + if (!success) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } + } +} diff --git a/contracts/src/v4.0.0-rc.3/PlonkVerifier.sol b/contracts/src/v4.0.0-rc.3/PlonkVerifier.sol new file mode 100644 index 0000000..e8ed75c --- /dev/null +++ b/contracts/src/v4.0.0-rc.3/PlonkVerifier.sol @@ -0,0 +1,1341 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 2023 Consensys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by gnark DO NOT EDIT + +pragma solidity ^0.8.0; + +contract PlonkVerifier { + + uint256 private constant R_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + uint256 private constant R_MOD_MINUS_ONE = 21888242871839275222246405745257275088548364400416034343698204186575808495616; + uint256 private constant P_MOD = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + uint256 private constant G2_SRS_0_X_0 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 private constant G2_SRS_0_X_1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 private constant G2_SRS_0_Y_0 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 private constant G2_SRS_0_Y_1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + + uint256 private constant G2_SRS_1_X_0 = 15805639136721018565402881920352193254830339253282065586954346329754995870280; + uint256 private constant G2_SRS_1_X_1 = 19089565590083334368588890253123139704298730990782503769911324779715431555531; + uint256 private constant G2_SRS_1_Y_0 = 9779648407879205346559610309258181044130619080926897934572699915909528404984; + uint256 private constant G2_SRS_1_Y_1 = 6779728121489434657638426458390319301070371227460768374343986326751507916979; + + uint256 private constant G1_SRS_X = 14312776538779914388377568895031746459131577658076416373430523308756343304251; + uint256 private constant G1_SRS_Y = 11763105256161367503191792604679297387056316997144156930871823008787082098465; + + // ----------------------- vk --------------------- + uint256 private constant VK_NB_PUBLIC_INPUTS = 2; + uint256 private constant VK_DOMAIN_SIZE = 16777216; + uint256 private constant VK_INV_DOMAIN_SIZE = 21888241567198334088790460357988866238279339518792980768180410072331574733841; + uint256 private constant VK_OMEGA = 5709868443893258075976348696661355716898495876243883251619397131511003808859; + uint256 private constant VK_QL_COM_X = 17984004271810887111892332882651374483612811613160171882231530673955342124072; + uint256 private constant VK_QL_COM_Y = 13277994607875064672295433418223618455646015691912141325106751561841646085822; + uint256 private constant VK_QR_COM_X = 16336286973672788525637509231329701034039020185740400021577697914995487271901; + uint256 private constant VK_QR_COM_Y = 88284377293612431075210969180485407669835075356048788125030179865751136579; + uint256 private constant VK_QM_COM_X = 10856514239855788242760873537198968126773773371401212930763195728908952457266; + uint256 private constant VK_QM_COM_Y = 9275209459688740642101488227886989724826404626746083670876354123229317070279; + uint256 private constant VK_QO_COM_X = 11483266664351462882096699260743761962335837409017508503826409502538996076321; + uint256 private constant VK_QO_COM_Y = 17394959804451514544078291653193154652690985857122468223674247897665667008225; + uint256 private constant VK_QK_COM_X = 6378083169737830823085597784511445554718388062388953364429190548392020833332; + uint256 private constant VK_QK_COM_Y = 12423874058816725405347015291038805106767729770869142043443023930694783565135; + + uint256 private constant VK_S1_COM_X = 8432363989348148399267595343996871949745756313266171098803972147558566645391; + uint256 private constant VK_S1_COM_Y = 21830041218908693046173167152196424202345484683169853420596524951634570306585; + + uint256 private constant VK_S2_COM_X = 2889872191603241225798623124219684248629415321428218534303823116357979417612; + uint256 private constant VK_S2_COM_Y = 8446598484540146291961338772594737249603462456099116591415979984815127389111; + + uint256 private constant VK_S3_COM_X = 12894021857927799668653295424700631150139887020229360316861211114803005826079; + uint256 private constant VK_S3_COM_Y = 5332155988317853016248818837542073627058808403298822656476772250044723938116; + + uint256 private constant VK_COSET_SHIFT = 5; + + + uint256 private constant VK_QCP_0_X = 16113316688614670925300831064391815772431716488200414163545127725991745431527; + uint256 private constant VK_QCP_0_Y = 18613967203673816695007264416375047840688367875268537453854528850830700989951; + + + uint256 private constant VK_INDEX_COMMIT_API_0 = 10853426; + uint256 private constant VK_NB_CUSTOM_GATES = 1; + + // ------------------------------------------------ + + // size of the proof without call custom gate + uint256 private constant FIXED_PROOF_SIZE = 0x300; + + // offset proof + + uint256 private constant PROOF_L_COM_X = 0x0; + uint256 private constant PROOF_L_COM_Y = 0x20; + uint256 private constant PROOF_R_COM_X = 0x40; + uint256 private constant PROOF_R_COM_Y = 0x60; + uint256 private constant PROOF_O_COM_X = 0x80; + uint256 private constant PROOF_O_COM_Y = 0xa0; + + // h = h_0 + x^{n+2}h_1 + x^{2(n+2)}h_2 + uint256 private constant PROOF_H_0_COM_X = 0xc0; + uint256 private constant PROOF_H_0_COM_Y = 0xe0; + uint256 private constant PROOF_H_1_COM_X = 0x100; + uint256 private constant PROOF_H_1_COM_Y = 0x120; + uint256 private constant PROOF_H_2_COM_X = 0x140; + uint256 private constant PROOF_H_2_COM_Y = 0x160; + + // "evaluations of wire polynomials at zeta + uint256 private constant PROOF_L_AT_ZETA = 0x180; + uint256 private constant PROOF_R_AT_ZETA = 0x1a0; + uint256 private constant PROOF_O_AT_ZETA = 0x1c0; + + // S1(zeta),S2(zeta) + uint256 private constant PROOF_S1_AT_ZETA = 0x1e0; // Sσ1(zeta) + uint256 private constant PROOF_S2_AT_ZETA = 0x200; // Sσ2(zeta) + + // [Z] + uint256 private constant PROOF_GRAND_PRODUCT_COMMITMENT_X = 0x220; + uint256 private constant PROOF_GRAND_PRODUCT_COMMITMENT_Y = 0x240; + + uint256 private constant PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA = 0x260; // z(w*zeta) + + // Folded proof for the opening of linearised poly, l, r, o, s_1, s_2, qcp + uint256 private constant PROOF_BATCH_OPENING_AT_ZETA_X = 0x280; + uint256 private constant PROOF_BATCH_OPENING_AT_ZETA_Y = 0x2a0; + + uint256 private constant PROOF_OPENING_AT_ZETA_OMEGA_X = 0x2c0; + uint256 private constant PROOF_OPENING_AT_ZETA_OMEGA_Y = 0x2e0; + + uint256 private constant PROOF_OPENING_QCP_AT_ZETA = 0x300; + uint256 private constant PROOF_BSB_COMMITMENTS = 0x320; + + // -------- offset state + + // challenges to check the claimed quotient + + uint256 private constant STATE_ALPHA = 0x0; + uint256 private constant STATE_BETA = 0x20; + uint256 private constant STATE_GAMMA = 0x40; + uint256 private constant STATE_ZETA = 0x60; + uint256 private constant STATE_ALPHA_SQUARE_LAGRANGE_0 = 0x80; + uint256 private constant STATE_FOLDED_H_X = 0xa0; + uint256 private constant STATE_FOLDED_H_Y = 0xc0; + uint256 private constant STATE_LINEARISED_POLYNOMIAL_X = 0xe0; + uint256 private constant STATE_LINEARISED_POLYNOMIAL_Y = 0x100; + uint256 private constant STATE_OPENING_LINEARISED_POLYNOMIAL_ZETA = 0x120; + uint256 private constant STATE_FOLDED_CLAIMED_VALUES = 0x140; // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp + uint256 private constant STATE_FOLDED_DIGESTS_X = 0x160; // linearised poly, l, r, o, s_1, s_2, qcp + uint256 private constant STATE_FOLDED_DIGESTS_Y = 0x180; + uint256 private constant STATE_PI = 0x1a0; + uint256 private constant STATE_ZETA_POWER_N_MINUS_ONE = 0x1c0; + uint256 private constant STATE_GAMMA_KZG = 0x1e0; + uint256 private constant STATE_SUCCESS = 0x200; + uint256 private constant STATE_CHECK_VAR = 0x220; // /!\ this slot is used for debugging only + uint256 private constant STATE_LAST_MEM = 0x240; + + // -------- utils (for Fiat Shamir) + uint256 private constant FS_ALPHA = 0x616C706861; // "alpha" + uint256 private constant FS_BETA = 0x62657461; // "beta" + uint256 private constant FS_GAMMA = 0x67616d6d61; // "gamma" + uint256 private constant FS_ZETA = 0x7a657461; // "zeta" + uint256 private constant FS_GAMMA_KZG = 0x67616d6d61; // "gamma" + + // -------- errors + uint256 private constant ERROR_STRING_ID = 0x08c379a000000000000000000000000000000000000000000000000000000000; // selector for function Error(string) + + + // -------- utils (for hash_fr) + uint256 private constant HASH_FR_BB = 340282366920938463463374607431768211456; // 2**128 + uint256 private constant HASH_FR_ZERO_UINT256 = 0; + uint8 private constant HASH_FR_LEN_IN_BYTES = 48; + uint8 private constant HASH_FR_SIZE_DOMAIN = 11; + uint8 private constant HASH_FR_ONE = 1; + uint8 private constant HASH_FR_TWO = 2; + + + // -------- precompiles + uint8 private constant SHA2 = 0x2; + uint8 private constant MOD_EXP = 0x5; + uint8 private constant EC_ADD = 0x6; + uint8 private constant EC_MUL = 0x7; + uint8 private constant EC_PAIR = 0x8; + + /// Verify a Plonk proof. + /// Reverts if the proof or the public inputs are malformed. + /// @param proof serialised plonk proof (using gnark's MarshalSolidity) + /// @param public_inputs (must be reduced) + /// @return success true if the proof passes false otherwise + function Verify(bytes calldata proof, uint256[] calldata public_inputs) + public view returns(bool success) { + + assembly { + + let mem := mload(0x40) + let freeMem := add(mem, STATE_LAST_MEM) + + // sanity checks + check_number_of_public_inputs(public_inputs.length) + check_inputs_size(public_inputs.length, public_inputs.offset) + check_proof_size(proof.length) + check_proof_openings_size(proof.offset) + + // compute the challenges + let prev_challenge_non_reduced + prev_challenge_non_reduced := derive_gamma(proof.offset, public_inputs.length, public_inputs.offset) + prev_challenge_non_reduced := derive_beta(prev_challenge_non_reduced) + prev_challenge_non_reduced := derive_alpha(proof.offset, prev_challenge_non_reduced) + derive_zeta(proof.offset, prev_challenge_non_reduced) + + // evaluation of Z=Xⁿ-1 at ζ, we save this value + let zeta := mload(add(mem, STATE_ZETA)) + let zeta_power_n_minus_one := addmod(pow(zeta, VK_DOMAIN_SIZE, freeMem), sub(R_MOD, 1), R_MOD) + mstore(add(mem, STATE_ZETA_POWER_N_MINUS_ONE), zeta_power_n_minus_one) + + // public inputs contribution + let l_pi := sum_pi_wo_api_commit(public_inputs.offset, public_inputs.length, freeMem) + let l_pi_commit := sum_pi_commit(proof.offset, public_inputs.length, freeMem) + l_pi := addmod(l_pi_commit, l_pi, R_MOD) + mstore(add(mem, STATE_PI), l_pi) + + compute_alpha_square_lagrange_0() + compute_opening_linearised_polynomial(proof.offset) + fold_h(proof.offset) + compute_commitment_linearised_polynomial(proof.offset) + compute_gamma_kzg(proof.offset) + fold_state(proof.offset) + batch_verify_multi_points(proof.offset) + + success := mload(add(mem, STATE_SUCCESS)) + + // Beginning errors ------------------------------------------------- + + function error_nb_public_inputs() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x1d) + mstore(add(ptError, 0x44), "wrong number of public inputs") + revert(ptError, 0x64) + } + + /// Called when an exponentiation mod r fails + function error_mod_exp() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0xc) + mstore(add(ptError, 0x44), "error mod exp") + revert(ptError, 0x64) + } + + /// Called when an operation on Bn254 fails + /// @dev for instance when calling EcMul on a point not on Bn254. + function error_ec_op() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x12) + mstore(add(ptError, 0x44), "error ec operation") + revert(ptError, 0x64) + } + + /// Called when one of the public inputs is not reduced. + function error_inputs_size() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x18) + mstore(add(ptError, 0x44), "inputs are bigger than r") + revert(ptError, 0x64) + } + + /// Called when the size proof is not as expected + /// @dev to avoid overflow attack for instance + function error_proof_size() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x10) + mstore(add(ptError, 0x44), "wrong proof size") + revert(ptError, 0x64) + } + + /// Called when one the openings is bigger than r + /// The openings are the claimed evalutions of a polynomial + /// in a Kzg proof. + function error_proof_openings_size() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x16) + mstore(add(ptError, 0x44), "openings bigger than r") + revert(ptError, 0x64) + } + + function error_pairing() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0xd) + mstore(add(ptError, 0x44), "error pairing") + revert(ptError, 0x64) + } + + function error_verify() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0xc) + mstore(add(ptError, 0x44), "error verify") + revert(ptError, 0x64) + } + + function error_random_generation() { + let ptError := mload(0x40) + mstore(ptError, ERROR_STRING_ID) // selector for function Error(string) + mstore(add(ptError, 0x4), 0x20) + mstore(add(ptError, 0x24), 0x14) + mstore(add(ptError, 0x44), "error random gen kzg") + revert(ptError, 0x64) + } + // end errors ------------------------------------------------- + + // Beginning checks ------------------------------------------------- + + /// @param s actual number of public inputs + function check_number_of_public_inputs(s) { + if iszero(eq(s, VK_NB_PUBLIC_INPUTS)) { + error_nb_public_inputs() + } + } + + /// Checks that the public inputs are < R_MOD. + /// @param s number of public inputs + /// @param p pointer to the public inputs array + function check_inputs_size(s, p) { + for {let i} lt(i, s) {i:=add(i,1)} + { + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_inputs_size() + } + p := add(p, 0x20) + } + } + + /// Checks if the proof is of the correct size + /// @param actual_proof_size size of the proof (not the expected size) + function check_proof_size(actual_proof_size) { + let expected_proof_size := add(FIXED_PROOF_SIZE, mul(VK_NB_CUSTOM_GATES,0x60)) + if iszero(eq(actual_proof_size, expected_proof_size)) { + error_proof_size() + } + } + + /// Checks if the multiple openings of the polynomials are < R_MOD. + /// @param aproof pointer to the beginning of the proof + /// @dev the 'a' prepending proof is to have a local name + function check_proof_openings_size(aproof) { + + // PROOF_L_AT_ZETA + let p := add(aproof, PROOF_L_AT_ZETA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_R_AT_ZETA + p := add(aproof, PROOF_R_AT_ZETA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_O_AT_ZETA + p := add(aproof, PROOF_O_AT_ZETA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_S1_AT_ZETA + p := add(aproof, PROOF_S1_AT_ZETA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_S2_AT_ZETA + p := add(aproof, PROOF_S2_AT_ZETA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA + p := add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA) + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + + // PROOF_OPENING_QCP_AT_ZETA + + p := add(aproof, PROOF_OPENING_QCP_AT_ZETA) + for {let i:=0} lt(i, VK_NB_CUSTOM_GATES) {i:=add(i,1)} + { + if gt(calldataload(p), R_MOD_MINUS_ONE) { + error_proof_openings_size() + } + p := add(p, 0x20) + } + + } + // end checks ------------------------------------------------- + + // Beginning challenges ------------------------------------------------- + + /// Derive gamma as Sha256() + /// @param aproof pointer to the proof + /// @param nb_pi number of public inputs + /// @param pi pointer to the array of public inputs + /// @return the challenge gamma, not reduced + /// @notice The transcript is the concatenation (in this order) of: + /// * the word "gamma" in ascii, equal to [0x67,0x61,0x6d, 0x6d, 0x61] and encoded as a uint256. + /// * the commitments to the permutation polynomials S1, S2, S3, where we concatenate the coordinates of those points + /// * the commitments of Ql, Qr, Qm, Qo, Qk + /// * the public inputs + /// * the commitments of the wires related to the custom gates (commitments_wires_commit_api) + /// * commitments to L, R, O (proof__com_) + /// The data described above is written starting at mPtr. "gamma" lies on 5 bytes, + /// and is encoded as a uint256 number n. In basis b = 256, the number looks like this + /// [0 0 0 .. 0x67 0x61 0x6d, 0x6d, 0x61]. The first non zero entry is at position 27=0x1b + /// Gamma reduced (the actual challenge) is stored at add(state, state_gamma) + function derive_gamma(aproof, nb_pi, pi)->gamma_not_reduced { + + let state := mload(0x40) + let mPtr := add(state, STATE_LAST_MEM) + + mstore(mPtr, FS_GAMMA) // "gamma" + + + mstore(add(mPtr, 0x20), VK_S1_COM_X) + mstore(add(mPtr, 0x40), VK_S1_COM_Y) + mstore(add(mPtr, 0x60), VK_S2_COM_X) + mstore(add(mPtr, 0x80), VK_S2_COM_Y) + mstore(add(mPtr, 0xa0), VK_S3_COM_X) + mstore(add(mPtr, 0xc0), VK_S3_COM_Y) + mstore(add(mPtr, 0xe0), VK_QL_COM_X) + mstore(add(mPtr, 0x100), VK_QL_COM_Y) + mstore(add(mPtr, 0x120), VK_QR_COM_X) + mstore(add(mPtr, 0x140), VK_QR_COM_Y) + mstore(add(mPtr, 0x160), VK_QM_COM_X) + mstore(add(mPtr, 0x180), VK_QM_COM_Y) + mstore(add(mPtr, 0x1a0), VK_QO_COM_X) + mstore(add(mPtr, 0x1c0), VK_QO_COM_Y) + mstore(add(mPtr, 0x1e0), VK_QK_COM_X) + mstore(add(mPtr, 0x200), VK_QK_COM_Y) + + mstore(add(mPtr, 0x220), VK_QCP_0_X) + mstore(add(mPtr, 0x240), VK_QCP_0_Y) + + // public inputs + let _mPtr := add(mPtr, 0x260) + let size_pi_in_bytes := mul(nb_pi, 0x20) + calldatacopy(_mPtr, pi, size_pi_in_bytes) + _mPtr := add(_mPtr, size_pi_in_bytes) + + // commitments to l, r, o + let size_commitments_lro_in_bytes := 0xc0 + calldatacopy(_mPtr, aproof, size_commitments_lro_in_bytes) + _mPtr := add(_mPtr, size_commitments_lro_in_bytes) + + // total size is : + // sizegamma(=0x5) + 11*64(=0x2c0) + // + nb_public_inputs*0x20 + // + nb_custom gates*0x40 + let size := add(0x2c5, size_pi_in_bytes) + + size := add(size, mul(VK_NB_CUSTOM_GATES, 0x40)) + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1b), size, mPtr, 0x20) //0x1b -> 000.."gamma" + if iszero(l_success) { + error_verify() + } + gamma_not_reduced := mload(mPtr) + mstore(add(state, STATE_GAMMA), mod(gamma_not_reduced, R_MOD)) + } + + /// derive beta as Sha256 + /// @param gamma_not_reduced the previous challenge (gamma) not reduced + /// @return beta_not_reduced the next challenge, beta, not reduced + /// @notice the transcript consists of the previous challenge only. + /// The reduced version of beta is stored at add(state, state_beta) + function derive_beta(gamma_not_reduced)->beta_not_reduced{ + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + + // beta + mstore(mPtr, FS_BETA) // "beta" + mstore(add(mPtr, 0x20), gamma_not_reduced) + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1c), 0x24, mPtr, 0x20) //0x1b -> 000.."gamma" + if iszero(l_success) { + error_verify() + } + beta_not_reduced := mload(mPtr) + mstore(add(state, STATE_BETA), mod(beta_not_reduced, R_MOD)) + } + + /// derive alpha as sha256 + /// @param aproof pointer to the proof object + /// @param beta_not_reduced the previous challenge (beta) not reduced + /// @return alpha_not_reduced the next challenge, alpha, not reduced + /// @notice the transcript consists of the previous challenge (beta) + /// not reduced, the commitments to the wires associated to the QCP_i, + /// and the commitment to the grand product polynomial + function derive_alpha(aproof, beta_not_reduced)->alpha_not_reduced { + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + let full_size := 0x65 // size("alpha") + 0x20 (previous challenge) + + // alpha + mstore(mPtr, FS_ALPHA) // "alpha" + let _mPtr := add(mPtr, 0x20) + mstore(_mPtr, beta_not_reduced) + _mPtr := add(_mPtr, 0x20) + + // Bsb22Commitments + let proof_bsb_commitments := add(aproof, PROOF_BSB_COMMITMENTS) + let size_bsb_commitments := mul(0x40, VK_NB_CUSTOM_GATES) + calldatacopy(_mPtr, proof_bsb_commitments, size_bsb_commitments) + _mPtr := add(_mPtr, size_bsb_commitments) + full_size := add(full_size, size_bsb_commitments) + + // [Z], the commitment to the grand product polynomial + calldatacopy(_mPtr, add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_X), 0x40) + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1b), full_size, mPtr, 0x20) + if iszero(l_success) { + error_verify() + } + + alpha_not_reduced := mload(mPtr) + mstore(add(state, STATE_ALPHA), mod(alpha_not_reduced, R_MOD)) + } + + /// derive zeta as sha256 + /// @param aproof pointer to the proof object + /// @param alpha_not_reduced the previous challenge (alpha) not reduced + /// The transcript consists of the previous challenge and the commitment to + /// the quotient polynomial h. + function derive_zeta(aproof, alpha_not_reduced) { + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + + // zeta + mstore(mPtr, FS_ZETA) // "zeta" + mstore(add(mPtr, 0x20), alpha_not_reduced) + calldatacopy(add(mPtr, 0x40), add(aproof, PROOF_H_0_COM_X), 0xc0) + let l_success := staticcall(gas(), SHA2, add(mPtr, 0x1c), 0xe4, mPtr, 0x20) + if iszero(l_success) { + error_verify() + } + let zeta_not_reduced := mload(mPtr) + mstore(add(state, STATE_ZETA), mod(zeta_not_reduced, R_MOD)) + } + // END challenges ------------------------------------------------- + + // BEGINNING compute_pi ------------------------------------------------- + + /// sum_pi_wo_api_commit computes the public inputs contributions, + /// except for the public inputs coming from the custom gate + /// @param ins pointer to the public inputs + /// @param n number of public inputs + /// @param mPtr free memory + /// @return pi_wo_commit public inputs contribution (except the public inputs coming from the custom gate) + function sum_pi_wo_api_commit(ins, n, mPtr)->pi_wo_commit { + + let state := mload(0x40) + let z := mload(add(state, STATE_ZETA)) + let zpnmo := mload(add(state, STATE_ZETA_POWER_N_MINUS_ONE)) + + let li := mPtr + batch_compute_lagranges_at_z(z, zpnmo, n, li) + + let tmp := 0 + for {let i:=0} lt(i,n) {i:=add(i,1)} + { + tmp := mulmod(mload(li), calldataload(ins), R_MOD) + pi_wo_commit := addmod(pi_wo_commit, tmp, R_MOD) + li := add(li, 0x20) + ins := add(ins, 0x20) + } + + } + + /// batch_compute_lagranges_at_z computes [L_0(z), .., L_{n-1}(z)] + /// @param z point at which the Lagranges are evaluated + /// @param zpnmo ζⁿ-1 + /// @param n_pub number of public inputs (number of Lagranges to compute) + /// @param mPtr pointer to which the results are stored + function batch_compute_lagranges_at_z(z, zpnmo, n_pub, mPtr) { + + let zn := mulmod(zpnmo, VK_INV_DOMAIN_SIZE, R_MOD) // 1/n * (ζⁿ - 1) + + let _w := 1 + let _mPtr := mPtr + for {let i:=0} lt(i,n_pub) {i:=add(i,1)} + { + mstore(_mPtr, addmod(z,sub(R_MOD, _w), R_MOD)) + _w := mulmod(_w, VK_OMEGA, R_MOD) + _mPtr := add(_mPtr, 0x20) + } + batch_invert(mPtr, n_pub, _mPtr) + _mPtr := mPtr + _w := 1 + for {let i:=0} lt(i,n_pub) {i:=add(i,1)} + { + mstore(_mPtr, mulmod(mulmod(mload(_mPtr), zn , R_MOD), _w, R_MOD)) + _mPtr := add(_mPtr, 0x20) + _w := mulmod(_w, VK_OMEGA, R_MOD) + } + } + + /// @notice Montgomery trick for batch inversion mod R_MOD + /// @param ins pointer to the data to batch invert + /// @param number of elements to batch invert + /// @param mPtr free memory + function batch_invert(ins, nb_ins, mPtr) { + mstore(mPtr, 1) + let offset := 0 + for {let i:=0} lt(i, nb_ins) {i:=add(i,1)} + { + let prev := mload(add(mPtr, offset)) + let cur := mload(add(ins, offset)) + cur := mulmod(prev, cur, R_MOD) + offset := add(offset, 0x20) + mstore(add(mPtr, offset), cur) + } + ins := add(ins, sub(offset, 0x20)) + mPtr := add(mPtr, offset) + let inv := pow(mload(mPtr), sub(R_MOD,2), add(mPtr, 0x20)) + for {let i:=0} lt(i, nb_ins) {i:=add(i,1)} + { + mPtr := sub(mPtr, 0x20) + let tmp := mload(ins) + let cur := mulmod(inv, mload(mPtr), R_MOD) + mstore(ins, cur) + inv := mulmod(inv, tmp, R_MOD) + ins := sub(ins, 0x20) + } + } + + + /// Public inputs (the ones coming from the custom gate) contribution + /// @param aproof pointer to the proof + /// @param nb_public_inputs number of public inputs + /// @param mPtr pointer to free memory + /// @return pi_commit custom gate public inputs contribution + function sum_pi_commit(aproof, nb_public_inputs, mPtr)->pi_commit { + + let state := mload(0x40) + let z := mload(add(state, STATE_ZETA)) + let zpnmo := mload(add(state, STATE_ZETA_POWER_N_MINUS_ONE)) + + let p := add(aproof, PROOF_BSB_COMMITMENTS) + + let h_fr, ith_lagrange + + + h_fr := hash_fr(calldataload(p), calldataload(add(p, 0x20)), mPtr) + ith_lagrange := compute_ith_lagrange_at_z(z, zpnmo, add(nb_public_inputs, VK_INDEX_COMMIT_API_0), mPtr) + pi_commit := addmod(pi_commit, mulmod(h_fr, ith_lagrange, R_MOD), R_MOD) + + + + } + + /// Computes L_i(zeta) = ωⁱ/n * (ζⁿ-1)/(ζ-ωⁱ) where: + /// @param z zeta + /// @param zpmno ζⁿ-1 + /// @param i i-th lagrange + /// @param mPtr free memory + /// @return res = ωⁱ/n * (ζⁿ-1)/(ζ-ωⁱ) + function compute_ith_lagrange_at_z(z, zpnmo, i, mPtr)->res { + + let w := pow(VK_OMEGA, i, mPtr) // w**i + i := addmod(z, sub(R_MOD, w), R_MOD) // z-w**i + w := mulmod(w, VK_INV_DOMAIN_SIZE, R_MOD) // w**i/n + i := pow(i, sub(R_MOD,2), mPtr) // (z-w**i)**-1 + w := mulmod(w, i, R_MOD) // w**i/n*(z-w)**-1 + res := mulmod(w, zpnmo, R_MOD) + + } + + /// @dev https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-5.2 + /// @param x x coordinate of a point on Bn254(𝔽_p) + /// @param y y coordinate of a point on Bn254(𝔽_p) + /// @param mPtr free memory + /// @return res an element mod R_MOD + function hash_fr(x, y, mPtr)->res { + + // [0x00, .. , 0x00 || x, y, || 0, 48, 0, dst, HASH_FR_SIZE_DOMAIN] + // <- 64 bytes -> <-64b -> <- 1 bytes each -> + + // [0x00, .., 0x00] 64 bytes of zero + mstore(mPtr, HASH_FR_ZERO_UINT256) + mstore(add(mPtr, 0x20), HASH_FR_ZERO_UINT256) + + // msg = x || y , both on 32 bytes + mstore(add(mPtr, 0x40), x) + mstore(add(mPtr, 0x60), y) + + // 0 || 48 || 0 all on 1 byte + mstore8(add(mPtr, 0x80), 0) + mstore8(add(mPtr, 0x81), HASH_FR_LEN_IN_BYTES) + mstore8(add(mPtr, 0x82), 0) + + // "BSB22-Plonk" = [42, 53, 42, 32, 32, 2d, 50, 6c, 6f, 6e, 6b,] + mstore8(add(mPtr, 0x83), 0x42) + mstore8(add(mPtr, 0x84), 0x53) + mstore8(add(mPtr, 0x85), 0x42) + mstore8(add(mPtr, 0x86), 0x32) + mstore8(add(mPtr, 0x87), 0x32) + mstore8(add(mPtr, 0x88), 0x2d) + mstore8(add(mPtr, 0x89), 0x50) + mstore8(add(mPtr, 0x8a), 0x6c) + mstore8(add(mPtr, 0x8b), 0x6f) + mstore8(add(mPtr, 0x8c), 0x6e) + mstore8(add(mPtr, 0x8d), 0x6b) + + // size domain + mstore8(add(mPtr, 0x8e), HASH_FR_SIZE_DOMAIN) + + let l_success := staticcall(gas(), SHA2, mPtr, 0x8f, mPtr, 0x20) + if iszero(l_success) { + error_verify() + } + + let b0 := mload(mPtr) + + // [b0 || one || dst || HASH_FR_SIZE_DOMAIN] + // <-64bytes -> <- 1 byte each -> + mstore8(add(mPtr, 0x20), HASH_FR_ONE) // 1 + + mstore8(add(mPtr, 0x21), 0x42) // dst + mstore8(add(mPtr, 0x22), 0x53) + mstore8(add(mPtr, 0x23), 0x42) + mstore8(add(mPtr, 0x24), 0x32) + mstore8(add(mPtr, 0x25), 0x32) + mstore8(add(mPtr, 0x26), 0x2d) + mstore8(add(mPtr, 0x27), 0x50) + mstore8(add(mPtr, 0x28), 0x6c) + mstore8(add(mPtr, 0x29), 0x6f) + mstore8(add(mPtr, 0x2a), 0x6e) + mstore8(add(mPtr, 0x2b), 0x6b) + + mstore8(add(mPtr, 0x2c), HASH_FR_SIZE_DOMAIN) // size domain + l_success := staticcall(gas(), SHA2, mPtr, 0x2d, mPtr, 0x20) + if iszero(l_success) { + error_verify() + } + + // b1 is located at mPtr. We store b2 at add(mPtr, 0x20) + + // [b0^b1 || two || dst || HASH_FR_SIZE_DOMAIN] + // <-64bytes -> <- 1 byte each -> + mstore(add(mPtr, 0x20), xor(mload(mPtr), b0)) + mstore8(add(mPtr, 0x40), HASH_FR_TWO) + + mstore8(add(mPtr, 0x41), 0x42) // dst + mstore8(add(mPtr, 0x42), 0x53) + mstore8(add(mPtr, 0x43), 0x42) + mstore8(add(mPtr, 0x44), 0x32) + mstore8(add(mPtr, 0x45), 0x32) + mstore8(add(mPtr, 0x46), 0x2d) + mstore8(add(mPtr, 0x47), 0x50) + mstore8(add(mPtr, 0x48), 0x6c) + mstore8(add(mPtr, 0x49), 0x6f) + mstore8(add(mPtr, 0x4a), 0x6e) + mstore8(add(mPtr, 0x4b), 0x6b) + + mstore8(add(mPtr, 0x4c), HASH_FR_SIZE_DOMAIN) // size domain + + let offset := add(mPtr, 0x20) + l_success := staticcall(gas(), SHA2, offset, 0x2d, offset, 0x20) + if iszero(l_success) { + error_verify() + } + + // at this point we have mPtr = [ b1 || b2] where b1 is on 32byes and b2 in 16bytes. + // we interpret it as a big integer mod r in big endian (similar to regular decimal notation) + // the result is then 2**(8*16)*mPtr[:32] + mPtr[32:48] + res := mulmod(mload(mPtr), HASH_FR_BB, R_MOD) // <- res = 2**128 * mPtr[:32] + let b1 := shr(128, mload(add(mPtr, 0x20))) // b1 <- [0, 0, .., 0 || b2[:16] ] + res := addmod(res, b1, R_MOD) + + } + + // END compute_pi ------------------------------------------------- + + /// @notice compute α² * 1/n * (ζ{n}-1)/(ζ - 1) where + /// * α = challenge derived in derive_gamma_beta_alpha_zeta + /// * n = vk_domain_size + /// * ω = vk_omega (generator of the multiplicative cyclic group of order n in (ℤ/rℤ)*) + /// * ζ = zeta (challenge derived with Fiat Shamir) + function compute_alpha_square_lagrange_0() { + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + + let res := mload(add(state, STATE_ZETA_POWER_N_MINUS_ONE)) + let den := addmod(mload(add(state, STATE_ZETA)), sub(R_MOD, 1), R_MOD) + den := pow(den, sub(R_MOD, 2), mPtr) + den := mulmod(den, VK_INV_DOMAIN_SIZE, R_MOD) + res := mulmod(den, res, R_MOD) + + let l_alpha := mload(add(state, STATE_ALPHA)) + res := mulmod(res, l_alpha, R_MOD) + res := mulmod(res, l_alpha, R_MOD) + mstore(add(state, STATE_ALPHA_SQUARE_LAGRANGE_0), res) + } + + /// @notice follows alg. p.13 of https://eprint.iacr.org/2019/953.pdf + /// with t₁ = t₂ = 1, and the proofs are ([digest] + [quotient] +purported evaluation): + /// * [state_folded_state_digests], [proof_batch_opening_at_zeta_x], state_folded_evals + /// * [proof_grand_product_commitment], [proof_opening_at_zeta_omega_x], [proof_grand_product_at_zeta_omega] + /// @param aproof pointer to the proof + function batch_verify_multi_points(aproof) { + let state := mload(0x40) + let mPtr := add(state, STATE_LAST_MEM) + + // derive a random number. As there is no random generator, we + // do an FS like challenge derivation, depending on both digests and + // ζ to ensure that the prover cannot control the random number. + // Note: adding the other point ζω is not needed, as ω is known beforehand. + mstore(mPtr, mload(add(state, STATE_FOLDED_DIGESTS_X))) + mstore(add(mPtr, 0x20), mload(add(state, STATE_FOLDED_DIGESTS_Y))) + mstore(add(mPtr, 0x40), calldataload(add(aproof, PROOF_BATCH_OPENING_AT_ZETA_X))) + mstore(add(mPtr, 0x60), calldataload(add(aproof, PROOF_BATCH_OPENING_AT_ZETA_Y))) + mstore(add(mPtr, 0x80), calldataload(add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_X))) + mstore(add(mPtr, 0xa0), calldataload(add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_Y))) + mstore(add(mPtr, 0xc0), calldataload(add(aproof, PROOF_OPENING_AT_ZETA_OMEGA_X))) + mstore(add(mPtr, 0xe0), calldataload(add(aproof, PROOF_OPENING_AT_ZETA_OMEGA_Y))) + mstore(add(mPtr, 0x100), mload(add(state, STATE_ZETA))) + mstore(add(mPtr, 0x120), mload(add(state, STATE_GAMMA_KZG))) + let random := staticcall(gas(), SHA2, mPtr, 0x140, mPtr, 0x20) + if iszero(random){ + error_random_generation() + } + random := mod(mload(mPtr), R_MOD) // use the same variable as we are one variable away from getting stack-too-deep error... + + let folded_quotients := mPtr + mPtr := add(folded_quotients, 0x40) + mstore(folded_quotients, calldataload(add(aproof, PROOF_BATCH_OPENING_AT_ZETA_X))) + mstore(add(folded_quotients, 0x20), calldataload(add(aproof, PROOF_BATCH_OPENING_AT_ZETA_Y))) + point_acc_mul_calldata(folded_quotients, add(aproof, PROOF_OPENING_AT_ZETA_OMEGA_X), random, mPtr) + + let folded_digests := add(state, STATE_FOLDED_DIGESTS_X) + point_acc_mul_calldata(folded_digests, add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_X), random, mPtr) + + let folded_evals := add(state, STATE_FOLDED_CLAIMED_VALUES) + fr_acc_mul_calldata(folded_evals, add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA), random) + + let folded_evals_commit := mPtr + mPtr := add(folded_evals_commit, 0x40) + mstore(folded_evals_commit, G1_SRS_X) + mstore(add(folded_evals_commit, 0x20), G1_SRS_Y) + mstore(add(folded_evals_commit, 0x40), mload(folded_evals)) + let check_staticcall := staticcall(gas(), 7, folded_evals_commit, 0x60, folded_evals_commit, 0x40) + if iszero(check_staticcall) { + error_verify() + } + + let folded_evals_commit_y := add(folded_evals_commit, 0x20) + mstore(folded_evals_commit_y, sub(P_MOD, mload(folded_evals_commit_y))) + point_add(folded_digests, folded_digests, folded_evals_commit, mPtr) + + let folded_points_quotients := mPtr + mPtr := add(mPtr, 0x40) + point_mul_calldata( + folded_points_quotients, + add(aproof, PROOF_BATCH_OPENING_AT_ZETA_X), + mload(add(state, STATE_ZETA)), + mPtr + ) + let zeta_omega := mulmod(mload(add(state, STATE_ZETA)), VK_OMEGA, R_MOD) + random := mulmod(random, zeta_omega, R_MOD) + point_acc_mul_calldata(folded_points_quotients, add(aproof, PROOF_OPENING_AT_ZETA_OMEGA_X), random, mPtr) + + point_add(folded_digests, folded_digests, folded_points_quotients, mPtr) + + let folded_quotients_y := add(folded_quotients, 0x20) + mstore(folded_quotients_y, sub(P_MOD, mload(folded_quotients_y))) + + mstore(mPtr, mload(folded_digests)) + + mstore(add(mPtr, 0x20), mload(add(folded_digests, 0x20))) + mstore(add(mPtr, 0x40), G2_SRS_0_X_0) // the 4 lines are the canonical G2 point on BN254 + mstore(add(mPtr, 0x60), G2_SRS_0_X_1) + mstore(add(mPtr, 0x80), G2_SRS_0_Y_0) + mstore(add(mPtr, 0xa0), G2_SRS_0_Y_1) + mstore(add(mPtr, 0xc0), mload(folded_quotients)) + mstore(add(mPtr, 0xe0), mload(add(folded_quotients, 0x20))) + mstore(add(mPtr, 0x100), G2_SRS_1_X_0) + mstore(add(mPtr, 0x120), G2_SRS_1_X_1) + mstore(add(mPtr, 0x140), G2_SRS_1_Y_0) + mstore(add(mPtr, 0x160), G2_SRS_1_Y_1) + check_pairing_kzg(mPtr) + } + + /// @notice check_pairing_kzg checks the result of the final pairing product of the batched + /// kzg verification. The purpose of this function is to avoid exhausting the stack + /// in the function batch_verify_multi_points. + /// @param mPtr pointer storing the tuple of pairs + function check_pairing_kzg(mPtr) { + let state := mload(0x40) + + let l_success := staticcall(gas(), 8, mPtr, 0x180, 0x00, 0x20) + if iszero(l_success) { + error_pairing() + } + let res_pairing := mload(0x00) + mstore(add(state, STATE_SUCCESS), res_pairing) + } + + /// @notice Fold the opening proofs at ζ: + /// * at state+state_folded_digest we store: [Linearised_polynomial]+γ[L] + γ²[R] + γ³[O] + γ⁴[S₁] +γ⁵[S₂] + ∑ᵢγ⁵⁺ⁱ[Pi_{i}] + /// * at state+state_folded_claimed_values we store: Linearised_polynomial(ζ)+γL(ζ) + γ²R(ζ)+ γ³O(ζ) + γ⁴S₁(ζ) +γ⁵S₂(ζ) + ∑ᵢγ⁵⁺ⁱPi_{i}(ζ) + /// @param aproof pointer to the proof + /// acc_gamma stores the γⁱ + function fold_state(aproof) { + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + let mPtr20 := add(mPtr, 0x20) + let mPtr40 := add(mPtr, 0x40) + + let l_gamma_kzg := mload(add(state, STATE_GAMMA_KZG)) + let acc_gamma := l_gamma_kzg + let state_folded_digests := add(state, STATE_FOLDED_DIGESTS_X) + + mstore(state_folded_digests, mload(add(state, STATE_LINEARISED_POLYNOMIAL_X))) + mstore(add(state, STATE_FOLDED_DIGESTS_Y), mload(add(state, STATE_LINEARISED_POLYNOMIAL_Y))) + mstore(add(state, STATE_FOLDED_CLAIMED_VALUES), mload(add(state, STATE_OPENING_LINEARISED_POLYNOMIAL_ZETA))) + + point_acc_mul_calldata(state_folded_digests, add(aproof, PROOF_L_COM_X), acc_gamma, mPtr) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_L_AT_ZETA), acc_gamma) + + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) + point_acc_mul_calldata(state_folded_digests, add(aproof, PROOF_R_COM_X), acc_gamma, mPtr) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_R_AT_ZETA), acc_gamma) + + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) + point_acc_mul_calldata(state_folded_digests, add(aproof, PROOF_O_COM_X), acc_gamma, mPtr) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_O_AT_ZETA), acc_gamma) + + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) + mstore(mPtr, VK_S1_COM_X) + mstore(mPtr20, VK_S1_COM_Y) + point_acc_mul(state_folded_digests, mPtr, acc_gamma, mPtr40) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_S1_AT_ZETA), acc_gamma) + + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) + mstore(mPtr, VK_S2_COM_X) + mstore(mPtr20, VK_S2_COM_Y) + point_acc_mul(state_folded_digests, mPtr, acc_gamma, mPtr40) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), add(aproof, PROOF_S2_AT_ZETA), acc_gamma) + let poqaz := add(aproof, PROOF_OPENING_QCP_AT_ZETA) + + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, R_MOD) + mstore(mPtr, VK_QCP_0_X) + mstore(mPtr20, VK_QCP_0_Y) + point_acc_mul(state_folded_digests, mPtr, acc_gamma, mPtr40) + fr_acc_mul_calldata(add(state, STATE_FOLDED_CLAIMED_VALUES), poqaz, acc_gamma) + poqaz := add(poqaz, 0x20) + + } + + /// @notice generate the challenge (using Fiat Shamir) to fold the opening proofs + /// at ζ. + /// The process for deriving γ is the same as in derive_gamma but this time the inputs are + /// in this order (the [] means it's a commitment): + /// * ζ + /// * [Linearised polynomial] + /// * [L], [R], [O] + /// * [S₁] [S₂] + /// * [Pi_{i}] (wires associated to custom gates) + /// Then there are the purported evaluations of the previous committed polynomials: + /// * Linearised_polynomial(ζ) + /// * L(ζ), R(ζ), O(ζ), S₁(ζ), S₂(ζ) + /// * Pi_{i}(ζ) + /// * Z(ζω) + /// @param aproof pointer to the proof + function compute_gamma_kzg(aproof) { + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + mstore(mPtr, FS_GAMMA_KZG) // "gamma" + mstore(add(mPtr, 0x20), mload(add(state, STATE_ZETA))) + mstore(add(mPtr,0x40), mload(add(state, STATE_LINEARISED_POLYNOMIAL_X))) + mstore(add(mPtr,0x60), mload(add(state, STATE_LINEARISED_POLYNOMIAL_Y))) + calldatacopy(add(mPtr, 0x80), add(aproof, PROOF_L_COM_X), 0xc0) + mstore(add(mPtr,0x140), VK_S1_COM_X) + mstore(add(mPtr,0x160), VK_S1_COM_Y) + mstore(add(mPtr,0x180), VK_S2_COM_X) + mstore(add(mPtr,0x1a0), VK_S2_COM_Y) + + let offset := 0x1c0 + + mstore(add(mPtr,offset), VK_QCP_0_X) + mstore(add(mPtr,add(offset, 0x20)), VK_QCP_0_Y) + offset := add(offset, 0x40) + mstore(add(mPtr, offset), mload(add(state, STATE_OPENING_LINEARISED_POLYNOMIAL_ZETA))) + mstore(add(mPtr, add(offset, 0x20)), calldataload(add(aproof, PROOF_L_AT_ZETA))) + mstore(add(mPtr, add(offset, 0x40)), calldataload(add(aproof, PROOF_R_AT_ZETA))) + mstore(add(mPtr, add(offset, 0x60)), calldataload(add(aproof, PROOF_O_AT_ZETA))) + mstore(add(mPtr, add(offset, 0x80)), calldataload(add(aproof, PROOF_S1_AT_ZETA))) + mstore(add(mPtr, add(offset, 0xa0)), calldataload(add(aproof, PROOF_S2_AT_ZETA))) + + let _mPtr := add(mPtr, add(offset, 0xc0)) + + + let _poqaz := add(aproof, PROOF_OPENING_QCP_AT_ZETA) + calldatacopy(_mPtr, _poqaz, mul(VK_NB_CUSTOM_GATES, 0x20)) + _mPtr := add(_mPtr, mul(VK_NB_CUSTOM_GATES, 0x20)) + + + mstore(_mPtr, calldataload(add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA))) + + let start_input := 0x1b // 00.."gamma" + let size_input := add(0x14, mul(VK_NB_CUSTOM_GATES,3)) // number of 32bytes elmts = 0x14 (zeta+3*6 for the digests+openings) + 3*VK_NB_CUSTOM_GATES (for the commitments of the selectors) + 1 (opening of Z at ζω) + size_input := add(0x5, mul(size_input, 0x20)) // size in bytes: 15*32 bytes + 5 bytes for gamma + let check_staticcall := staticcall(gas(), SHA2, add(mPtr,start_input), size_input, add(state, STATE_GAMMA_KZG), 0x20) + if iszero(check_staticcall) { + error_verify() + } + mstore(add(state, STATE_GAMMA_KZG), mod(mload(add(state, STATE_GAMMA_KZG)), R_MOD)) + } + + function compute_commitment_linearised_polynomial_ec(aproof, s1, s2) { + + let state := mload(0x40) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + + mstore(mPtr, VK_QL_COM_X) + mstore(add(mPtr, 0x20), VK_QL_COM_Y) + point_mul( + add(state, STATE_LINEARISED_POLYNOMIAL_X), + mPtr, + calldataload(add(aproof, PROOF_L_AT_ZETA)), + add(mPtr, 0x40) + ) + + mstore(mPtr, VK_QR_COM_X) + mstore(add(mPtr, 0x20), VK_QR_COM_Y) + point_acc_mul( + add(state, STATE_LINEARISED_POLYNOMIAL_X), + mPtr, + calldataload(add(aproof, PROOF_R_AT_ZETA)), + add(mPtr, 0x40) + ) + + let rl := mulmod(calldataload(add(aproof, PROOF_L_AT_ZETA)), calldataload(add(aproof, PROOF_R_AT_ZETA)), R_MOD) + mstore(mPtr, VK_QM_COM_X) + mstore(add(mPtr, 0x20), VK_QM_COM_Y) + point_acc_mul(add(state, STATE_LINEARISED_POLYNOMIAL_X), mPtr, rl, add(mPtr, 0x40)) + + mstore(mPtr, VK_QO_COM_X) + mstore(add(mPtr, 0x20), VK_QO_COM_Y) + point_acc_mul( + add(state, STATE_LINEARISED_POLYNOMIAL_X), + mPtr, + calldataload(add(aproof, PROOF_O_AT_ZETA)), + add(mPtr, 0x40) + ) + + mstore(mPtr, VK_QK_COM_X) + mstore(add(mPtr, 0x20), VK_QK_COM_Y) + point_add( + add(state, STATE_LINEARISED_POLYNOMIAL_X), + add(state, STATE_LINEARISED_POLYNOMIAL_X), + mPtr, + add(mPtr, 0x40) + ) + + + let qcp_opening_at_zeta := add(aproof, PROOF_OPENING_QCP_AT_ZETA) + let bsb_commitments := add(aproof, PROOF_BSB_COMMITMENTS) + for { + let i := 0 + } lt(i, VK_NB_CUSTOM_GATES) { + i := add(i, 1) + } { + mstore(mPtr, calldataload(bsb_commitments)) + mstore(add(mPtr, 0x20), calldataload(add(bsb_commitments, 0x20))) + point_acc_mul( + add(state, STATE_LINEARISED_POLYNOMIAL_X), + mPtr, + calldataload(qcp_opening_at_zeta), + add(mPtr, 0x40) + ) + qcp_opening_at_zeta := add(qcp_opening_at_zeta, 0x20) + bsb_commitments := add(bsb_commitments, 0x40) + } + + + mstore(mPtr, VK_S3_COM_X) + mstore(add(mPtr, 0x20), VK_S3_COM_Y) + point_acc_mul(add(state, STATE_LINEARISED_POLYNOMIAL_X), mPtr, s1, add(mPtr, 0x40)) + + mstore(mPtr, calldataload(add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_X))) + mstore(add(mPtr, 0x20), calldataload(add(aproof, PROOF_GRAND_PRODUCT_COMMITMENT_Y))) + point_acc_mul(add(state, STATE_LINEARISED_POLYNOMIAL_X), mPtr, s2, add(mPtr, 0x40)) + + point_add( + add(state, STATE_LINEARISED_POLYNOMIAL_X), + add(state, STATE_LINEARISED_POLYNOMIAL_X), + add(state, STATE_FOLDED_H_X), + mPtr) + } + + /// @notice Compute the commitment to the linearized polynomial equal to + /// L(ζ)[Qₗ]+r(ζ)[Qᵣ]+R(ζ)L(ζ)[Qₘ]+O(ζ)[Qₒ]+[Qₖ]+Σᵢqc'ᵢ(ζ)[BsbCommitmentᵢ] + + /// α*( Z(μζ)(L(ζ)+β*S₁(ζ)+γ)*(R(ζ)+β*S₂(ζ)+γ)[S₃]-[Z](L(ζ)+β*id_{1}(ζ)+γ)*(R(ζ)+β*id_{2}(ζ)+γ)*(O(ζ)+β*id_{3}(ζ)+γ) ) + + /// α²*L₁(ζ)[Z] - Z_{H}(ζ)*(([H₀] + ζᵐ⁺²*[H₁] + ζ²⁽ᵐ⁺²⁾*[H₂]) + /// where + /// * id_1 = id, id_2 = vk_coset_shift*id, id_3 = vk_coset_shift^{2}*id + /// * the [] means that it's a commitment (i.e. a point on Bn254(F_p)) + /// * Z_{H}(ζ) = ζ^n-1 + /// @param aproof pointer to the proof + function compute_commitment_linearised_polynomial(aproof) { + let state := mload(0x40) + let l_beta := mload(add(state, STATE_BETA)) + let l_gamma := mload(add(state, STATE_GAMMA)) + let l_zeta := mload(add(state, STATE_ZETA)) + let l_alpha := mload(add(state, STATE_ALPHA)) + + let u := mulmod(calldataload(add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA)), l_beta, R_MOD) + let v := mulmod(l_beta, calldataload(add(aproof, PROOF_S1_AT_ZETA)), R_MOD) + v := addmod(v, calldataload(add(aproof, PROOF_L_AT_ZETA)), R_MOD) + v := addmod(v, l_gamma, R_MOD) + + let w := mulmod(l_beta, calldataload(add(aproof, PROOF_S2_AT_ZETA)), R_MOD) + w := addmod(w, calldataload(add(aproof, PROOF_R_AT_ZETA)), R_MOD) + w := addmod(w, l_gamma, R_MOD) + + let s1 := mulmod(u, v, R_MOD) + s1 := mulmod(s1, w, R_MOD) + s1 := mulmod(s1, l_alpha, R_MOD) + + let coset_square := mulmod(VK_COSET_SHIFT, VK_COSET_SHIFT, R_MOD) + let betazeta := mulmod(l_beta, l_zeta, R_MOD) + u := addmod(betazeta, calldataload(add(aproof, PROOF_L_AT_ZETA)), R_MOD) + u := addmod(u, l_gamma, R_MOD) + + v := mulmod(betazeta, VK_COSET_SHIFT, R_MOD) + v := addmod(v, calldataload(add(aproof, PROOF_R_AT_ZETA)), R_MOD) + v := addmod(v, l_gamma, R_MOD) + + w := mulmod(betazeta, coset_square, R_MOD) + w := addmod(w, calldataload(add(aproof, PROOF_O_AT_ZETA)), R_MOD) + w := addmod(w, l_gamma, R_MOD) + + let s2 := mulmod(u, v, R_MOD) + s2 := mulmod(s2, w, R_MOD) + s2 := sub(R_MOD, s2) + s2 := mulmod(s2, l_alpha, R_MOD) + s2 := addmod(s2, mload(add(state, STATE_ALPHA_SQUARE_LAGRANGE_0)), R_MOD) + + // at this stage: + // * s₁ = α*Z(μζ)(l(ζ)+β*s₁(ζ)+γ)*(r(ζ)+β*s₂(ζ)+γ)*β + // * s₂ = -α*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ) + α²*L₁(ζ) + + compute_commitment_linearised_polynomial_ec(aproof, s1, s2) + } + + /// @notice compute -z_h(ζ)*([H₁] + ζⁿ⁺²[H₂] + ζ²⁽ⁿ⁺²⁾[H₃]) and store the result at + /// state + state_folded_h + /// @param aproof pointer to the proof + function fold_h(aproof) { + let state := mload(0x40) + let n_plus_two := add(VK_DOMAIN_SIZE, 2) + let mPtr := add(mload(0x40), STATE_LAST_MEM) + let zeta_power_n_plus_two := pow(mload(add(state, STATE_ZETA)), n_plus_two, mPtr) + point_mul_calldata(add(state, STATE_FOLDED_H_X), add(aproof, PROOF_H_2_COM_X), zeta_power_n_plus_two, mPtr) + point_add_calldata(add(state, STATE_FOLDED_H_X), add(state, STATE_FOLDED_H_X), add(aproof, PROOF_H_1_COM_X), mPtr) + point_mul(add(state, STATE_FOLDED_H_X), add(state, STATE_FOLDED_H_X), zeta_power_n_plus_two, mPtr) + point_add_calldata(add(state, STATE_FOLDED_H_X), add(state, STATE_FOLDED_H_X), add(aproof, PROOF_H_0_COM_X), mPtr) + point_mul(add(state, STATE_FOLDED_H_X), add(state, STATE_FOLDED_H_X), mload(add(state, STATE_ZETA_POWER_N_MINUS_ONE)), mPtr) + let folded_h_y := mload(add(state, STATE_FOLDED_H_Y)) + folded_h_y := sub(P_MOD, folded_h_y) + mstore(add(state, STATE_FOLDED_H_Y), folded_h_y) + } + + /// @notice check that the opening of the linearised polynomial at zeta is equal to + /// - [ PI(ζ) - α²*L₁(ζ) + α(l(ζ)+β*s1(ζ)+γ)(r(ζ)+β*s2(ζ)+γ)(o(ζ)+γ)*z(ωζ) ] + /// @param aproof pointer to the proof + function compute_opening_linearised_polynomial(aproof) { + + let state := mload(0x40) + + // (l(ζ)+β*s1(ζ)+γ) + let s1 + s1 := mulmod(calldataload(add(aproof, PROOF_S1_AT_ZETA)), mload(add(state, STATE_BETA)), R_MOD) + s1 := addmod(s1, mload(add(state, STATE_GAMMA)), R_MOD) + s1 := addmod(s1, calldataload(add(aproof, PROOF_L_AT_ZETA)), R_MOD) + + // (r(ζ)+β*s2(ζ)+γ) + let s2 + s2 := mulmod(calldataload(add(aproof, PROOF_S2_AT_ZETA)), mload(add(state, STATE_BETA)), R_MOD) + s2 := addmod(s2, mload(add(state, STATE_GAMMA)), R_MOD) + s2 := addmod(s2, calldataload(add(aproof, PROOF_R_AT_ZETA)), R_MOD) + + // (o(ζ)+γ) + let o + o := addmod(calldataload(add(aproof, PROOF_O_AT_ZETA)), mload(add(state, STATE_GAMMA)), R_MOD) + + // α*Z(μζ)*(l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(o(ζ)+γ) + s1 := mulmod(s1, s2, R_MOD) + s1 := mulmod(s1, o, R_MOD) + s1 := mulmod(s1, mload(add(state, STATE_ALPHA)), R_MOD) + s1 := mulmod(s1, calldataload(add(aproof, PROOF_GRAND_PRODUCT_AT_ZETA_OMEGA)), R_MOD) + + // PI(ζ) - α²*L₁(ζ) + α(l(ζ)+β*s1(ζ)+γ)(r(ζ)+β*s2(ζ)+γ)(o(ζ)+γ)*z(ωζ) + s1 := addmod(s1, mload(add(state, STATE_PI)), R_MOD) + s2 := mload(add(state, STATE_ALPHA_SQUARE_LAGRANGE_0)) + s2 := sub(R_MOD, s2) + s1 := addmod(s1, s2, R_MOD) + s1 := sub(R_MOD, s1) + + mstore(add(state, STATE_OPENING_LINEARISED_POLYNOMIAL_ZETA), s1) + } + + // BEGINNING utils math functions ------------------------------------------------- + + /// @param dst pointer storing the result + /// @param p pointer to the first point + /// @param q pointer to the second point + /// @param mPtr pointer to free memory + function point_add(dst, p, q, mPtr) { + mstore(mPtr, mload(p)) + mstore(add(mPtr, 0x20), mload(add(p, 0x20))) + mstore(add(mPtr, 0x40), mload(q)) + mstore(add(mPtr, 0x60), mload(add(q, 0x20))) + let l_success := staticcall(gas(),EC_ADD,mPtr,0x80,dst,0x40) + if iszero(l_success) { + error_ec_op() + } + } + + /// @param dst pointer storing the result + /// @param p pointer to the first point (calldata) + /// @param q pointer to the second point (calladata) + /// @param mPtr pointer to free memory + function point_add_calldata(dst, p, q, mPtr) { + mstore(mPtr, mload(p)) + mstore(add(mPtr, 0x20), mload(add(p, 0x20))) + mstore(add(mPtr, 0x40), calldataload(q)) + mstore(add(mPtr, 0x60), calldataload(add(q, 0x20))) + let l_success := staticcall(gas(), EC_ADD, mPtr, 0x80, dst, 0x40) + if iszero(l_success) { + error_ec_op() + } + } + + /// @parma dst pointer storing the result + /// @param src pointer to a point on Bn254(𝔽_p) + /// @param s scalar + /// @param mPtr free memory + function point_mul(dst,src,s, mPtr) { + mstore(mPtr,mload(src)) + mstore(add(mPtr,0x20),mload(add(src,0x20))) + mstore(add(mPtr,0x40),s) + let l_success := staticcall(gas(),EC_MUL,mPtr,0x60,dst,0x40) + if iszero(l_success) { + error_ec_op() + } + } + + /// @parma dst pointer storing the result + /// @param src pointer to a point on Bn254(𝔽_p) on calldata + /// @param s scalar + /// @param mPtr free memory + function point_mul_calldata(dst, src, s, mPtr) { + mstore(mPtr, calldataload(src)) + mstore(add(mPtr, 0x20), calldataload(add(src, 0x20))) + mstore(add(mPtr, 0x40), s) + let l_success := staticcall(gas(), EC_MUL, mPtr, 0x60, dst, 0x40) + if iszero(l_success) { + error_ec_op() + } + } + + /// @notice dst <- dst + [s]src (Elliptic curve) + /// @param dst pointer accumulator point storing the result + /// @param src pointer to the point to multiply and add + /// @param s scalar + /// @param mPtr free memory + function point_acc_mul(dst,src,s, mPtr) { + mstore(mPtr,mload(src)) + mstore(add(mPtr,0x20),mload(add(src,0x20))) + mstore(add(mPtr,0x40),s) + let l_success := staticcall(gas(),7,mPtr,0x60,mPtr,0x40) + mstore(add(mPtr,0x40),mload(dst)) + mstore(add(mPtr,0x60),mload(add(dst,0x20))) + l_success := and(l_success, staticcall(gas(),EC_ADD,mPtr,0x80,dst, 0x40)) + if iszero(l_success) { + error_ec_op() + } + } + + /// @notice dst <- dst + [s]src (Elliptic curve) + /// @param dst pointer accumulator point storing the result + /// @param src pointer to the point to multiply and add (on calldata) + /// @param s scalar + /// @mPtr free memory + function point_acc_mul_calldata(dst, src, s, mPtr) { + mstore(mPtr, calldataload(src)) + mstore(add(mPtr, 0x20), calldataload(add(src, 0x20))) + mstore(add(mPtr, 0x40), s) + let l_success := staticcall(gas(), 7, mPtr, 0x60, mPtr, 0x40) + mstore(add(mPtr, 0x40), mload(dst)) + mstore(add(mPtr, 0x60), mload(add(dst, 0x20))) + l_success := and(l_success, staticcall(gas(), EC_ADD, mPtr, 0x80, dst, 0x40)) + if iszero(l_success) { + error_ec_op() + } + } + + /// @notice dst <- dst + src*s (Fr) dst,src are addresses, s is a value + /// @param dst pointer storing the result + /// @param src pointer to the scalar to multiply and add (on calldata) + /// @param s scalar + function fr_acc_mul_calldata(dst, src, s) { + let tmp := mulmod(calldataload(src), s, R_MOD) + mstore(dst, addmod(mload(dst), tmp, R_MOD)) + } + + /// @param x element to exponentiate + /// @param e exponent + /// @param mPtr free memory + /// @return res x ** e mod r + function pow(x, e, mPtr)->res { + mstore(mPtr, 0x20) + mstore(add(mPtr, 0x20), 0x20) + mstore(add(mPtr, 0x40), 0x20) + mstore(add(mPtr, 0x60), x) + mstore(add(mPtr, 0x80), e) + mstore(add(mPtr, 0xa0), R_MOD) + let check_staticcall := staticcall(gas(),MOD_EXP,mPtr,0xc0,mPtr,0x20) + if eq(check_staticcall, 0) { + error_mod_exp() + } + res := mload(mPtr) + } + } + } +} diff --git a/contracts/src/v4.0.0-rc.3/SP1VerifierGroth16.sol b/contracts/src/v4.0.0-rc.3/SP1VerifierGroth16.sol new file mode 100644 index 0000000..d05ad4e --- /dev/null +++ b/contracts/src/v4.0.0-rc.3/SP1VerifierGroth16.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ISP1Verifier, ISP1VerifierWithHash} from "../ISP1Verifier.sol"; +import {Groth16Verifier} from "./Groth16Verifier.sol"; + +/// @title SP1 Verifier +/// @author Succinct Labs +/// @notice This contracts implements a solidity verifier for SP1. +contract SP1Verifier is Groth16Verifier, ISP1VerifierWithHash { + /// @notice Thrown when the verifier selector from this proof does not match the one in this + /// verifier. This indicates that this proof was sent to the wrong verifier. + /// @param received The verifier selector from the first 4 bytes of the proof. + /// @param expected The verifier selector from the first 4 bytes of the VERIFIER_HASH(). + error WrongVerifierSelector(bytes4 received, bytes4 expected); + + /// @notice Thrown when the proof is invalid. + error InvalidProof(); + + function VERSION() external pure returns (string memory) { + return "v4.0.0-rc.3"; + } + + /// @inheritdoc ISP1VerifierWithHash + function VERIFIER_HASH() public pure returns (bytes32) { + return 0x11b6a09d63d255ad425ee3a7f6211d5ec63fbde9805b40551c3136275b6f4eb4; + } + + /// @notice Hashes the public values to a field elements inside Bn254. + /// @param publicValues The public values. + function hashPublicValues( + bytes calldata publicValues + ) public pure returns (bytes32) { + return sha256(publicValues) & bytes32(uint256((1 << 253) - 1)); + } + + /// @notice Verifies a proof with given public values and vkey. + /// @param programVKey The verification key for the RISC-V program. + /// @param publicValues The public values encoded as bytes. + /// @param proofBytes The proof of the program execution the SP1 zkVM encoded as bytes. + function verifyProof( + bytes32 programVKey, + bytes calldata publicValues, + bytes calldata proofBytes + ) external view { + bytes4 receivedSelector = bytes4(proofBytes[:4]); + bytes4 expectedSelector = bytes4(VERIFIER_HASH()); + if (receivedSelector != expectedSelector) { + revert WrongVerifierSelector(receivedSelector, expectedSelector); + } + + bytes32 publicValuesDigest = hashPublicValues(publicValues); + uint256[2] memory inputs; + inputs[0] = uint256(programVKey); + inputs[1] = uint256(publicValuesDigest); + uint256[8] memory proof = abi.decode(proofBytes[4:], (uint256[8])); + this.Verify(proof, inputs); + } +} \ No newline at end of file diff --git a/contracts/src/v4.0.0-rc.3/SP1VerifierPlonk.sol b/contracts/src/v4.0.0-rc.3/SP1VerifierPlonk.sol new file mode 100644 index 0000000..d81a74a --- /dev/null +++ b/contracts/src/v4.0.0-rc.3/SP1VerifierPlonk.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ISP1Verifier, ISP1VerifierWithHash} from "../ISP1Verifier.sol"; +import {PlonkVerifier} from "./PlonkVerifier.sol"; + +/// @title SP1 Verifier +/// @author Succinct Labs +/// @notice This contracts implements a solidity verifier for SP1. +contract SP1Verifier is PlonkVerifier, ISP1VerifierWithHash { + /// @notice Thrown when the verifier selector from this proof does not match the one in this + /// verifier. This indicates that this proof was sent to the wrong verifier. + /// @param received The verifier selector from the first 4 bytes of the proof. + /// @param expected The verifier selector from the first 4 bytes of the VERIFIER_HASH(). + error WrongVerifierSelector(bytes4 received, bytes4 expected); + + /// @notice Thrown when the proof is invalid. + error InvalidProof(); + + function VERSION() external pure returns (string memory) { + return "v4.0.0-rc.3"; + } + + /// @inheritdoc ISP1VerifierWithHash + function VERIFIER_HASH() public pure returns (bytes32) { + return 0x1b34fe11a637737f0c75c88241669dcf9ca3c03713659265b8241f398a2d286d; + } + + /// @notice Hashes the public values to a field elements inside Bn254. + /// @param publicValues The public values. + function hashPublicValues( + bytes calldata publicValues + ) public pure returns (bytes32) { + return sha256(publicValues) & bytes32(uint256((1 << 253) - 1)); + } + + /// @notice Verifies a proof with given public values and vkey. + /// @param programVKey The verification key for the RISC-V program. + /// @param publicValues The public values encoded as bytes. + /// @param proofBytes The proof of the program execution the SP1 zkVM encoded as bytes. + function verifyProof( + bytes32 programVKey, + bytes calldata publicValues, + bytes calldata proofBytes + ) external view { + bytes4 receivedSelector = bytes4(proofBytes[:4]); + bytes4 expectedSelector = bytes4(VERIFIER_HASH()); + if (receivedSelector != expectedSelector) { + revert WrongVerifierSelector(receivedSelector, expectedSelector); + } + + bytes32 publicValuesDigest = hashPublicValues(publicValues); + uint256[] memory inputs = new uint256[](2); + inputs[0] = uint256(programVKey); + inputs[1] = uint256(publicValuesDigest); + bool success = this.Verify(proofBytes[4:], inputs); + if (!success) { + revert InvalidProof(); + } + } +} \ No newline at end of file