diff --git a/circomx/src/circuit.ts b/circomx/src/circuit.ts index 15b6de1a..5ec54f23 100644 --- a/circomx/src/circuit.ts +++ b/circomx/src/circuit.ts @@ -95,7 +95,7 @@ contract FunctionVerifier is IFunctionVerifier, Groth16Verifier { function verify(bytes32 _inputHash, bytes32 _outputHash, bytes memory _proof) external view returns (bool) { (uint256[8] memory proof) = abi.decode(_proof, (uint256[8])); - uint256[4] memory input; + uint256[3] memory input; input[0] = uint256(CIRCUIT_DIGEST); input[1] = uint256(_inputHash) & ((1 << 253) - 1); input[2] = uint256(_outputHash) & ((1 << 253) - 1); diff --git a/plonky2x/core/src/backend/function/mod.rs b/plonky2x/core/src/backend/function/mod.rs index 969ef721..90fc40e2 100644 --- a/plonky2x/core/src/backend/function/mod.rs +++ b/plonky2x/core/src/backend/function/mod.rs @@ -306,7 +306,7 @@ contract FunctionVerifier is IFunctionVerifier, Verifier { function verify(bytes32 _inputHash, bytes32 _outputHash, bytes memory _proof) external view returns (bool) { (uint256[8] memory proof) = abi.decode(_proof, (uint256[8])); - uint256[4] memory input; + uint256[3] memory input; input[0] = uint256(CIRCUIT_DIGEST); input[1] = uint256(_inputHash) & ((1 << 253) - 1); input[2] = uint256(_outputHash) & ((1 << 253) - 1); diff --git a/plonky2x/verifier/cli.go b/plonky2x/verifier/cli.go index 6f18e166..6187ffed 100644 --- a/plonky2x/verifier/cli.go +++ b/plonky2x/verifier/cli.go @@ -37,9 +37,9 @@ func main() { var s system.ProvingSystem if *systemFlag == "groth16" { - s = system.NewGroth16System(logger, "./data/dummy", *dataPath) + s = system.NewGroth16System(logger, "../data/dummy", *dataPath) } else if *systemFlag == "plonk" { - s = system.NewPlonkSystem(logger, "./data/dummy", *dataPath) + s = system.NewPlonkSystem(logger, "../data/dummy", *dataPath) } else { logger.Error().Msg("invalid proving system") os.Exit(1) diff --git a/plonky2x/verifier/system/VerifierGroth16.sol b/plonky2x/verifier/system/VerifierGroth16.sol new file mode 100644 index 00000000..5dfbfc08 --- /dev/null +++ b/plonky2x/verifier/system/VerifierGroth16.sol @@ -0,0 +1,555 @@ + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @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 Verifier { + + /// 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 = 3741564008954293658842651640216006249052941891294907918019761807657669738151; + uint256 constant ALPHA_Y = 8531068795972712446596049913682250607293100996124780500819002920674310801117; + + // Groth16 beta point in G2 in powers of i + uint256 constant BETA_NEG_X_0 = 5588215724852072815478676822110909256682878036618449593892619677351463406134; + uint256 constant BETA_NEG_X_1 = 16106083803613191292392096240971775300433860829591624229988617525050930542235; + uint256 constant BETA_NEG_Y_0 = 9558165615024084377755529224347161054741924035963398258359880509129140796195; + uint256 constant BETA_NEG_Y_1 = 2874796885051244080561271478962298672991271445303852184350868115936977878112; + + // Groth16 gamma point in G2 in powers of i + uint256 constant GAMMA_NEG_X_0 = 4055837031204616756711920805834314561808938724669028119596652704416026096830; + uint256 constant GAMMA_NEG_X_1 = 14646040385381266259519631530558144306728299696496603343025409471834460901656; + uint256 constant GAMMA_NEG_Y_0 = 11079280748075471633446689205631645393753566898053559938900617215418232491804; + uint256 constant GAMMA_NEG_Y_1 = 14193365224354378870653847122370273372722774268559530698360134591690487733125; + + // Groth16 delta point in G2 in powers of i + uint256 constant DELTA_NEG_X_0 = 12873771659071866910116098964022936150028446836493127495653440542057195563654; + uint256 constant DELTA_NEG_X_1 = 5015994507525689332802849734818680423055741194652033238846519098758004086274; + uint256 constant DELTA_NEG_Y_0 = 14314281012111074120148127784260509991083649241082251648635022296180205731402; + uint256 constant DELTA_NEG_Y_1 = 19363851101711131669088740447312874027030546571178851400041879116169041792013; + + // Constant and public input points + uint256 constant CONSTANT_X = 19302829150669190559774624230469940145690853082683555403192241878358139712885; + uint256 constant CONSTANT_Y = 16170349509678164758931226314845187996595082033625885048461957251807536508601; + uint256 constant PUB_0_X = 15460190371256877943883079119535730573669531426864881627771323312099987971345; + uint256 constant PUB_0_Y = 2998728550917831591741720062774638175136089468774955469646503484212186980366; + uint256 constant PUB_1_X = 20136047682579636114973858149797887070595498507137477853277396417246737964582; + uint256 constant PUB_1_Y = 20096883705253549847685900172278520912537872200798966362366735483459098156807; + uint256 constant PUB_2_X = 1325840837001530065149296079573641897117141006457807245309948477900054624357; + uint256 constant PUB_2_Y = 7608783221136087461234921938499956126326186468248835685462114954678174082769; + uint256 constant PUB_3_X = 20542789652503160132034975670639769035385571708440095190690180334558212774701; + uint256 constant PUB_3_Y = 11441316113588981083681442711890327617696707090242412511449973648721447063520; + + /// 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 wheter 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[4] 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 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)) + mstore(g, PUB_2_X) + mstore(add(g, 0x20), PUB_2_Y) + s := calldataload(add(input, 64)) + 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_3_X) + mstore(add(g, 0x20), PUB_3_Y) + s := calldataload(add(input, 96)) + 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 succesfully 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[4] calldata input + ) public view { + (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. + uint256[24] memory pairings; + // 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 succesfully 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 verifyProof( + uint256[8] calldata proof, + uint256[4] 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/plonky2x/verifier/system/VerifierPlonkRangeCheck.sol b/plonky2x/verifier/system/VerifierPlonkRangeCheck.sol new file mode 100644 index 00000000..70764014 --- /dev/null +++ b/plonky2x/verifier/system/VerifierPlonkRangeCheck.sol @@ -0,0 +1,1193 @@ +// 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.19; + +contract PlonkVerifier { + + uint256 private constant r_mod = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + 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 = 21323229529889760400310609138060984578321400435643037105222148635013783955846; + uint256 private constant g2_srs_1_x_1 = 7915260051931941538748138979493493966755291026217742914291639044395107542202; + uint256 private constant g2_srs_1_y_0 = 17791469851791648782611298697472836548738358789685208509553135024861064561473; + uint256 private constant g2_srs_1_y_1 = 16332990142623790078438354820304447395333723240669819461948529069801697041948; + + // ----------------------- vk --------------------- + uint256 private constant vk_domain_size = 64; + uint256 private constant vk_inv_domain_size = 21546239076966786546898805655487630165289796206659533807077919746160561487873; + uint256 private constant vk_omega = 9088801421649573101014283686030284801466796108869023335878462724291607593530; + uint256 private constant vk_ql_com_x = 19729883129661144174726187739203197368050078035869654528902309069843069807181; + uint256 private constant vk_ql_com_y = 645142408773811550268067785615047510934747843675270083358221501201756293595; + uint256 private constant vk_qr_com_x = 18354242111432818300389067034636472715068554074451272834134460244555239306284; + uint256 private constant vk_qr_com_y = 17769511431347799432848695605663122864383936131363992373624451085894501657141; + uint256 private constant vk_qm_com_x = 510299637142059705124414624042515720697010299223302229012923639825416226069; + uint256 private constant vk_qm_com_y = 4269471513872787687909397255322481939612469548579340499794505466411774190644; + uint256 private constant vk_qo_com_x = 9377736298674645017470600395695379692952747895228000512726794418503909434571; + uint256 private constant vk_qo_com_y = 21623498917885376918462821108748375423075028328860137744808932005207460874084; + uint256 private constant vk_qk_com_x = 11271489628688508960113329439364029227291350134950149429945275626741186300707; + uint256 private constant vk_qk_com_y = 8514682666988368135950339009884779599874051132756435394054971091214874357976; + + uint256 private constant vk_s1_com_x = 16970767024149095344980150765665010275473606539183018655464373110249073339142; + uint256 private constant vk_s1_com_y = 15005586862160751860297715778069425117658075789612952298361191405583582137845; + + uint256 private constant vk_s2_com_x = 1359265141430896663658118453706373901147719740914569996045658798568677734708; + uint256 private constant vk_s2_com_y = 12791117034800742312724338202176452670185723233674125519286242986547496912724; + + uint256 private constant vk_s3_com_x = 19366621212501104980973211081489496423113623659572255178048722706707724884097; + uint256 private constant vk_s3_com_y = 5388533770814042973212734374983973574420588469881659863049674631227692321454; + + uint256 private constant vk_coset_shift = 5; + + + uint256 private constant vk_selector_commitments_commit_api_0_x = 16119771062900448830664782347248752177396844086167961702686738654483784181546; + uint256 private constant vk_selector_commitments_commit_api_0_y = 6360259271838975241602928087298505310867398329079301287033269357587857464548; + + + uint256 private constant vk_index_commit_api_0 = 14; + + + uint256 private constant vk_nb_commitments_commit_api = 1; + + // ------------------------------------------------ + + // offset proof + uint256 private constant proof_l_com_x = 0x00; + 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_x = 0xc0; + uint256 private constant proof_h_0_y = 0xe0; + uint256 private constant proof_h_1_x = 0x100; + uint256 private constant proof_h_1_y = 0x120; + uint256 private constant proof_h_2_x = 0x140; + uint256 private constant proof_h_2_y = 0x160; + + // wire values 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; + + //uint256[STATE_WIDTH-1] permutation_polynomials_at_zeta; // Sσ1(zeta),Sσ2(zeta) + uint256 private constant proof_s1_at_zeta = 0x1e0; // Sσ1(zeta) + uint256 private constant proof_s2_at_zeta = 0x200; // Sσ2(zeta) + + //Bn254.G1Point grand_product_commitment; // [z(x)] + 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) + uint256 private constant proof_quotient_polynomial_at_zeta = 0x280; // t(zeta) + uint256 private constant proof_linearised_polynomial_at_zeta = 0x2a0; // r(zeta) + + // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp + uint256 private constant proof_batch_opening_at_zeta_x = 0x2c0; // [Wzeta] + uint256 private constant proof_batch_opening_at_zeta_y = 0x2e0; + + //Bn254.G1Point opening_at_zeta_omega_proof; // [Wzeta*omega] + uint256 private constant proof_opening_at_zeta_omega_x = 0x300; + uint256 private constant proof_opening_at_zeta_omega_y = 0x320; + + uint256 private constant proof_openings_selector_commit_api_at_zeta = 0x340; + // -> next part of proof is + // [ openings_selector_commits || commitments_wires_commit_api] + + // -------- offset state + + // challenges to check the claimed quotient + uint256 private constant state_alpha = 0x00; + uint256 private constant state_beta = 0x20; + uint256 private constant state_gamma = 0x40; + uint256 private constant state_zeta = 0x60; + + // reusable value + uint256 private constant state_alpha_square_lagrange_0 = 0x80; + + // commitment to H + uint256 private constant state_folded_h_x = 0xa0; + uint256 private constant state_folded_h_y = 0xc0; + + // commitment to the linearised polynomial + uint256 private constant state_linearised_polynomial_x = 0xe0; + uint256 private constant state_linearised_polynomial_y = 0x100; + + // Folded proof for the opening of H, linearised poly, l, r, o, s_1, s_2, qcp + uint256 private constant state_folded_claimed_values = 0x120; + + // folded digests of H, linearised poly, l, r, o, s_1, s_2, qcp + // Bn254.G1Point folded_digests; + uint256 private constant state_folded_digests_x = 0x140; + uint256 private constant state_folded_digests_y = 0x160; + + uint256 private constant state_pi = 0x180; + + uint256 private constant state_zeta_power_n_minus_one = 0x1a0; + + uint256 private constant state_gamma_kzg = 0x1c0; + + uint256 private constant state_success = 0x1e0; + uint256 private constant state_check_var = 0x200; // /!\ this slot is used for debugging only + + uint256 private constant state_last_mem = 0x220; + + // -------- errors + uint256 private constant error_string_id = 0x08c379a000000000000000000000000000000000000000000000000000000000; // selector for function Error(string) + + + // -------- utils (for hash_fr) + uint256 private constant bb = 340282366920938463463374607431768211456; // 2**128 + uint256 private constant zero_uint256 = 0; + + uint8 private constant lenInBytes = 48; + uint8 private constant sizeDomain = 11; + uint8 private constant one = 1; + uint8 private constant two = 2; + + + 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_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_wocommit := sum_pi_commit(proof.offset, public_inputs.length, freeMem) + l_pi := addmod(l_wocommit, l_pi, r_mod) + mstore(add(mem, state_pi), l_pi) + + compute_alpha_square_lagrange_0() + verify_quotient_poly_eval_at_zeta(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_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) + } + + 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) + } + + 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) + } + + 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_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) + } + // end errors ------------------------------------------------- + + // Beginning checks ------------------------------------------------- + + // s number of public inputs, p pointer the public inputs + function check_inputs_size(s, p) { + let input_checks := 1 + for {let i} lt(i, s) {i:=add(i,1)} + { + input_checks := and(input_checks,lt(calldataload(p), r_mod)) + p := add(p, 0x20) + } + if iszero(input_checks) { + error_inputs_size() + } + } + + function check_proof_size(actual_proof_size) { + let expected_proof_size := add(0x340, mul(vk_nb_commitments_commit_api,0x60)) + if iszero(eq(actual_proof_size, expected_proof_size)) { + error_proof_size() + } + } + + function check_proof_openings_size(aproof) { + + let openings_check := 1 + + // linearised polynomial at zeta + let p := add(aproof, proof_linearised_polynomial_at_zeta) + openings_check := and(openings_check, lt(calldataload(p), r_mod)) + + // quotient polynomial at zeta + p := add(aproof, proof_quotient_polynomial_at_zeta) + openings_check := and(openings_check, lt(calldataload(p), r_mod)) + + // proof_l_at_zeta + p := add(aproof, proof_l_at_zeta) + openings_check := and(openings_check, lt(calldataload(p), r_mod)) + + // proof_r_at_zeta + p := add(aproof, proof_r_at_zeta) + openings_check := and(openings_check, lt(calldataload(p), r_mod)) + + // proof_o_at_zeta + p := add(aproof, proof_o_at_zeta) + openings_check := and(openings_check, lt(calldataload(p), r_mod)) + + // proof_s1_at_zeta + p := add(aproof, proof_s1_at_zeta) + openings_check := and(openings_check, lt(calldataload(p), r_mod)) + + // proof_s2_at_zeta + p := add(aproof, proof_s2_at_zeta) + openings_check := and(openings_check, lt(calldataload(p), r_mod)) + + // proof_grand_product_at_zeta_omega + p := add(aproof, proof_grand_product_at_zeta_omega) + openings_check := and(openings_check, lt(calldataload(p), r_mod)) + + // proof_openings_selector_commit_api_at_zeta + + p := add(aproof, proof_openings_selector_commit_api_at_zeta) + for {let i:=0} lt(i, vk_nb_commitments_commit_api) {i:=add(i,1)} + { + openings_check := and(openings_check, lt(calldataload(p), r_mod)) + p := add(p, 0x20) + } + + if iszero(openings_check) { + error_proof_openings_size() + } + + } + // end checks ------------------------------------------------- + + // Beginning challenges ------------------------------------------------- + + // Derive gamma as Sha256() + // where 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 + // nb_pi, pi respectively number of public inputs and public inputs + function derive_gamma(aproof, nb_pi, pi)->gamma_not_reduced { + + let state := mload(0x40) + let mPtr := add(state, state_last_mem) + + // gamma + // gamma in ascii is [0x67,0x61,0x6d, 0x6d, 0x61] + // (same for alpha, beta, zeta) + mstore(mPtr, 0x67616d6d61) // "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) + + // public inputs + let _mPtr := add(mPtr, 0x220) + let size_pi_in_bytes := mul(nb_pi, 0x20) + calldatacopy(_mPtr, pi, size_pi_in_bytes) + _mPtr := add(_mPtr, size_pi_in_bytes) + + // wire commitment commit api + let _proof := add(aproof, proof_openings_selector_commit_api_at_zeta) + _proof := add(_proof, mul(vk_nb_commitments_commit_api, 0x20)) + let size_wire_commitments_commit_api_in_bytes := mul(vk_nb_commitments_commit_api, 0x40) + calldatacopy(_mPtr, _proof, size_wire_commitments_commit_api_in_bytes) + _mPtr := add(_mPtr, size_wire_commitments_commit_api_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) + + let size := add(0x2c5, mul(nb_pi, 0x20)) // 0x2c5 = 22*32+5 + size := add(size, mul(vk_nb_commitments_commit_api, 0x40)) + let l_success := staticcall(gas(), 0x2, 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)) + } + + function derive_beta(gamma_not_reduced)->beta_not_reduced{ + + let state := mload(0x40) + let mPtr := add(mload(0x40), state_last_mem) + + // beta + mstore(mPtr, 0x62657461) // "beta" + mstore(add(mPtr, 0x20), gamma_not_reduced) + let l_success := staticcall(gas(), 0x2, 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)) + } + + // alpha depends on the previous challenge (beta) and on 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) + + // alpha + mstore(mPtr, 0x616C706861) // "alpha" + mstore(add(mPtr, 0x20), beta_not_reduced) + calldatacopy(add(mPtr, 0x40), add(aproof, proof_grand_product_commitment_x), 0x40) + let l_success := staticcall(gas(), 0x2, add(mPtr, 0x1b), 0x65, mPtr, 0x20) //0x1b -> 000.."gamma" + if iszero(l_success) { + error_verify() + } + alpha_not_reduced := mload(mPtr) + mstore(add(state, state_alpha), mod(alpha_not_reduced, r_mod)) + } + + // zeta depends on the previous challenge (alpha) and on the commitment to the quotient polynomial + function derive_zeta(aproof, alpha_not_reduced) { + + let state := mload(0x40) + let mPtr := add(mload(0x40), state_last_mem) + + // zeta + mstore(mPtr, 0x7a657461) // "zeta" + mstore(add(mPtr, 0x20), alpha_not_reduced) + calldatacopy(add(mPtr, 0x40), add(aproof, proof_h_0_x), 0xc0) + let l_success := staticcall(gas(), 0x2, 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 ------------------------------------------------- + + // public input (not comming from the commit api) contribution + // ins, n are the public inputs and number of public inputs respectively + 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) + } + + } + + // mPtr <- [L_0(z), .., L_{n-1}(z)] + // + // Here L_i(zeta) = ωⁱ/n * (ζⁿ-1)/(ζ-ωⁱ) where: + // * n = vk_domain_size + // * ω = vk_omega (generator of the multiplicative cyclic group of order n in (ℤ/rℤ)*) + // * ζ = z (challenge derived with Fiat Shamir) + // * zpnmo = 'zeta power n minus one' (ζⁿ-1) which has been precomputed + function batch_compute_lagranges_at_z(z, zpnmo, n, 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) {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, _mPtr) + _mPtr := mPtr + _w := 1 + for {let i:=0} lt(i,n) {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) + } + } + + // batch invert (modulo r) in place the nb_ins uint256 inputs starting at ins. + 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) + } + } + + + // mPtr free memory. Computes the public input contribution related to the commit + 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_openings_selector_commit_api_at_zeta) + p := add(p, mul(vk_nb_commitments_commit_api, 0x20)) // p points now to the wire 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) + p := add(p, 0x40) + + + } + + // z zeta + // zpmno ζⁿ-1 + // i i-th lagrange + // mPtr free memory + // Computes L_i(zeta) = ωⁱ/n * (ζⁿ-1)/(ζ-ωⁱ) where: + 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) + + } + + // (x, y) point on bn254, both on 32bytes + // mPtr free memory + function hash_fr(x, y, mPtr)->res { + + // [0x00, .. , 0x00 || x, y, || 0, 48, 0, dst, sizeDomain] + // <- 64 bytes -> <-64b -> <- 1 bytes each -> + + // [0x00, .., 0x00] 64 bytes of zero + mstore(mPtr, zero_uint256) + mstore(add(mPtr, 0x20), 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), lenInBytes) + 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), sizeDomain) + + let l_success := staticcall(gas(), 0x2, mPtr, 0x8f, mPtr, 0x20) + if iszero(l_success) { + error_verify() + } + + let b0 := mload(mPtr) + + // [b0 || one || dst || sizeDomain] + // <-64bytes -> <- 1 byte each -> + mstore8(add(mPtr, 0x20), 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), sizeDomain) // size domain + l_success := staticcall(gas(), 0x2, 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 || sizeDomain] + // <-64bytes -> <- 1 byte each -> + mstore(add(mPtr, 0x20), xor(mload(mPtr), b0)) + mstore8(add(mPtr, 0x40), 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), sizeDomain) // size domain + + let offset := add(mPtr, 0x20) + l_success := staticcall(gas(), 0x2, 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), bb, r_mod) // <- res = 2**128 * mPtr[:32] + offset := add(mPtr, 0x10) + for {let i:=0} lt(i, 0x10) {i:=add(i,1)} // mPtr <- [xx, xx, .., | 0, 0, .. 0 || b2 ] + { + mstore8(offset, 0x00) + offset := add(offset, 0x1) + } + let b1 := mload(add(mPtr, 0x10)) // b1 <- [0, 0, .., 0 || b2[:16] ] + res := addmod(res, b1, r_mod) + + } + + // END compute_pi ------------------------------------------------- + + // 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) + } + + // 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] + function batch_verify_multi_points(aproof) { + let state := mload(0x40) + let mPtr := add(state, state_last_mem) + + // here the random is not a challenge, hence no need to use Fiat Shamir, we just + // need an unpredictible result. + let random := mod(keccak256(state, 0x20), r_mod) + + 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, 1) + mstore(add(folded_evals_commit, 0x20), 2) + mstore(add(folded_evals_commit, 0x40), mload(folded_evals)) + let check_staticcall := staticcall(gas(), 7, folded_evals_commit, 0x60, folded_evals_commit, 0x40) + if eq(check_staticcall, 0) { + 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) + } + + // check_pairing_kzg checks the result of the final pairing product of the batched + // kzg verification. The purpose of this function is too avoid exhausting the stack + // in the function batch_verify_multi_points. + // mPtr: pointer storing the tuple of pairs + function check_pairing_kzg(mPtr) { + let state := mload(0x40) + + // TODO test the staticcall using the method from audit_4-5 + let l_success := staticcall(gas(), 8, mPtr, 0x180, 0x00, 0x20) + let res_pairing := mload(0x00) + let s_success := mload(add(state, state_success)) + res_pairing := and(and(res_pairing, l_success), s_success) + mstore(add(state, state_success), res_pairing) + } + + // Fold the opening proofs at ζ: + // * at state+state_folded_digest we store: [H] + γ[Linearised_polynomial]+γ²[L] + γ³[R] + γ⁴[O] + γ⁵[S₁] +γ⁶[S₂] + ∑ᵢγ⁶⁺ⁱ[Pi_{i}] + // * at state+state_folded_claimed_values we store: H(ζ) + γLinearised_polynomial(ζ)+γ²L(ζ) + γ³R(ζ)+ γ⁴O(ζ) + γ⁵S₁(ζ) +γ⁶S₂(ζ) + ∑ᵢγ⁶⁺ⁱPi_{i}(ζ) + // acc_gamma stores the γⁱ + function fold_state(aproof) { + let state := mload(0x40) + let mPtr := add(mload(0x40), state_last_mem) + + let l_gamma_kzg := mload(add(state, state_gamma_kzg)) + let acc_gamma := l_gamma_kzg + + let offset := add(0x200, mul(vk_nb_commitments_commit_api, 0x40)) // 0x40 = 2*0x20 + let mPtrOffset := add(mPtr, offset) + + mstore(add(state, state_folded_digests_x), mload(add(mPtr, 0x40))) + mstore(add(state, state_folded_digests_y), mload(add(mPtr, 0x60))) + mstore(add(state, state_folded_claimed_values), calldataload(add(aproof, proof_quotient_polynomial_at_zeta))) + + point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x80), acc_gamma, mPtrOffset) + fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_linearised_polynomial_at_zeta), acc_gamma) + + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod) + point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0xc0), acc_gamma, mPtrOffset) + 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(add(state, state_folded_digests_x), add(mPtr, 0x100), acc_gamma, add(mPtr, offset)) + 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(add(state, state_folded_digests_x), add(mPtr, 0x140), acc_gamma, add(mPtr, offset)) + 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) + point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x180), acc_gamma, add(mPtr, offset)) + 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) + point_acc_mul(add(state, state_folded_digests_x), add(mPtr, 0x1c0), acc_gamma, add(mPtr, offset)) + fr_acc_mul_calldata(add(state, state_folded_claimed_values), add(aproof, proof_s2_at_zeta), acc_gamma) + + let poscaz := add(aproof, proof_openings_selector_commit_api_at_zeta) + let opca := add(mPtr, 0x200) // offset_proof_commits_api + for {let i := 0} lt(i, vk_nb_commitments_commit_api) {i := add(i, 1)} + { + acc_gamma := mulmod(acc_gamma, l_gamma_kzg, r_mod) + point_acc_mul(add(state, state_folded_digests_x), opca, acc_gamma, add(mPtr, offset)) + fr_acc_mul_calldata(add(state, state_folded_claimed_values), poscaz, acc_gamma) + poscaz := add(poscaz, 0x20) + opca := add(opca, 0x40) + } + } + + // 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): + // * ζ + // * [H] ( = H₁ + ζᵐ⁺²*H₂ + ζ²⁽ᵐ⁺²⁾*H₃ ) + // * [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: + // * H(ζ) + // * Linearised_polynomial(ζ) + // * L(ζ), R(ζ), O(ζ), S₁(ζ), S₂(ζ) + // * Pi_{i}(ζ) + function compute_gamma_kzg(aproof) { + + let state := mload(0x40) + let mPtr := add(mload(0x40), state_last_mem) + mstore(mPtr, 0x67616d6d61) // "gamma" + mstore(add(mPtr, 0x20), mload(add(state, state_zeta))) + mstore(add(mPtr,0x40), mload(add(state, state_folded_h_x))) + mstore(add(mPtr,0x60), mload(add(state, state_folded_h_y))) + mstore(add(mPtr,0x80), mload(add(state, state_linearised_polynomial_x))) + mstore(add(mPtr,0xa0), mload(add(state, state_linearised_polynomial_y))) + calldatacopy(add(mPtr, 0xc0), add(aproof, proof_l_com_x), 0xc0) + mstore(add(mPtr,0x180), vk_s1_com_x) + mstore(add(mPtr,0x1a0), vk_s1_com_y) + mstore(add(mPtr,0x1c0), vk_s2_com_x) + mstore(add(mPtr,0x1e0), vk_s2_com_y) + + let offset := 0x200 + + mstore(add(mPtr,offset), vk_selector_commitments_commit_api_0_x) + mstore(add(mPtr,add(offset, 0x20)), vk_selector_commitments_commit_api_0_y) + offset := add(offset, 0x40) + + + mstore(add(mPtr, offset), calldataload(add(aproof, proof_quotient_polynomial_at_zeta))) + mstore(add(mPtr, add(offset, 0x20)), calldataload(add(aproof, proof_linearised_polynomial_at_zeta))) + mstore(add(mPtr, add(offset, 0x40)), calldataload(add(aproof, proof_l_at_zeta))) + mstore(add(mPtr, add(offset, 0x60)), calldataload(add(aproof, proof_r_at_zeta))) + mstore(add(mPtr, add(offset, 0x80)), calldataload(add(aproof, proof_o_at_zeta))) + mstore(add(mPtr, add(offset, 0xa0)), calldataload(add(aproof, proof_s1_at_zeta))) + mstore(add(mPtr, add(offset, 0xc0)), calldataload(add(aproof, proof_s2_at_zeta))) + + + let _mPtr := add(mPtr, add(offset, 0xe0)) + let _poscaz := add(aproof, proof_openings_selector_commit_api_at_zeta) + for {let i:=0} lt(i, vk_nb_commitments_commit_api) {i:=add(i,1)} + { + mstore(_mPtr, calldataload(_poscaz)) + _poscaz := add(_poscaz, 0x20) + _mPtr := add(_mPtr, 0x20) + } + + + let start_input := 0x1b // 00.."gamma" + let size_input := add(0x16, mul(vk_nb_commitments_commit_api,3)) // number of 32bytes elmts = 0x16 (zeta+2*7+7 for the digests+openings) + 2*vk_nb_commitments_commit_api (for the commitments of the selectors) + vk_nb_commitments_commit_api (for the openings of the selectors) + size_input := add(0x5, mul(size_input, 0x20)) // size in bytes: 15*32 bytes + 5 bytes for gamma + let check_staticcall := staticcall(gas(), 0x2, add(mPtr,start_input), size_input, add(state, state_gamma_kzg), 0x20) + if eq(check_staticcall, 0) { + 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 commits_api_at_zeta := add(aproof, proof_openings_selector_commit_api_at_zeta) + let commits_api := add( + aproof, + add(proof_openings_selector_commit_api_at_zeta, mul(vk_nb_commitments_commit_api, 0x20)) + ) + for { + let i := 0 + } lt(i, vk_nb_commitments_commit_api) { + i := add(i, 1) + } { + mstore(mPtr, calldataload(commits_api)) + mstore(add(mPtr, 0x20), calldataload(add(commits_api, 0x20))) + point_acc_mul( + add(state, state_linearised_polynomial_x), + mPtr, + calldataload(commits_api_at_zeta), + add(mPtr, 0x40) + ) + commits_api_at_zeta := add(commits_api_at_zeta, 0x20) + commits_api := add(commits_api, 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)) + } + + // 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] + // 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)) + 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) + } + + // compute H₁ + ζᵐ⁺²*H₂ + ζ²⁽ᵐ⁺²⁾*H₃ and store the result at + // state + state_folded_h + 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_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_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_x), mPtr) + } + + // check that + // L(ζ)Qₗ(ζ)+r(ζ)Qᵣ(ζ)+R(ζ)L(ζ)Qₘ(ζ)+O(ζ)Qₒ(ζ)+Qₖ(ζ)+Σᵢqc'ᵢ(ζ)BsbCommitmentᵢ(ζ) + + // α*( Z(μζ)(l(ζ)+β*s₁(ζ)+γ)*(r(ζ)+β*s₂(ζ)+γ)*β*s₃(X)-Z(X)(l(ζ)+β*id_1(ζ)+γ)*(r(ζ)+β*id_2(ζ)+γ)*(o(ζ)+β*id_3(ζ)+γ) ) ) + // + α²*L₁(ζ) = + // (ζⁿ-1)H(ζ) + function verify_quotient_poly_eval_at_zeta(aproof) { + let state := mload(0x40) + + // (l(ζ)+β*s1(ζ)+γ) + let s1 := add(mload(0x40), state_last_mem) + mstore(s1, mulmod(calldataload(add(aproof, proof_s1_at_zeta)), mload(add(state, state_beta)), r_mod)) + mstore(s1, addmod(mload(s1), mload(add(state, state_gamma)), r_mod)) + mstore(s1, addmod(mload(s1), calldataload(add(aproof, proof_l_at_zeta)), r_mod)) + + // (r(ζ)+β*s2(ζ)+γ) + let s2 := add(s1, 0x20) + mstore(s2, mulmod(calldataload(add(aproof, proof_s2_at_zeta)), mload(add(state, state_beta)), r_mod)) + mstore(s2, addmod(mload(s2), mload(add(state, state_gamma)), r_mod)) + mstore(s2, addmod(mload(s2), calldataload(add(aproof, proof_r_at_zeta)), r_mod)) + // _s2 := mload(s2) + + // (o(ζ)+γ) + let o := add(s1, 0x40) + mstore(o, addmod(calldataload(add(aproof, proof_o_at_zeta)), mload(add(state, state_gamma)), r_mod)) + + // α*(Z(μζ))*(l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(o(ζ)+γ) + mstore(s1, mulmod(mload(s1), mload(s2), r_mod)) + mstore(s1, mulmod(mload(s1), mload(o), r_mod)) + mstore(s1, mulmod(mload(s1), mload(add(state, state_alpha)), r_mod)) + mstore(s1, mulmod(mload(s1), calldataload(add(aproof, proof_grand_product_at_zeta_omega)), r_mod)) + + let computed_quotient := add(s1, 0x60) + + // linearizedpolynomial + pi(zeta) + mstore(computed_quotient,addmod(calldataload(add(aproof, proof_linearised_polynomial_at_zeta)), mload(add(state, state_pi)), r_mod)) + mstore(computed_quotient, addmod(mload(computed_quotient), mload(s1), r_mod)) + mstore(computed_quotient,addmod(mload(computed_quotient), sub(r_mod, mload(add(state, state_alpha_square_lagrange_0))), r_mod)) + mstore(s2,mulmod(calldataload(add(aproof, proof_quotient_polynomial_at_zeta)),mload(add(state, state_zeta_power_n_minus_one)),r_mod)) + + mstore(add(state, state_success), eq(mload(computed_quotient), mload(s2))) + } + + // BEGINNING utils math functions ------------------------------------------------- + function point_add(dst, p, q, mPtr) { + let state := mload(0x40) + 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(),6,mPtr,0x80,dst,0x40) + if iszero(l_success) { + error_ec_op() + } + } + + function point_add_calldata(dst, p, q, mPtr) { + let state := mload(0x40) + 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(), 6, mPtr, 0x80, dst, 0x40) + if iszero(l_success) { + error_ec_op() + } + } + + // dst <- [s]src + function point_mul(dst,src,s, mPtr) { + let state := mload(0x40) + 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,dst,0x40) + if iszero(l_success) { + error_ec_op() + } + } + + // dst <- [s]src + function point_mul_calldata(dst, src, s, mPtr) { + let state := mload(0x40) + 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, dst, 0x40) + if iszero(l_success) { + error_ec_op() + } + } + + // dst <- dst + [s]src (Elliptic curve) + function point_acc_mul(dst,src,s, mPtr) { + let state := mload(0x40) + 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(),6,mPtr,0x80,dst, 0x40)) + if iszero(l_success) { + error_ec_op() + } + } + + // dst <- dst + [s]src (Elliptic curve) + function point_acc_mul_calldata(dst, src, s, mPtr) { + let state := mload(0x40) + 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(), 6, mPtr, 0x80, dst, 0x40)) + if iszero(l_success) { + error_ec_op() + } + } + + // dst <- dst + src (Fr) dst,src are addresses, s is a value + function fr_acc_mul_calldata(dst, src, s) { + let tmp := mulmod(calldataload(src), s, r_mod) + mstore(dst, addmod(mload(dst), tmp, r_mod)) + } + + // dst <- x ** e mod r (x, e are values, not pointers) + 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(),0x05,mPtr,0xc0,mPtr,0x20) + if eq(check_staticcall, 0) { + error_verify() + } + res := mload(mPtr) + } + } + } +} diff --git a/plonky2x/verifier/system/circuit_test.go b/plonky2x/verifier/system/circuit_test.go index 0aea477c..71c43a65 100644 --- a/plonky2x/verifier/system/circuit_test.go +++ b/plonky2x/verifier/system/circuit_test.go @@ -16,8 +16,8 @@ func TestPlonky2xVerifierCircuit(t *testing.T) { assert := test.NewAssert(t) testCase := func(option int64) error { - dummyCircuitPath := "./data/dummy" - circuitPath := "./data/test_circuit" + dummyCircuitPath := "../data/dummy" + circuitPath := "../data/test_circuit" verifierOnlyCircuitDataDummy := variables.DeserializeVerifierOnlyCircuitData( types.ReadVerifierOnlyCircuitData(dummyCircuitPath + "/verifier_only_circuit_data.json"), diff --git a/plonky2x/verifier/system/groth16.go b/plonky2x/verifier/system/groth16.go index 5477ca10..340f02d2 100644 --- a/plonky2x/verifier/system/groth16.go +++ b/plonky2x/verifier/system/groth16.go @@ -476,42 +476,42 @@ func (s *Groth16System) LoadPublicWitness() (witness.Witness, error) { // } type VerifyingKeyJSON struct { - VkAlpha1X uint64 `json:"vk_alpha1_x"` - VkAlpha1Y uint64 `json:"vk_alpha1_y"` - VkBeta2X1 uint64 `json:"vk_beta2_x1"` - VkBeta2X0 uint64 `json:"vk_beta2_x0"` - VkBeta2Y1 uint64 `json:"vk_beta2_y1"` - VkBeta2Y0 uint64 `json:"vk_beta2_y0"` - VkGamma2X1 uint64 `json:"vk_gamma2_x1"` - VkGamma2X0 uint64 `json:"vk_gamma2_x0"` - VkGamma2Y1 uint64 `json:"vk_gamma2_y1"` - VkGamma2Y0 uint64 `json:"vk_gamma2_y0"` - VkDelta2X1 uint64 `json:"vk_delta2_x1"` - VkDelta2X0 uint64 `json:"vk_delta2_x0"` - VkDelta2Y1 uint64 `json:"vk_delta2_y1"` - VkDelta2Y0 uint64 `json:"vk_delta2_y0"` - Ax uint64 `json:"ax"` - Ay uint64 `json:"ay"` - Bx1 uint64 `json:"bx1"` - Bx0 uint64 `json:"bx0"` - By1 uint64 `json:"by1"` - By0 uint64 `json:"by0"` - Cx uint64 `json:"cx"` - Cy uint64 `json:"cy"` - VkIc0X uint64 `json:"vk_ic0_x"` - VkIc0Y uint64 `json:"vk_ic0_y"` - VkIc1X uint64 `json:"vk_ic1_x"` - VkIc1Y uint64 `json:"vk_ic1_y"` - VkIc2X uint64 `json:"vk_ic2_x"` - VkIc2Y uint64 `json:"vk_ic2_y"` - VkIc3X uint64 `json:"vk_ic3_x"` - VkIc3Y uint64 `json:"vk_ic3_y"` - VkIc4X uint64 `json:"vk_ic4_x"` - VkIc4Y uint64 `json:"vk_ic4_y"` - Input0 uint64 `json:"input_0"` - Input1 uint64 `json:"input_1"` - Input2 uint64 `json:"input_2"` - Input3 uint64 `json:"input_3"` + VkAlpha1X string `json:"vk_alpha1_x"` + VkAlpha1Y string `json:"vk_alpha1_y"` + VkBeta2X1 string `json:"vk_beta2_x1"` + VkBeta2X0 string `json:"vk_beta2_x0"` + VkBeta2Y1 string `json:"vk_beta2_y1"` + VkBeta2Y0 string `json:"vk_beta2_y0"` + VkGamma2X1 string `json:"vk_gamma2_x1"` + VkGamma2X0 string `json:"vk_gamma2_x0"` + VkGamma2Y1 string `json:"vk_gamma2_y1"` + VkGamma2Y0 string `json:"vk_gamma2_y0"` + VkDelta2X1 string `json:"vk_delta2_x1"` + VkDelta2X0 string `json:"vk_delta2_x0"` + VkDelta2Y1 string `json:"vk_delta2_y1"` + VkDelta2Y0 string `json:"vk_delta2_y0"` + Ax string `json:"ax"` + Ay string `json:"ay"` + Bx1 string `json:"bx1"` + Bx0 string `json:"bx0"` + By1 string `json:"by1"` + By0 string `json:"by0"` + Cx string `json:"cx"` + Cy string `json:"cy"` + VkIc0X string `json:"vk_ic0_x"` + VkIc0Y string `json:"vk_ic0_y"` + VkIc1X string `json:"vk_ic1_x"` + VkIc1Y string `json:"vk_ic1_y"` + VkIc2X string `json:"vk_ic2_x"` + VkIc2Y string `json:"vk_ic2_y"` + VkIc3X string `json:"vk_ic3_x"` + VkIc3Y string `json:"vk_ic3_y"` + VkIc4X string `json:"vk_ic4_x"` + VkIc4Y string `json:"vk_ic4_y"` + Input0 string `json:"input_0"` + Input1 string `json:"input_1"` + Input2 string `json:"input_2"` + Input3 string `json:"input_3"` } // VerifyingKeyWrapper wraps groth16.VerifyingKey to allow adding methods. @@ -523,42 +523,25 @@ func (vk *VerifyingKeyWrapper) WriteJSONTo(w io.Writer) error { vkJSON := VerifyingKeyJSON{} // Fill in the scalar fields - vkJSON.VkAlpha1X = vk.G1.Alpha.X[0] - vkJSON.VkAlpha1Y = vk.G1.Alpha.Y[0] + vkJSON.VkAlpha1X = elementToStr(vk.G1.Alpha.X) + vkJSON.VkAlpha1Y = elementToStr(vk.G1.Alpha.Y) + vkJSON.Ax = elementToStr(vk.G1.Beta.X) // Fill in the complex fields like vk.G2.Beta, vk.G2.Gamma, vk.G2.Delta - vkJSON.VkBeta2X1 = vk.G2.Beta.X.A1[0] - vkJSON.VkBeta2X0 = vk.G2.Beta.X.A0[0] - vkJSON.VkBeta2Y1 = vk.G2.Beta.Y.A1[0] - vkJSON.VkBeta2Y0 = vk.G2.Beta.Y.A0[0] - vkJSON.VkGamma2X1 = vk.G2.Gamma.X.A1[0] - vkJSON.VkGamma2X0 = vk.G2.Gamma.X.A0[0] - vkJSON.VkGamma2Y1 = vk.G2.Gamma.Y.A1[0] - vkJSON.VkGamma2Y0 = vk.G2.Gamma.Y.A0[0] - vkJSON.VkDelta2X1 = vk.G2.Delta.X.A1[0] - vkJSON.VkDelta2X0 = vk.G2.Delta.X.A0[0] - vkJSON.VkDelta2Y1 = vk.G2.Delta.Y.A1[0] - vkJSON.VkDelta2Y0 = vk.G2.Delta.Y.A0[0] - - // Fill in the scalar fields - vkJSON.Ax = vk.G1.Alpha.X[0] - vkJSON.Ay = vk.G1.Alpha.Y[0] - vkJSON.Bx1 = vk.G2.Beta.X.A1[0] - vkJSON.Bx0 = vk.G2.Beta.X.A0[0] - vkJSON.By1 = vk.G2.Beta.Y.A1[0] - vkJSON.By0 = vk.G2.Beta.Y.A0[0] - vkJSON.Cx = vk.G1.Alpha.X[0] - vkJSON.Cy = vk.G1.Alpha.Y[0] - - // Fill in the scalar fields - vkJSON.VkIc0X = vk.G1.Delta.X[0] - vkJSON.VkIc0Y = vk.G1.Delta.Y[0] - vkJSON.VkIc1X = vk.G1.Delta.X[1] - vkJSON.VkIc1Y = vk.G1.Delta.Y[1] - vkJSON.VkIc2X = vk.G1.Delta.X[2] - vkJSON.VkIc2Y = vk.G1.Delta.Y[2] - vkJSON.VkIc3X = vk.G1.Delta.X[3] - vkJSON.VkIc3Y = vk.G1.Delta.Y[3] + vkJSON.VkBeta2X1 = elementToStr(vk.G2.Beta.X.A1) + vkJSON.VkBeta2X0 = elementToStr(vk.G2.Beta.X.A0) + vkJSON.VkBeta2Y1 = elementToStr(vk.G2.Beta.Y.A1) + vkJSON.VkBeta2Y0 = elementToStr(vk.G2.Beta.Y.A0) + vkJSON.VkGamma2X1 = elementToStr(vk.G2.Gamma.X.A1) + vkJSON.VkGamma2X0 = elementToStr(vk.G2.Gamma.X.A0) + vkJSON.VkGamma2Y1 = elementToStr(vk.G2.Gamma.Y.A1) + vkJSON.VkGamma2Y0 = elementToStr(vk.G2.Gamma.Y.A0) + vkJSON.VkDelta2X1 = elementToStr(vk.G2.Delta.X.A1) + vkJSON.VkDelta2X0 = elementToStr(vk.G2.Delta.X.A0) + vkJSON.VkDelta2Y1 = elementToStr(vk.G2.Delta.Y.A1) + vkJSON.VkDelta2Y0 = elementToStr(vk.G2.Delta.Y.A0) + + vk.NbG1() // Marshal the struct to JSON jsonData, err := json.MarshalIndent(vkJSON, "", " ") @@ -570,3 +553,13 @@ func (vk *VerifyingKeyWrapper) WriteJSONTo(w io.Writer) error { _, err = w.Write(jsonData) return err } + +func elementToStr(e [4]uint64) string { + // assumes little endian, shifts each limb by 64 bits and adds to bigInt + bigInt := new(big.Int) + for i := len(e) - 1; i >= 0; i-- { + bigInt.Lsh(bigInt, 64) + bigInt.Add(bigInt, new(big.Int).SetUint64(e[i])) + } + return bigInt.String() +} diff --git a/plonky2x/verifier/system/groth16_proof_data.json b/plonky2x/verifier/system/groth16_proof_data.json new file mode 100644 index 00000000..7a3b1d5e --- /dev/null +++ b/plonky2x/verifier/system/groth16_proof_data.json @@ -0,0 +1,17 @@ +{ + "proof": [ + "0x25c185337eb32b4a14249b4182920cfbec865d135ec217c42c80999b9e7c5e9e", + "0x199b5341edb422fc632b222fa212a791ce4df97289211191200f40fa29ddcb77", + "0x14a50ece1b4e8f6872934171f784c240af601b5bbeee8a96f9b09fed3174506e", + "0x04bbe25498f9b1ff653ba75a9e2a56f03a76f159392280db281b0926ca2a59a3", + "0x0a6fa941541dfc1663f21ddec16685f2fe2b7655d6b4a6a7a9eedd0c45772dc1", + "0x16c243a088083dedc7b40e82587e0a8e380e4feb110f45ddaf2cc9497a5d48ed", + "0x13b9c8d36dfe56c7e8ffe548499df91264a668b3b8f456a471cd9660b1b84da8", + "0x20cb4c88165681acc79270d4274410576abfcd524281025fec30d85f3a171f24" + ], + "inputs": [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ] +} \ No newline at end of file diff --git a/plonky2x/verifier/system/plonk_proof_data_range_check.json b/plonky2x/verifier/system/plonk_proof_data_range_check.json new file mode 100644 index 00000000..f04f0fd1 --- /dev/null +++ b/plonky2x/verifier/system/plonk_proof_data_range_check.json @@ -0,0 +1,8 @@ +{ + "proof": "0x19c0034a18cac97126f76d2bd8ecd911661bfd54997800b1b17417a11ef8f6630037b884c6fbc4fcb22c00625173d2d33ef3ca3b3e639600c4e13c665913d09a1a7f8e84b0af818e97f4870096342b4c56f02dd119a85c09b9ef2b98cbb209f202cf5af5608addf3edaaeef295bad371b8ef43961b23d90623642fccdb1e1c30304388f725967a4e856ff325224e10234e989a15642531e43c494acde3738e550ad2a2864c2be31e0640ba5ef7515a85e3affb001e0f53bb3f036649d1e222de23e93152bc7f429595f79aa2ea8324dd598197e61223e134bb8d71c03ef3019520f646f0ce0f908b553c0b7790518ba705e3a6372aa7aefbb6ba92d18bd73c701638cf540bc25ebe12a85fd014a498b723a073f9c864c81ab5d4f0c963eeb76d246d6c7e9a9c12e4a09a25d5bb70adffd4f43b866fe5f022380322652011076b2593b67b3de2ee1d498581630006411f88c00f36ef2e567745995fa7a707f82227733585978311358dc9be48a84089b3d0abca8905dbd2ce6ac4a03e3bbde0b001507003b005a0c028031cd6fd6dcc9371c4f7abec12e9d4b386f89b71d9b78f158d16111fe9159adcded18d4ded33bc7484a5cd1b1734140f7fca5b7ee51c23198a581af8c56f4dbe674c354dc68c171e6f4ee48a5836b2cafe8e135df028f92c57ba61d9469ab96a3c537282b28278a17b3591ac439db41fe547323cf89c0b1c1dc647c257610c176fe4331cbdf37d9352b9ce6a22b3b799ebdec8625e1eac30345ed74e6b33acdb78b2170638306ce8078b34f29ce42bf26ee083427a93b80710d51f7ca0e8458879f8cb9d64a1680ce100586d47e5db75ea43b1a5ceaa0d2f66502ef8d1bfd4a35fd71ecf11388d32a29fcf29695e063d7297b6f24f641a233d01ff862f2ce4a69628c412365488f3311ffe08c3f076e62847e26e4c782e154ddb8a630faa18a0168651e125a998d734a59a4821237ab0f0c42f24c350ef297b798dd0abd4dc15f4fcb7edf3ed05c59b219143c4e2165676bdf57a79ba0106f8469b9b8bc4e88f96611aed1f2961344db818c6c967c091e67eab0d46bad52aaabc3bf63f905118370917ba1da00603e1e829a00e7d8d94a923d2c3d03e3d1902a0e320762ebbe638d8f75e674f091729b98a9cf0584a8ded385f2821f8e205879e35cd9ee368a0aed16703a69a5df5ec3afc5d09aacc3c8aa7d782d23f962f37233358c430e6a90e548d71577aae72851978dbb6fd3f7a03a83bf4cda1500fccc649e0bd283fbd61e46442c6b1d17d9085a939485870687a004a6bf4c638", + "inputs": [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ] +} \ No newline at end of file