From 96733552b0764729678e2752fd49136302e5f0bc Mon Sep 17 00:00:00 2001 From: vladbochok Date: Wed, 23 Oct 2024 15:01:42 +0200 Subject: [PATCH 01/15] Dual verifier implementation --- .../contracts/common/L1ContractErrors.sol | 6 + ...est.sol => PlonkVerifierRecursiveTest.sol} | 4 +- ...VerifierTest.sol => PlonkVerifierTest.sol} | 4 +- .../chain-interfaces/IVerifier.sol | 2 +- .../chain-interfaces/IVerifierV2.sol | 17 + .../verifiers/DualVerifier.sol | 108 ++ .../{ => verifiers}/TestnetVerifier.sol | 9 +- .../verifiers/VerifierFflonk.sol | 1600 +++++++++++++++++ .../VerifierPlonk.sol} | 4 +- l1-contracts/deploy-scripts/DeployL1.s.sol | 25 +- .../concrete/Executor/ExecutorProof.t.sol | 6 +- .../concrete/Executor/_Executor_Shared.t.sol | 6 +- .../{Verifier.t.sol => PlonkVerifier.t.sol} | 10 +- ...ive.t.sol => PlonkVerifierRecursive.t.sol} | 8 +- .../_StateTransitionManager_Shared.t.sol | 6 +- .../DiamondInit/_DiamondInit_Shared.t.sol | 6 +- .../DiamondProxy/DiamondProxy.t.sol | 6 +- .../facets/Admin/_Admin_Shared.t.sol | 6 +- .../chain-deps/facets/Base/_Base_Shared.t.sol | 6 +- .../facets/Mailbox/_Mailbox_Shared.t.sol | 6 +- 20 files changed, 1804 insertions(+), 41 deletions(-) rename l1-contracts/contracts/dev-contracts/test/{VerifierRecursiveTest.sol => PlonkVerifierRecursiveTest.sol} (97%) rename l1-contracts/contracts/dev-contracts/test/{VerifierTest.sol => PlonkVerifierTest.sol} (97%) create mode 100644 l1-contracts/contracts/state-transition/chain-interfaces/IVerifierV2.sol create mode 100644 l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol rename l1-contracts/contracts/state-transition/{ => verifiers}/TestnetVerifier.sol (76%) create mode 100644 l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol rename l1-contracts/contracts/state-transition/{Verifier.sol => verifiers/VerifierPlonk.sol} (99%) rename l1-contracts/test/foundry/unit/concrete/Verifier/{Verifier.t.sol => PlonkVerifier.t.sol} (96%) rename l1-contracts/test/foundry/unit/concrete/Verifier/{VerifierRecursive.t.sol => PlonkVerifierRecursive.t.sol} (88%) diff --git a/l1-contracts/contracts/common/L1ContractErrors.sol b/l1-contracts/contracts/common/L1ContractErrors.sol index 73ff72cc9..b98b21d8f 100644 --- a/l1-contracts/contracts/common/L1ContractErrors.sol +++ b/l1-contracts/contracts/common/L1ContractErrors.sol @@ -305,6 +305,12 @@ error ZeroAddress(); error ZeroBalance(); // 0xc84885d4 error ZeroChainId(); +// 0xe2e6f808 +error WrongRecursiveAggregationInputLength(); +// 0xc352bb73 +error UnknownVerifierType(); +// 0xdf320f0a +error EmptyRecursiveAggregationInputLength(); enum SharedBridgeKey { PostUpgradeFirstBatch, diff --git a/l1-contracts/contracts/dev-contracts/test/VerifierRecursiveTest.sol b/l1-contracts/contracts/dev-contracts/test/PlonkVerifierRecursiveTest.sol similarity index 97% rename from l1-contracts/contracts/dev-contracts/test/VerifierRecursiveTest.sol rename to l1-contracts/contracts/dev-contracts/test/PlonkVerifierRecursiveTest.sol index e2ea7cbbc..951967378 100644 --- a/l1-contracts/contracts/dev-contracts/test/VerifierRecursiveTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/PlonkVerifierRecursiveTest.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.24; -import {Verifier} from "../../state-transition/Verifier.sol"; +import {VerifierPlonk} from "../../state-transition/verifiers/VerifierPlonk.sol"; /// @author Matter Labs -contract VerifierRecursiveTest is Verifier { +contract PlonkVerifierRecursiveTest is VerifierPlonk { // add this to be excluded from coverage report function test() internal virtual {} diff --git a/l1-contracts/contracts/dev-contracts/test/VerifierTest.sol b/l1-contracts/contracts/dev-contracts/test/PlonkVerifierTest.sol similarity index 97% rename from l1-contracts/contracts/dev-contracts/test/VerifierTest.sol rename to l1-contracts/contracts/dev-contracts/test/PlonkVerifierTest.sol index ef60794a0..2a7888328 100644 --- a/l1-contracts/contracts/dev-contracts/test/VerifierTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/PlonkVerifierTest.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.24; -import {Verifier} from "../../state-transition/Verifier.sol"; +import {VerifierPlonk} from "../../state-transition/verifiers/VerifierPlonk.sol"; /// @author Matter Labs -contract VerifierTest is Verifier { +contract PlonkVerifierTest is VerifierPlonk { // add this to be excluded from coverage report function test() internal virtual {} diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol index 97872c370..fac8333a4 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IVerifier.sol @@ -24,5 +24,5 @@ interface IVerifier { /// @notice Calculates a keccak256 hash of the runtime loaded verification keys. /// @return vkHash The keccak256 hash of the loaded verification keys. - function verificationKeyHash() external pure returns (bytes32); + function verificationKeyHash() external view returns (bytes32); } diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IVerifierV2.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IVerifierV2.sol new file mode 100644 index 000000000..ac0acff25 --- /dev/null +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IVerifierV2.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. +pragma solidity ^0.8.21; + +/// @title The interface of the Verifier contract, responsible for the zero knowledge proof verification. +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IVerifierV2 { + /// @dev Verifies a zk-SNARK proof. + /// @return A boolean value indicating whether the zk-SNARK proof is valid. + /// Note: The function may revert execution instead of returning false in some cases. + function verify(uint256[] calldata _publicInputs, uint256[] calldata _proof) external view returns (bool); + + /// @notice Calculates a keccak256 hash of the runtime loaded verification keys. + /// @return vkHash The keccak256 hash of the loaded verification keys. + function verificationKeyHash() external view returns (bytes32); +} diff --git a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol new file mode 100644 index 000000000..7e8a37507 --- /dev/null +++ b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IVerifierV2} from "../chain-interfaces/IVerifierV2.sol"; +import {IVerifier} from "../chain-interfaces/IVerifier.sol"; +import {UnknownVerifierType, EmptyRecursiveAggregationInputLength} from "../../common/L1ContractErrors.sol"; + +/// @title Dual Verifier +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice This contract wraps two different verifiers (FFLONK and PLONK) and routes zk-SNARK proof verification +/// to the correct verifier based on the provided proof type. It reuses the same interface as on the original `Verifier` +/// contract, while abusing on of the fields (`_recursiveAggregationInput`) for proof verification type. The contract is +/// needed for the smooth transition from PLONK based verifier to the FFLONK verifier. +contract DualVerifier is IVerifier { + /// @notice The latest FFLONK verifier contract. + IVerifierV2 public immutable fflonkVerifier; + + /// @notice PLONK verifier contract. + IVerifier public immutable plonkVerifier; + + /// @notice Type of verification for FFLONK verifier. + uint256 constant FFLONK_VERIFICATION_TYPE = 0; + + /// @notice Type of verification for PLONK verifier. + uint256 constant PLONK_VERIFICATION_TYPE = 1; + + /// @param _fflonkVerifier The address of the FFLONK verifier contract. + /// @param _plonkVerifier The address of the PLONK verifier contract. + constructor(IVerifierV2 _fflonkVerifier, IVerifier _plonkVerifier) { + fflonkVerifier = _fflonkVerifier; + plonkVerifier = _plonkVerifier; + } + + /// @notice Routes zk-SNARK proof verification to the appropriate verifier (FFLONK or PLONK) based on the proof type. + /// @param _publicInputs The public inputs to the proof. + /// @param _proof The zk-SNARK proof itself. + /// @param _recursiveAggregationInput The recursive aggregation input, used to determine which verifier to use. + /// The first element determines the verifier type. + /// - 0 indicates the FFLONK verifier should be used. + /// - 1 indicates the PLONK verifier should be used. + /// @return Returns `true` if the proof verification succeeds, otherwise throws an error. + function verify( + uint256[] calldata _publicInputs, + uint256[] calldata _proof, + uint256[] calldata _recursiveAggregationInput + ) public view virtual returns (bool) { + // Ensure the recursive aggregation input has a valid length (at least one element + // for the proof system differentiator). + if (_recursiveAggregationInput.length > 0) { + revert EmptyRecursiveAggregationInputLength(); + } + + // The first element of `_recursiveAggregationInput` determines the verifier type (either FFLONK or PLONK). + uint256 verifierType = _recursiveAggregationInput[0]; + if (verifierType == FFLONK_VERIFICATION_TYPE) { + return fflonkVerifier.verify(_publicInputs, _proof); + } else if (_recursiveAggregationInput[1] == PLONK_VERIFICATION_TYPE) { + return + plonkVerifier.verify( + _publicInputs, + _proof, + _extractRecursiveAggregationInput(_recursiveAggregationInput) + ); + } + // If the verifier type is unknown, revert with an error. + else { + revert UnknownVerifierType(); + } + } + + /// @notice Extract the recursive aggregation input by removing the first element (proof type differentiator). + /// @param _recursiveAggregationInput The original recursive aggregation input array. + /// @return result A new array with the first element removed. The first element was used as a hack for + /// differentiator between FFLONK and PLONK proofs. + function _extractRecursiveAggregationInput( + uint256[] calldata _recursiveAggregationInput + ) internal pure returns (uint256[] memory result) { + uint256 length = _recursiveAggregationInput.length; + // Allocate memory for the new array (length - 1) since the first element is omitted. + result = new uint256[](length - 1); + + // Copy elements starting from index 1 (the second element) of the original array. + for (uint256 i = 1; i < length; ++i) { + result[i - 1] = _recursiveAggregationInput[i]; + } + } + + /// @inheritdoc IVerifier + function verificationKeyHash() external view returns (bytes32) { + return plonkVerifier.verificationKeyHash(); + } + + /// @notice Calculates a keccak256 hash of the runtime loaded verification keys from the selected verifier. + /// @return The keccak256 hash of the loaded verification keys based on the verifier. + function verificationKeyHash(uint256 _verifierType) external view returns (bytes32) { + if (_verifierType == FFLONK_VERIFICATION_TYPE) { + return fflonkVerifier.verificationKeyHash(); + } else if (_verifierType == PLONK_VERIFICATION_TYPE) { + return plonkVerifier.verificationKeyHash(); + } + // If the verifier type is unknown, revert with an error. + else { + revert UnknownVerifierType(); + } + } +} diff --git a/l1-contracts/contracts/state-transition/TestnetVerifier.sol b/l1-contracts/contracts/state-transition/verifiers/TestnetVerifier.sol similarity index 76% rename from l1-contracts/contracts/state-transition/TestnetVerifier.sol rename to l1-contracts/contracts/state-transition/verifiers/TestnetVerifier.sol index 6e97fed05..a778344fb 100644 --- a/l1-contracts/contracts/state-transition/TestnetVerifier.sol +++ b/l1-contracts/contracts/state-transition/verifiers/TestnetVerifier.sol @@ -2,8 +2,9 @@ pragma solidity 0.8.24; -import {Verifier} from "./Verifier.sol"; -import {IVerifier} from "./chain-interfaces/IVerifier.sol"; +import {DualVerifier} from "./DualVerifier.sol"; +import {IVerifierV2} from "../chain-interfaces/IVerifierV2.sol"; +import {IVerifier} from "../chain-interfaces/IVerifier.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -11,8 +12,8 @@ import {IVerifier} from "./chain-interfaces/IVerifier.sol"; /// @dev This contract is used to skip the zkp verification for the testnet environment. /// If the proof is not empty, it will verify it using the main verifier contract, /// otherwise, it will skip the verification. -contract TestnetVerifier is Verifier { - constructor() { +contract TestnetVerifier is DualVerifier { + constructor(IVerifierV2 _fflonkVerifier, IVerifier _plonkVerifier) DualVerifier(_fflonkVerifier, _plonkVerifier) { assert(block.chainid != 1); } diff --git a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol new file mode 100644 index 000000000..5353032e5 --- /dev/null +++ b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol @@ -0,0 +1,1600 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IVerifierV2} from "../chain-interfaces/IVerifierV2.sol"; + +/// @title Fflonk Verifier Implementation +/// @author Matter Labs +/// @notice FFT inspired version of PlonK to optimize on-chain gas cost +/// @dev For better understanding of the protocol follow the below papers: +/// * Fflonk Paper: https://eprint.iacr.org/2021/1167 +/// @custom:security-contact security@matterlabs.dev +contract VerifierFflonk is IVerifierV2 { + // ================Constants================ + uint32 internal constant DST_0 = 0; + uint32 internal constant DST_1 = 1; + uint32 internal constant DST_CHALLENGE = 2; + uint256 internal constant FR_MASK = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + uint256 internal constant Q_MOD = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + uint256 internal constant R_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + uint256 internal constant BN254_B_COEFF = 3; + + // ================Verification Key================ + uint256 internal constant VK_NUM_INPUTS = 1; + // [C0]1 = qL(X^8)+ X*qR(X^8)+ X^2*qO(X^8)+ X^3*qM(X^8)+ X^4*qC(X^8)+ X^5*Sσ1(X^8)+ X^6*Sσ2(X^8)+ X^7*Sσ3(X^8) + uint256 internal constant VK_C0_G1_X = 0x15c99dbc62b8191204ff93984b0de4fb7c79ac7a1ef2c94f4ce940319a2408b2; + uint256 internal constant VK_C0_G1_Y = 0x0521b86a104e07c8971bf2e17d7665d59df7566c08e6e0c9750f584bb24084ce; + // k1 = 5, k2 = 7 + uint256 internal constant VK_NON_RESIDUES_0 = 0x0000000000000000000000000000000000000000000000000000000000000005; + uint256 internal constant VK_NON_RESIDUES_1 = 0x0000000000000000000000000000000000000000000000000000000000000007; + // G2 Elements = [1]_2, [s]_2 + uint256 internal constant VK_G2_ELEMENT_0_X1 = 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2; + uint256 internal constant VK_G2_ELEMENT_0_X2 = 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed; + uint256 internal constant VK_G2_ELEMENT_0_Y1 = 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b; + uint256 internal constant VK_G2_ELEMENT_0_Y2 = 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa; + uint256 internal constant VK_G2_ELEMENT_1_X1 = 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1; + uint256 internal constant VK_G2_ELEMENT_1_X2 = 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0; + uint256 internal constant VK_G2_ELEMENT_1_Y1 = 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4; + uint256 internal constant VK_G2_ELEMENT_1_Y2 = 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55; + + // Memory slots from 0x000 to 0x200 are reserved for intermediate computations and call to precompiles. + + // ================Transcript================ + // ================Constants================ + uint256 internal constant ONE = 1; + uint256 internal constant DOMAIN_SIZE = 8388608; + uint256 internal constant OMEGA = 0x1283ba6f4b7b1a76ba2008fe823128bea4adb9269cbfd7c41c223be65bc60863; + // ========================================= + uint256 internal constant TRANSCRIPT_BEGIN_SLOT = 0x200; + uint256 internal constant TRANSCRIPT_DST_BYTE_SLOT = 0x203; + uint256 internal constant TRANSCRIPT_STATE_0_SLOT = 0x204; + uint256 internal constant TRANSCRIPT_STATE_1_SLOT = 0x224; + uint256 internal constant TRANSCRIPT_CHALLENGE_SLOT = 0x244; + + // ================PartialVerifierState================ + // copy-permutation challenges + uint256 internal constant PVS_BETA = 0x264 + 0x00; + uint256 internal constant PVS_GAMMA = 0x264 + 0x20; + // evaluation challenges + uint256 internal constant PVS_R = 0x264 + 0x40; + uint256 internal constant PVS_Z = 0x264 + 0x60; + uint256 internal constant PVS_Z_OMEGA = 0x264 + 0x80; + // aggregation challenge + uint256 internal constant PVS_ALPHA_0 = 0x264 + 0xa0; + uint256 internal constant PVS_ALPHA_1 = 0x264 + 0xc0; + // final evaluation challenge + uint256 internal constant PVS_Y = 0x264 + 0xe0; + // convenience + uint256 internal constant PVS_VANISHING_AT_Z = 0x264 + 0x100; + uint256 internal constant PVS_VANISHING_AT_Z_INV = 0x264 + 0x120; + uint256 internal constant PVS_L_0_AT_Z = 0x264 + 0x140; + uint256 internal constant MAIN_GATE_QUOTIENT_AT_Z = 0x264 + 0x160; + uint256 internal constant COPY_PERM_FIRST_QUOTIENT_AT_Z = 0x264 + 0x180; + uint256 internal constant COPY_PERM_SECOND_QUOTIENT_AT_Z = 0x264 + 0x1a0; + // ================Opening State================ + // h0, h1, h2, h2_shifted + uint256 internal constant OPS_OPENING_POINTS = 0x264 + 0x1c0 + 0x00; // 4 slots + uint256 internal constant OPS_Y_POWS = 0x264 + 0x1c0 + 0x80; // 9 SLOTS + + // ================Pairing State================ + + uint256 internal constant PS_VANISHING_AT_Y = 0x264 + 0x1c0 + 0x1a0; + uint256 internal constant PS_INV_ZTS0_AT_Y = 0x264 + 0x1c0 + 0x1c0; + uint256 internal constant PS_SET_DIFFERENCES_AT_Y = 0x264 + 0x1c0 + 0x1e0; // 3 slots + uint256 internal constant PS_MINUS_Z = 0x264 + 0x1c0 + 0x240; // 2 slots + uint256 internal constant PS_R_EVALS = 0x264 + 0x1c0 + 0x280; // 3 slots + + // ================In Memory(from Proof)================ + uint256 internal constant MEM_PROOF_PUBLIC_INPUT_SLOT = 0x264 + 0x1c0 + 0x2e0; + + uint256 internal constant MEM_PROOF_COMMITMENT_0_G1_X = 0x264 + 0x1c0 + 0x2e0 + 0x20; + uint256 internal constant MEM_PROOF_COMMITMENT_0_G1_Y = 0x264 + 0x1c0 + 0x2e0 + 0x40; + uint256 internal constant MEM_PROOF_COMMITMENT_1_G1_X = 0x264 + 0x1c0 + 0x2e0 + 0x60; + uint256 internal constant MEM_PROOF_COMMITMENT_1_G1_Y = 0x264 + 0x1c0 + 0x2e0 + 0x80; + uint256 internal constant MEM_PROOF_COMMITMENT_2_G1_X = 0x264 + 0x1c0 + 0x2e0 + 0xa0; + uint256 internal constant MEM_PROOF_COMMITMENT_2_G1_Y = 0x264 + 0x1c0 + 0x2e0 + 0xc0; + uint256 internal constant MEM_PROOF_COMMITMENT_3_G1_X = 0x264 + 0x1c0 + 0x2e0 + 0xe0; + uint256 internal constant MEM_PROOF_COMMITMENT_3_G1_Y = 0x264 + 0x1c0 + 0x2e0 + 0x100; + + uint256 internal constant MEM_PROOF_EVALUATIONS = 0x264 + 0x1c0 + 0x2e0 + 0x120; // 15 slots + + uint256 internal constant MEM_PROOF_MONTGOMERY_LAGRANGE_BASIS_INVERSE = 0x264 + 0x1c0 + 0x2e0 + 0x120 + 0x1e0; // 1 slots + + uint256 internal constant MEM_LAGRANGE_BASIS_DENOMS = 0x264 + 0x1c0 + 0x2e0 + 0x120 + 0x200; //18 slots + uint256 internal constant MEM_LAGRANGE_BASIS_DENOM_PRODUCTS = 0x264 + 0x1c0 + 0x2e0 + 0x120 + 0x440; // 18 slots + uint256 internal constant MEM_PROOF_LAGRANGE_BASIS_EVALS = 0x264 + 0x1c0 + 0x2e0 + 0x120 + 0x680; // 18 Slots + + // ================Constants================ + uint256 internal constant PROOF_PUBLIC_INPUTS_LENGTH = 1; + uint256 internal constant PROOF_LENGTH = 24; + uint256 internal constant PROOF_EVALUATIONS_LENGTH = 15; + uint256 internal constant TOTAL_LAGRANGE_BASIS_INVERSES_LENGTH = 18; + + /// @inheritdoc IVerifierV2 + function verificationKeyHash() external pure returns (bytes32 vkHash) { + return + keccak256( + abi.encodePacked( + VK_NUM_INPUTS, + VK_C0_G1_X, + VK_C0_G1_Y, + VK_NON_RESIDUES_0, + VK_NON_RESIDUES_1, + VK_G2_ELEMENT_0_X1, + VK_G2_ELEMENT_0_X2, + VK_G2_ELEMENT_0_Y1, + VK_G2_ELEMENT_0_Y2, + VK_G2_ELEMENT_1_X1, + VK_G2_ELEMENT_1_X2, + VK_G2_ELEMENT_1_Y1, + VK_G2_ELEMENT_1_Y2 + ) + ); + } + + /// @inheritdoc IVerifierV2 + function verify( + uint256[] calldata, // _publicInputs + uint256[] calldata // _proof + ) public view virtual returns (bool) { + // Beginning of the big inline assembly block that makes all the verification work. + // Note: We use the custom memory layout, so the return value should be returned from the assembly, not + // Solidity code. + assembly { + // load public inputs and proof from the calldata + load_inputs() + initialize_transcript() + // identities at verifier's point + compute_main_gate_quotient() + compute_copy_permutation_quotients() + // openings + initialize_opening_state() + // final pairing + let result := check_openings() + mstore(0, result) + return(0, 0x20) + + function load_inputs() { + // 1. Load public inputs + let publicInputOffset := calldataload(0x04) + let publicInputLengthInWords := calldataload(add(publicInputOffset, 0x04)) + // We expect only one public input + if iszero(eq(publicInputLengthInWords, PROOF_PUBLIC_INPUTS_LENGTH)) { + revertWithMessage(32, "public input length is incorrect") + } + mstore(MEM_PROOF_PUBLIC_INPUT_SLOT, mod(calldataload(add(publicInputOffset, 0x24)), R_MOD)) + + // 2. Load proof + let proofLengthOffset := calldataload(0x24) + let proofLengthInWords := calldataload(add(proofLengthOffset, 0x04)) + + if iszero(eq(proofLengthInWords, PROOF_LENGTH)) { + revertWithMessage(25, "proof length is incorrect") + } + let proofOffset := add(proofLengthOffset, 0x24) + // Note: We don't accept the point-at-infinity as a valid input for the commitments considering the security risks involved, + // as it may aid in proof manipulation and final pairing computation. + { + let x := mod(calldataload(proofOffset), Q_MOD) + let y := mod(calldataload(add(proofOffset, 0x20)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + if iszero(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD))) { + revertWithMessage(28, "commitment 0 is not on curve") + } + mstore(MEM_PROOF_COMMITMENT_0_G1_Y, y) + mstore(MEM_PROOF_COMMITMENT_0_G1_X, x) + } + { + let x := mod(calldataload(add(proofOffset, 0x40)), Q_MOD) + let y := mod(calldataload(add(proofOffset, 0x60)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + if iszero(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD))) { + revertWithMessage(28, "commitment 1 is not on curve") + } + mstore(MEM_PROOF_COMMITMENT_1_G1_Y, y) + mstore(MEM_PROOF_COMMITMENT_1_G1_X, x) + } + { + let x := mod(calldataload(add(proofOffset, 0x80)), Q_MOD) + let y := mod(calldataload(add(proofOffset, 0xa0)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + if iszero(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD))) { + revertWithMessage(28, "commitment 2 is not on curve") + } + mstore(MEM_PROOF_COMMITMENT_2_G1_Y, y) + mstore(MEM_PROOF_COMMITMENT_2_G1_X, x) + } + { + let x := mod(calldataload(add(proofOffset, 0xc0)), Q_MOD) + let y := mod(calldataload(add(proofOffset, 0xe0)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + if iszero(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD))) { + revertWithMessage(28, "commitment 3 is not on curve") + } + mstore(MEM_PROOF_COMMITMENT_3_G1_Y, y) + mstore(MEM_PROOF_COMMITMENT_3_G1_X, x) + } + proofOffset := add(proofOffset, 0x100) + + for { + let i := 0 + } lt(i, PROOF_EVALUATIONS_LENGTH) { + i := add(i, 1) + } { + let eval := mod(calldataload(add(proofOffset, mul(i, 0x20))), R_MOD) + let slot := add(MEM_PROOF_EVALUATIONS, mul(i, 0x20)) + mstore(slot, eval) + } + proofOffset := add(proofOffset, mul(PROOF_EVALUATIONS_LENGTH, 0x20)) + + mstore(MEM_PROOF_MONTGOMERY_LAGRANGE_BASIS_INVERSE, mod(calldataload(proofOffset), R_MOD)) + } + + /** + * @dev Commits data in the transcript then gets the challenges + * @notice that at this point, the transcript only has public inputs + * But luckily prover doesn't need any randomness in the first round + * so that prover has no control over the values because quotients are + * separated(there is no quotient aggregation neither in this round nor all rounds) + * + * w = 0x1283ba6f4b7b1a76ba2008fe823128bea4adb9269cbfd7c41c223be65bc60863 + */ + function initialize_transcript() { + if iszero(lt(DOMAIN_SIZE, R_MOD)) { + revertWithMessage(26, "Domain size >= R_MOD [ITS]") + } + if iszero(lt(OMEGA, R_MOD)) { + revertWithMessage(20, "Omega >= R_MOD [ITS]") + } + for { + let i := 0 + } lt(i, VK_NUM_INPUTS) { + i := add(i, 1) + } { + update_transcript(mload(add(MEM_PROOF_PUBLIC_INPUT_SLOT, mul(i, 0x20)))) + } + // commit first round commitment: preprocessed polynomials + update_transcript(VK_C0_G1_X) + update_transcript(VK_C0_G1_Y) + + // commit second round commitment: witnesses and gate identities + update_transcript(mload(MEM_PROOF_COMMITMENT_0_G1_X)) + update_transcript(mload(MEM_PROOF_COMMITMENT_0_G1_Y)) + + // copy-permutation challenges + mstore(PVS_BETA, get_challenge(0)) + mstore(PVS_GAMMA, get_challenge(1)) + // commit third round commitment: copy-perm + update_transcript(mload(MEM_PROOF_COMMITMENT_1_G1_X)) + update_transcript(mload(MEM_PROOF_COMMITMENT_1_G1_Y)) + // get evaluation challenge + // all system polynomials will be evaluated at z + // then combined polynomials will be opened at h_i = r^power_i + // then it becomes e.g C_i(X) = f_0(x^2) + x*f(x^2) in case of two polynomials + mstore(PVS_R, get_challenge(2)) + // commit all evaluations + for { + let i := 0 + } lt(i, PROOF_EVALUATIONS_LENGTH) { + i := add(i, 1) + } { + update_transcript(mload(add(MEM_PROOF_EVALUATIONS, mul(i, 0x20)))) + } + // get aggregation challenge + mstore(PVS_ALPHA_0, get_challenge(3)) + mstore(PVS_ALPHA_1, mulmod(mload(PVS_ALPHA_0), mload(PVS_ALPHA_0), R_MOD)) + // commit w(X) + update_transcript(mload(MEM_PROOF_COMMITMENT_2_G1_X)) + update_transcript(mload(MEM_PROOF_COMMITMENT_2_G1_Y)) + // opening challenge + mstore(PVS_Y, get_challenge(4)) + mstore(PVS_Z, modexp(mload(PVS_R), 24)) + // grand product of copy-permutation needs to be opened at shifted position + mstore(PVS_Z_OMEGA, mulmod(mload(PVS_Z), OMEGA, R_MOD)) + // Z_h(z) = X^N - 1 + mstore(PVS_VANISHING_AT_Z, addmod(modexp(mload(PVS_Z), DOMAIN_SIZE), sub(R_MOD, ONE), R_MOD)) + // L0(z) = 1/(N*(X-1)) * (X^N - 1) + mstore( + PVS_L_0_AT_Z, + modexp(mulmod(addmod(mload(PVS_Z), sub(R_MOD, ONE), R_MOD), DOMAIN_SIZE, R_MOD), sub(R_MOD, 2)) + ) + mstore(PVS_L_0_AT_Z, mulmod(mload(PVS_L_0_AT_Z), mload(PVS_VANISHING_AT_Z), R_MOD)) + mstore(PVS_VANISHING_AT_Z_INV, modexp(mload(PVS_VANISHING_AT_Z), sub(R_MOD, 2))) + } + + /** + * @dev Computes main gate quotient T0(ζ) + * T0(ζ) = (qm(ζ)*a(ζ)*b(ζ) + qa(ζ)*a(ζ) + qb(ζ)*b(ζ) + qc(ζ)*c(ζ) + qconst(ζ) + PI*L0(ζ)) * ZH(ζ)^-1 + */ + function compute_main_gate_quotient() { + // q_const + let rhs := mload(add(MEM_PROOF_EVALUATIONS, mul(4, 0x20))) + rhs := addmod(rhs, mulmod(mload(PVS_L_0_AT_Z), mload(MEM_PROOF_PUBLIC_INPUT_SLOT), R_MOD), R_MOD) + for { + let i := 0 + } lt(i, 3) { + i := add(i, 1) + } { + rhs := addmod( + rhs, + mulmod( + mload(add(MEM_PROOF_EVALUATIONS, mul(i, 0x20))), + mload(add(MEM_PROOF_EVALUATIONS, mul(add(8, i), 0x20))), + R_MOD + ), + R_MOD + ) + } + // q_m*A*B + rhs := mulmod( + addmod( + rhs, + mulmod( + mulmod( + mload(add(MEM_PROOF_EVALUATIONS, mul(3, 0x20))), + mload(add(MEM_PROOF_EVALUATIONS, mul(8, 0x20))), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(9, 0x20))), + R_MOD + ), + R_MOD + ), + mload(PVS_VANISHING_AT_Z_INV), + R_MOD + ) + mstore(MAIN_GATE_QUOTIENT_AT_Z, rhs) + } + + /** + * @dev Computes copy permutation quotients T1(ζ) & T2(ζ) + * T1(ζ) = ((z(ζ) * (a(ζ)+β*ζ+γ) * (b(ζ)+k1*β*ζ+γ) * (c(ζ)+k2*β*ζ+γ)) + * −(z(ζω) * (a(ζ)+β*sσ1(ζ)+γ) * (b(ζ)+β*sσ2(ζ)+γ) * (c(ζ)+β*sσ3(ζ)+γ)) * ZH(ζ)^-1 + * T2(ζ) = (z(ζ)−1)*L0(ζ)*ZH(ζ)^-1 + */ + function compute_copy_permutation_quotients() { + let tmp + let tmp2 + // (c(ζ)+k2*β*ζ+γ) + let rhs := addmod( + addmod( + mulmod(mulmod(mload(PVS_BETA), mload(PVS_Z), R_MOD), VK_NON_RESIDUES_1, R_MOD), + mload(PVS_GAMMA), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(add(8, 2), 0x20))), + R_MOD + ) + // (b(ζ)+k1*β*ζ+γ) + tmp := addmod( + addmod( + mulmod(mulmod(mload(PVS_BETA), mload(PVS_Z), R_MOD), VK_NON_RESIDUES_0, R_MOD), + mload(PVS_GAMMA), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(add(8, 1), 0x20))), + R_MOD + ) + // (b(ζ)+k1*β*ζ+γ) * (c(ζ)+k2*β*ζ+γ) + rhs := mulmod(rhs, tmp, R_MOD) + // (z(ζ) * (a(ζ)+β*ζ+γ) * (b(ζ)+k1*β*ζ+γ) * (c(ζ)+k2*β*ζ+γ) + rhs := mulmod( + mulmod( + rhs, + addmod( + addmod(mulmod(mload(PVS_BETA), mload(PVS_Z), R_MOD), mload(PVS_GAMMA), R_MOD), + mload(add(MEM_PROOF_EVALUATIONS, mul(8, 0x20))), + R_MOD + ), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(11, 0x20))), + R_MOD + ) + + // (z(ζω) * (b(ζ)+β*sσ2(ζ)+γ) * (c(ζ)+β*sσ3(ζ)+γ)) + tmp2 := mulmod( + mulmod( + addmod( + addmod( + mulmod(mload(PVS_BETA), mload(add(MEM_PROOF_EVALUATIONS, mul(add(5, 2), 0x20))), R_MOD), + mload(PVS_GAMMA), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(add(8, 2), 0x20))), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(12, 0x20))), + R_MOD + ), + addmod( + addmod( + mulmod(mload(PVS_BETA), mload(add(MEM_PROOF_EVALUATIONS, mul(add(5, 1), 0x20))), R_MOD), + mload(PVS_GAMMA), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(add(8, 1), 0x20))), + R_MOD + ), + R_MOD + ) + // (a(ζ)+β*sσ1(ζ)+γ) + tmp := addmod( + addmod( + mulmod(mload(PVS_BETA), mload(add(MEM_PROOF_EVALUATIONS, mul(5, 0x20))), R_MOD), + mload(PVS_GAMMA), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(8, 0x20))), + R_MOD + ) + // z(ζω) * (a(ζ)+β*sσ1(ζ)+γ) * (b(ζ)+β*sσ2(ζ)+γ) * (c(ζ)+β*sσ3(ζ)+γ) + tmp2 := mulmod(tmp2, tmp, R_MOD) + // −(z(ζω) * (a(ζ)+β*sσ1(ζ)+γ) * (b(ζ)+β*sσ2(ζ)+γ) * (c(ζ)+β*sσ3(ζ)+γ)) + tmp2 := sub(R_MOD, tmp2) + // ((z(ζ) * (a(ζ)+β*ζ+γ) * (b(ζ)+k1*β*ζ+γ) * (c(ζ)+k2*β*ζ+γ)) − (z(ζω) * (a(ζ)+β*sσ1(ζ)+γ) * (b(ζ)+β*sσ2(ζ)+γ) * (c(ζ)+β*sσ3(ζ)+γ)) * ZH(ζ)^-1 + rhs := mulmod(addmod(rhs, tmp2, R_MOD), mload(PVS_VANISHING_AT_Z_INV), R_MOD) + mstore(COPY_PERM_FIRST_QUOTIENT_AT_Z, rhs) + + // (z(ζ)−1)*L0(ζ)*ZH(ζ)^-1 + rhs := mulmod( + mulmod( + addmod(mload(add(MEM_PROOF_EVALUATIONS, mul(11, 0x20))), sub(R_MOD, 1), R_MOD), + mload(PVS_L_0_AT_Z), + R_MOD + ), + mload(PVS_VANISHING_AT_Z_INV), + R_MOD + ) + mstore(COPY_PERM_SECOND_QUOTIENT_AT_Z, rhs) + } + + /** + * @dev Computes partial lagrange basis evaluations Li(y)_numerator {i = [start..(start+num_polys))} using montgomery lagrange basis inverses sent with proof. + * Li(y)_numerator = (w_i * (y^{num_polys} - h^{num_polys})) + * Li(y)_denominator = (num_polys * h^{num_polys-1} * (y - (h * w_i))) + * Li(y) = Li(y)_numerator / Li(y)_denominator = (w_i * (y^{num_polys} - h^{num_polys})) / (num_polys * h^{num_polys-1} * (y - (h * w_i))) + * + * Also calculates the products of the denominators of the lagrange basis evaluations: + * Li(y)_denominators_product = Li(y)_previous_denominators_product * (∏(Li(y)_denominator {i = [start..(start+num_polys))})) + */ + function precompute_partial_lagrange_basis_evaluations(start, num_polys, y, omega, h, product) + -> interim_product + { + if gt(add(start, num_polys), TOTAL_LAGRANGE_BASIS_INVERSES_LENGTH) { + revertWithMessage(31, "Precompute Eval. Error [PLBEI1]") + } + let tmp := h + let loop_length := sub(num_polys, 2) + // h^{num_polys-1} + for { + let i := 0 + } lt(i, loop_length) { + i := add(i, 1) + } { + tmp := mulmod(tmp, h, R_MOD) + } + // num_polys * h^{num_polys-1} + let constant_part := mulmod(num_polys, tmp, R_MOD) + + // y^{num_polys} + let y_pow := mload(add(OPS_Y_POWS, mul(num_polys, 0x20))) + // h^{num_polys} + let num_at_y := mulmod(tmp, h, R_MOD) + // -h^{num_polys} + num_at_y := sub(R_MOD, num_at_y) + // (y^{num_polys} - h^{num_polys}) + num_at_y := addmod(num_at_y, y_pow, R_MOD) + + let current_omega := 1 + for { + let i := 0 + } lt(i, num_polys) { + i := add(i, 1) + } { + // h*w_i + tmp := mulmod(current_omega, h, R_MOD) + // -h*w_i + tmp := sub(R_MOD, tmp) + // y-(h*w_i) + tmp := addmod(tmp, y, R_MOD) + // (num_polys * h^{num_polys-1} * (y - (h * w_i))) + tmp := mulmod(tmp, constant_part, R_MOD) + + mstore(add(MEM_LAGRANGE_BASIS_DENOMS, mul(add(start, i), 0x20)), tmp) + + product := mulmod(product, tmp, R_MOD) + + mstore(add(MEM_LAGRANGE_BASIS_DENOM_PRODUCTS, mul(add(start, i), 0x20)), product) + // Li(y) = (W_i * (y^{num_polys} - h^{num_polys})) + mstore( + add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(start, i), 0x20)), + mulmod(num_at_y, current_omega, R_MOD) + ) + + // w_i {i = i+1} + current_omega := mulmod(current_omega, omega, R_MOD) + } + + interim_product := product + } + + /** + * @dev Computes partial lagrange basis evaluations Li(y)_numerator = {i = [start..(start+num_polys))} & Li(y)_numerator {i = [(start+num_polys)..(start+(2*num_polys)))} using montgomery lagrange basis inverses sent with proof. + * For Li(y)_numerator{i = [start..(start+num_polys))}: + * Li(y)_numerator = w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys}))) + * Li(y)_denominator = (num_polys * (h^{(2*num_polys)-1}-(h^{num_polys-1} * h_s^{num_polys})) * (y-(h*w_i))) + * Li(y) = Li(y)_numerator / Li(y)_denominator = (w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys})))) / (num_polys * (h^{(2*num_polys)-1}-(h^{num_polys-1} * h_s^{num_polys})) * (y-(h*w_i))) + * + * For Li(y)_numerator{i = [(start+num_polys)..(start+(2*num_polys)))} + * Li(y)_numerator = w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys}))) + * Li(y)_denominator = (num_polys * (h_s^{(2*num_polys)-1}-(h_s^{num_polys-1} * h^{num_polys})) * (y-(h_s*w_i))) + * Li(y) = Li(y)_numerator / Li(y)_denominator = (w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys}))) ) / (num_polys * (h_s^{(2*num_polys)-1}-(h_s^{num_polys-1} * h^{num_polys})) * (y-(h_s*w_i))) + * + * Also calculates the products of the denominators of the lagrange basis evaluations: + * Li(y)_denominators_product = Li(y)_previous_denominators_product * (∏(Li(y)_denominator {i = [start..(start+num_polys))})) * (∏(Li(y)_denominator {i = [(start+num_polys)..(start+(2*num_polys)))})) + */ + + function precompute_partial_lagrange_basis_evaluations_for_union_set( + start, + num_polys, + y, + omega, + h, + h_shifted, + product + ) -> final_product { + if gt(add(start, mul(2, num_polys)), TOTAL_LAGRANGE_BASIS_INVERSES_LENGTH) { + revertWithMessage(32, "Precompute Eval. Error [PLBEIU1]") + } + let h_pows_0 := h + let h_pows_1 := h_shifted + let loop_length := sub(num_polys, 2) + // h^{num_polys-1} & h_s^{num_polys-1} + for { + let i := 0 + } lt(i, loop_length) { + i := add(i, 1) + } { + h_pows_0 := mulmod(h_pows_0, h, R_MOD) + h_pows_1 := mulmod(h_pows_1, h_shifted, R_MOD) + } + let constant_parts_0 := h_pows_0 + let constant_parts_1 := h_pows_1 + // h^{num_polys} + h_pows_0 := mulmod(h_pows_0, h, R_MOD) + // h_s^{num_polys} + h_pows_1 := mulmod(h_pows_1, h_shifted, R_MOD) + + // h^{num_polys-1} * h_s^{num_polys} + constant_parts_0 := mulmod(constant_parts_0, h_pows_1, R_MOD) + // -h^{num_polys-1} * h_s^{num_polys} + constant_parts_0 := sub(R_MOD, constant_parts_0) + // h_s^{num_polys-1} * h^{num_polys} + constant_parts_1 := mulmod(constant_parts_1, h_pows_0, R_MOD) + // -h_s^{num_polys-1} * h^{num_polys} + constant_parts_1 := sub(R_MOD, constant_parts_1) + + // y^{num_polys} + let t_2 := mload(add(OPS_Y_POWS, mul(num_polys, 0x20))) + // h^{num_polys} * h_s^{num_polys} + let t_1 := mulmod(h_pows_0, h_pows_1, R_MOD) + // h^{num_polys} + h_s^{num_polys} + let t_0 := addmod(h_pows_0, h_pows_1, R_MOD) + // y^{num_polys} * (h^{num_polys} + h_s^{num_polys}) + t_0 := mulmod(t_0, t_2, R_MOD) + // - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys})) + t_0 := sub(R_MOD, t_0) + // h^{num_polys} * h_s^{num_polys} - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys})) + t_1 := addmod(t_1, t_0, R_MOD) + // y^{2*num_polys} + t_2 := mulmod(t_2, t_2, R_MOD) + // y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys})) + t_1 := addmod(t_1, t_2, R_MOD) + loop_length := sub(num_polys, 1) + // h^{(2*num_polys)-1} & h_s^{(2*num_polys)-1} + for { + let i := 0 + } lt(i, loop_length) { + i := add(i, 1) + } { + h_pows_0 := mulmod(h_pows_0, h, R_MOD) + h_pows_1 := mulmod(h_pows_1, h_shifted, R_MOD) + } + // h^{(2*num_polys)-1}-(h^{num_polys-1} * h_s^{num_polys}) + constant_parts_0 := addmod(constant_parts_0, h_pows_0, R_MOD) + // num_polys * (h^{(2*num_polys)-1}-(h^{num_polys-1} * h_s^{num_polys})) + constant_parts_0 := mulmod(constant_parts_0, num_polys, R_MOD) + // h_s^{(2*num_polys)-1}-(h_s^{num_polys-1} * h^{num_polys}) + constant_parts_1 := addmod(constant_parts_1, h_pows_1, R_MOD) + // num_polys * (h_s^{(2*num_polys)-1}-(h_s^{num_polys-1} * h^{num_polys})) + constant_parts_1 := mulmod(constant_parts_1, num_polys, R_MOD) + + let current_omega := 1 + let interim_product := product + for { + let i := 0 + } lt(i, num_polys) { + i := add(i, 1) + } { + t_0 := mulmod(current_omega, h, R_MOD) + t_0 := sub(R_MOD, t_0) + t_0 := addmod(t_0, y, R_MOD) + // (num_polys * (h^{(2*num_polys)-1}-(h^{num_polys-1} * h_s^{num_polys})) * (y-(h*w_i))) + t_0 := mulmod(t_0, constant_parts_0, R_MOD) + + mstore(add(MEM_LAGRANGE_BASIS_DENOMS, mul(add(start, i), 0x20)), t_0) + + interim_product := mulmod(interim_product, t_0, R_MOD) + + mstore(add(MEM_LAGRANGE_BASIS_DENOM_PRODUCTS, mul(add(start, i), 0x20)), interim_product) + // w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys}))) + mstore( + add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(start, i), 0x20)), + mulmod(t_1, current_omega, R_MOD) + ) + // w_i {i = i+1} + current_omega := mulmod(current_omega, omega, R_MOD) + } + + current_omega := 1 + for { + let i := 0 + } lt(i, num_polys) { + i := add(i, 1) + } { + t_0 := mulmod(current_omega, h_shifted, R_MOD) + t_0 := sub(R_MOD, t_0) + t_0 := addmod(t_0, y, R_MOD) + // (num_polys * (h_s^{(2*num_polys)-1}-(h_s^{num_polys-1} * h^{num_polys})) * (y-(h_s*w_i))) + t_0 := mulmod(t_0, constant_parts_1, R_MOD) + + mstore(add(MEM_LAGRANGE_BASIS_DENOMS, mul(add(add(start, num_polys), i), 0x20)), t_0) + + interim_product := mulmod(interim_product, t_0, R_MOD) + + mstore( + add(MEM_LAGRANGE_BASIS_DENOM_PRODUCTS, mul(add(add(start, num_polys), i), 0x20)), + interim_product + ) + // w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys}))) + mstore( + add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(start, num_polys), i), 0x20)), + mulmod(t_1, current_omega, R_MOD) + ) + // w_i {i = i+1} + current_omega := mulmod(current_omega, omega, R_MOD) + } + + final_product := interim_product + } + + /** + * @dev Computes lagrange basis evaluations using montgomery lagrange basis inverses sent with proof. + * @notice Check individual functions for more details + */ + function precompute_all_lagrange_basis_evaluations_from_inverses() { + let y := mload(PVS_Y) + // w8 = 0x2b337de1c8c14f22ec9b9e2f96afef3652627366f8170a0a948dad4ac1bd5e80 + // w4 = 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636 + // w3 = 0x0000000000000000b3c4d79d41a917585bfc41088d8daaa78b17ea66b99c90dd + let product_0_7 := precompute_partial_lagrange_basis_evaluations( + 0, + 8, + y, + 0x2b337de1c8c14f22ec9b9e2f96afef3652627366f8170a0a948dad4ac1bd5e80, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + 1 + ) + let product_0_11 := precompute_partial_lagrange_basis_evaluations( + 8, + 4, + y, + 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636, + mload(add(OPS_OPENING_POINTS, mul(1, 0x20))), + product_0_7 + ) + let product_0_17 := precompute_partial_lagrange_basis_evaluations_for_union_set( + add(8, 4), + 3, + y, + 0x0000000000000000b3c4d79d41a917585bfc41088d8daaa78b17ea66b99c90dd, + mload(add(OPS_OPENING_POINTS, mul(2, 0x20))), + mload(add(OPS_OPENING_POINTS, mul(3, 0x20))), + product_0_11 + ) + + let montgomery_inverse := mload(MEM_PROOF_MONTGOMERY_LAGRANGE_BASIS_INVERSE) + + if iszero(eq(mulmod(product_0_17, montgomery_inverse, R_MOD), 1)) { + revertWithMessage(30, "Precompute Eval. Error [PALBE]") + } + let temp := montgomery_inverse + let loop_length := sub(TOTAL_LAGRANGE_BASIS_INVERSES_LENGTH, 1) + for { + let i := loop_length + } gt(i, 0) { + i := sub(i, 1) + } { + mstore( + add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(i, 0x20)), + mulmod( + mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(i, 0x20))), + mulmod(mload(add(MEM_LAGRANGE_BASIS_DENOM_PRODUCTS, mul(sub(i, 1), 0x20))), temp, R_MOD), + R_MOD + ) + ) + temp := mulmod(temp, mload(add(MEM_LAGRANGE_BASIS_DENOMS, mul(i, 0x20))), R_MOD) + } + mstore( + add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(0, 0x20)), + mulmod(mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(0, 0x20))), temp, R_MOD) + ) + } + + /** + * @dev Computes opening points h0, h1, h2, h3 + */ + function compute_opening_points() { + // h = r^{power/num_polys} + let pvs_r := mload(PVS_R) + let r_2 := mulmod(pvs_r, pvs_r, R_MOD) + let r_3 := mulmod(r_2, pvs_r, R_MOD) + let r_6 := mulmod(r_3, r_3, R_MOD) + let r_8 := mulmod(r_6, r_2, R_MOD) + // h0 = pvs_r^3 + mstore(add(OPS_OPENING_POINTS, mul(0, 0x20)), r_3) + // h1 = pvs_r^6 + mstore(add(OPS_OPENING_POINTS, mul(1, 0x20)), r_6) + // h2 = pvs_r^8 + mstore(add(OPS_OPENING_POINTS, mul(2, 0x20)), r_8) + + // h3 (only round 2 needs opening at shifted point) + mstore( + add(OPS_OPENING_POINTS, mul(3, 0x20)), + mulmod(r_8, 0x0925f0bd364638ec3084b45fc27895f8f3f6f079096600fe946c8e9db9a47124, R_MOD) + ) + } + + /** + * @dev Initializes opening state OPS_Y_POWS[i] = y^i + * @notice only 9 powers are computed since the rest stay unused. + */ + function initialize_opening_state() { + compute_opening_points() + let acc := 1 + for { + let i := 0 + } lt(i, 9) { + i := add(i, 1) + } { + mstore(add(OPS_Y_POWS, mul(i, 0x20)), acc) + acc := mulmod(acc, mload(PVS_Y), R_MOD) + } + precompute_all_lagrange_basis_evaluations_from_inverses() + } + + /** + * @dev Computes r polynomial evaluations utilizing horner method + * (r*w)^{i}:{1, w*r, (w*r)^2, .. , (w*r)^{k-1}} + * horner: c0 + c1*(rw) + c2*(rw)^2 + c3*(rw)^3 -> (c0 + (rw)*(c1 + (rw)*(c2 + c3*(rw)))) + */ + function evaluate_r_polys_at_point_unrolled( + main_gate_quotient_at_z, + copy_perm_first_quotient_at_z, + copy_perm_second_quotient_at_z + ) { + let omega_h + let c + + // setup round + // r + + // w8^1 = 0x2b337de1c8c14f22ec9b9e2f96afef3652627366f8170a0a948dad4ac1bd5e80 + // w8^2 = 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636 + // w8^3 = 0x1d59376149b959ccbd157ac850893a6f07c2d99b3852513ab8d01be8e846a566 + // w8^4 = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000 + // w8^5 = 0x0530d09118705106cbb4a786ead16926d5d174e181a26686af5448492e42a181 + // w8^6 = 0x0000000000000000b3c4d79d41a91758cb49c3517c4604a520cff123608fc9cb + // w8^7 = 0x130b17119778465cfb3acaee30f81dee20710ead41671f568b11d9ab07b95a9b + omega_h := mload(add(OPS_OPENING_POINTS, mul(0, 0x20))) + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(0, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x2b337de1c8c14f22ec9b9e2f96afef3652627366f8170a0a948dad4ac1bd5e80, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(1, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(2, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x1d59376149b959ccbd157ac850893a6f07c2d99b3852513ab8d01be8e846a566, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(3, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(4, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x0530d09118705106cbb4a786ead16926d5d174e181a26686af5448492e42a181, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(5, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x0000000000000000b3c4d79d41a91758cb49c3517c4604a520cff123608fc9cb, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(6, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x130b17119778465cfb3acaee30f81dee20710ead41671f568b11d9ab07b95a9b, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(7, 0x20))), R_MOD), + R_MOD + ) + ) + + // first round + // r + + // w4^1 = 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636 + // w4^2 = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000 + // w4^3 = 0x0000000000000000b3c4d79d41a91758cb49c3517c4604a520cff123608fc9cb + omega_h := mload(add(OPS_OPENING_POINTS, mul(1, 0x20))) + + c := mulmod(main_gate_quotient_at_z, omega_h, R_MOD) + for { + let i := 1 + } lt(i, 3) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), i), 1), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), 3), 1), 0x20))), R_MOD) + + mstore( + add(PS_R_EVALS, mul(1, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(1, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(8, 0), 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636, + mload(add(OPS_OPENING_POINTS, mul(1, 0x20))), + R_MOD + ) + + c := mulmod(main_gate_quotient_at_z, omega_h, R_MOD) + for { + let i := 1 + } lt(i, 3) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), i), 1), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), 3), 1), 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(1, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(1, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(8, 1), 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000, + mload(add(OPS_OPENING_POINTS, mul(1, 0x20))), + R_MOD + ) + + c := mulmod(main_gate_quotient_at_z, omega_h, R_MOD) + for { + let i := 1 + } lt(i, 3) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), i), 1), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), 3), 1), 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(1, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(1, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(8, 2), 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x0000000000000000b3c4d79d41a91758cb49c3517c4604a520cff123608fc9cb, + mload(add(OPS_OPENING_POINTS, mul(1, 0x20))), + R_MOD + ) + + c := mulmod(main_gate_quotient_at_z, omega_h, R_MOD) + for { + let i := 1 + } lt(i, 3) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), i), 1), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), 3), 1), 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(1, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(1, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(8, 3), 0x20))), R_MOD), + R_MOD + ) + ) + + // second round + // c2 + // r + omega_h := mload(add(OPS_OPENING_POINTS, mul(2, 0x20))) + let omega_h_shifted := mload(add(OPS_OPENING_POINTS, mul(3, 0x20))) + c := mulmod(copy_perm_second_quotient_at_z, omega_h, R_MOD) + c := mulmod(addmod(c, copy_perm_first_quotient_at_z, R_MOD), omega_h, R_MOD) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(11, 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(8, 4), 0), 0x20))), R_MOD), + R_MOD + ) + ) + // c2 shifted + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 2), 0x20))), omega_h_shifted, R_MOD) + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 1), 0x20))), R_MOD), + omega_h_shifted, + R_MOD + ) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(12, 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod( + c, + mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(add(8, 4), 3), 0), 0x20))), + R_MOD + ), + R_MOD + ) + ) + // c2 + omega_h := mulmod( + 0x0000000000000000b3c4d79d41a917585bfc41088d8daaa78b17ea66b99c90dd, + mload(add(OPS_OPENING_POINTS, mul(2, 0x20))), + R_MOD + ) + omega_h_shifted := mulmod( + 0x0000000000000000b3c4d79d41a917585bfc41088d8daaa78b17ea66b99c90dd, + mload(add(OPS_OPENING_POINTS, mul(3, 0x20))), + R_MOD + ) + + c := mulmod(copy_perm_second_quotient_at_z, omega_h, R_MOD) + c := mulmod(addmod(c, copy_perm_first_quotient_at_z, R_MOD), omega_h, R_MOD) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(11, 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(8, 4), 1), 0x20))), R_MOD), + R_MOD + ) + ) + // c2 shifted + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 2), 0x20))), omega_h_shifted, R_MOD) + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 1), 0x20))), R_MOD), + omega_h_shifted, + R_MOD + ) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(12, 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod( + c, + mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(add(8, 4), 3), 1), 0x20))), + R_MOD + ), + R_MOD + ) + ) + // c2 + omega_h := mulmod( + 0x30644e72e131a029048b6e193fd84104cc37a73fec2bc5e9b8ca0b2d36636f23, + mload(add(OPS_OPENING_POINTS, mul(2, 0x20))), + R_MOD + ) + omega_h_shifted := mulmod( + 0x30644e72e131a029048b6e193fd84104cc37a73fec2bc5e9b8ca0b2d36636f23, + mload(add(OPS_OPENING_POINTS, mul(3, 0x20))), + R_MOD + ) + + c := mulmod(copy_perm_second_quotient_at_z, omega_h, R_MOD) + c := mulmod(addmod(c, copy_perm_first_quotient_at_z, R_MOD), omega_h, R_MOD) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(11, 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(8, 4), 2), 0x20))), R_MOD), + R_MOD + ) + ) + // c2 shifted + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 2), 0x20))), omega_h_shifted, R_MOD) + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 1), 0x20))), R_MOD), + omega_h_shifted, + R_MOD + ) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(12, 0x20))), R_MOD) + + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod( + c, + mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(add(8, 4), 3), 2), 0x20))), + R_MOD + ), + R_MOD + ) + ) + } + + /** + * @dev Computes the openings and returns the result of pairing computation + */ + function check_openings() -> out { + // f(X) = (Z_{T\S0}(y) * (C0(X) - r0(y))) + (alpha*(Z_{T\S1}(y)*(C1(X) - r1(y)))) + (alpha^{2}*(Z_{T\S2}(y)*(C2(X) - r2(y)))) + // Note that, in our case set differences(Z_T\{S_i}) are: + // - Z_{T\S0}(y): (y^{k1}-ζ)*(y^{k2}-ζ)*(y^{k2}-(ζ*w)) + // - Z_{T\S1}(y): (y^{k0}-ζ)*(y^{k2}-ζ)*(y^{k2}-(ζ*w)) + // - Z_{T\S2}(y): (y^{k0}-ζ)*(y^{k1}-ζ) where + // k0=8, k1=4, and k2=3 are number of the polynomials for setup, first and second round respectively + + let tmp + evaluate_r_polys_at_point_unrolled( + mload(MAIN_GATE_QUOTIENT_AT_Z), + mload(COPY_PERM_FIRST_QUOTIENT_AT_Z), + mload(COPY_PERM_SECOND_QUOTIENT_AT_Z) + ) + + // -ζ + mstore(add(PS_MINUS_Z, mul(0, 0x20)), sub(R_MOD, mload(PVS_Z))) + // -(ζ*w) + mstore(add(PS_MINUS_Z, mul(1, 0x20)), sub(R_MOD, mload(PVS_Z_OMEGA))) + + // Z_{T\S0}(y) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20)), + addmod(mload(add(OPS_Y_POWS, mul(3, 0x20))), mload(add(PS_MINUS_Z, mul(1, 0x20))), R_MOD) + ) + tmp := addmod(mload(add(OPS_Y_POWS, mul(3, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20)), + mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20))), tmp, R_MOD) + ) + tmp := addmod(mload(add(OPS_Y_POWS, mul(4, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20)), + mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20))), tmp, R_MOD) + ) + mstore(PS_VANISHING_AT_Y, mload(add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20)))) + mstore(PS_INV_ZTS0_AT_Y, modexp(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20))), sub(R_MOD, 2))) + + // Z_{T\S1}(y) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20)), + addmod(mload(add(OPS_Y_POWS, mul(3, 0x20))), mload(add(PS_MINUS_Z, mul(1, 0x20))), R_MOD) + ) + tmp := addmod(mload(add(OPS_Y_POWS, mul(3, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20)), + mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20))), tmp, R_MOD) + ) + tmp := addmod(mload(add(OPS_Y_POWS, mul(8, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20)), + mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20))), tmp, R_MOD) + ) + mstore(PS_VANISHING_AT_Y, mulmod(mload(PS_VANISHING_AT_Y), tmp, R_MOD)) + + // // Z_{T\S2}(y) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(2, 0x20)), + addmod(mload(add(OPS_Y_POWS, mul(4, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + ) + tmp := addmod(mload(add(OPS_Y_POWS, mul(8, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(2, 0x20)), + mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(2, 0x20))), tmp, R_MOD) + ) + + // W(X) = f(X) / Z_T(y) where Z_T(y) = (y^{k0}-ζ)*(y^{k1}-ζ)*(y^{k2}-ζ)*(y^{k2}-(ζ*w)) + // we need to check that + // f(X) - W(X) * Z_T(y) = 0 + + // W'(X) = L(X) / (Z_{T\S0}(y)*(X-y)) + // L(X)/Z_{T\S0}(y) = (C0(X) - r0(y)) + (alpha*(Z_{T\S1}(y)/Z_{T\S0}(y))*(C1(X) - r1(y))) + (alpha^{2}*(Z_{T\S2}(y)/Z_{T\S0}(y))*(C2(X) - r2(y))) - ((Z_T(y)/Z_{T\S0}(y))*W(X)) + + // the identity check is reduced into following + // L(X) - W'(X)*Z_{T\S0}(y)(X-y) == 0 + // verifier has commitments to the C_i(X) polynomials + // verifer also recomputed r_i(y) + // group constant and commitment parts + // first prepare L(X)/Z_{T\S0}(y) + // C(X) = C0(X) + ((alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*C1(X)) + ((alpha^2*Z_{T\S2}(y)/Z_{T\S0}(y))*C2(X)) + // r(y) = r0(y) + ((alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*r1(y)) + ((alpha^2*Z_{T\S2}(y)/Z_{T\S0}(y))*r2(y)) + // now construct + // L(X)/Z_{T\S0}(y) = C(X) - r(y) - ((Z_T(y)/Z_{T\S0}(y))*W(X)) + // now check following identity + // C(X) - r(y) - ((Z_t(y)/Z_{T\S0}(y))*W(X)) - (W'(X)*(X-y)) = 0 + // [C(X)] - [r(y)*G1] - (Z_T(y)/Z_{T\S0}(y))*[W] - [(X-y)*W'] = 0 + // [C(X)] - [r(y)*G1] - (Z_T(y)/Z_{T\S0}(y))*[W] - [X*W'] + [y*W]' = 0 + // [C(X)] - [r(y)*G1] - (Z_T(y)/Z_{T\S0}(y))*[W] + [y*W'] - [X*W'] = 0 + // points with X will be multiplied in the exponent via pairing + // so final pairing would ne + // e([C(X)] - [r(y)*G1] - [Z_T(y)/(Z_{T\S0}(y)*W)] + [y*W'], G2)*e(-W', X*G2) = 1 + + // C0 + let ps_aggregated_commitment_g1_x := VK_C0_G1_X + let ps_aggregated_commitment_g1_y := VK_C0_G1_Y + + // ((alpha^{2}*Z_{T\S2}(y))/Z_{T\S0}(y)) + let aggregated_r_at_y := mulmod( + mload(add(PS_SET_DIFFERENCES_AT_Y, mul(2, 0x20))), + mload(PS_INV_ZTS0_AT_Y), + R_MOD + ) + aggregated_r_at_y := mulmod(aggregated_r_at_y, mload(PVS_ALPHA_1), R_MOD) + + // ((alpha^{2}*Z_{T\S2}(y))/Z_{T\S0}(y))*C2 + let tp_g1_x, tp_g1_y := point_mul( + mload(MEM_PROOF_COMMITMENT_1_G1_X), + mload(MEM_PROOF_COMMITMENT_1_G1_Y), + aggregated_r_at_y + ) + // c0 + (((alpha^{2}*Z_{T\S2}(y))/Z_{T\S0}(y))*C2) + ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y := point_add( + ps_aggregated_commitment_g1_x, + ps_aggregated_commitment_g1_y, + tp_g1_x, + tp_g1_y + ) + // ((alpha^{2}*Z_{T\S2}(y))/Z_{T\S0}(y))*r2 + aggregated_r_at_y := mulmod(aggregated_r_at_y, mload(add(PS_R_EVALS, mul(2, 0x20))), R_MOD) + + // (alpha*Z_{T\S1}(y)/Z_{T\S0}(y)) + tmp := mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20))), mload(PS_INV_ZTS0_AT_Y), R_MOD) + tmp := mulmod(tmp, mload(PVS_ALPHA_0), R_MOD) + + // (alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*C1 + tp_g1_x, tp_g1_y := point_mul( + mload(MEM_PROOF_COMMITMENT_0_G1_X), + mload(MEM_PROOF_COMMITMENT_0_G1_Y), + tmp + ) + // c0 + ((alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*C1) + (((alpha^{2}*Z_{T\S2}(y))/Z_{T\S0}(y))*C2) + ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y := point_add( + ps_aggregated_commitment_g1_x, + ps_aggregated_commitment_g1_y, + tp_g1_x, + tp_g1_y + ) + // (alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*r1 + tmp := mulmod(tmp, mload(add(PS_R_EVALS, mul(1, 0x20))), R_MOD) + // ((alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*r1) + ((alpha^{2}*Z_{T\S2}(y)/Z_{T\S0}(y))*r2) + aggregated_r_at_y := addmod(aggregated_r_at_y, tmp, R_MOD) + // r0 + (alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*r1 + ((alpha^{2}*Z_{T\S2}(y)/Z_{T\S0}(y))*r2) + aggregated_r_at_y := addmod(aggregated_r_at_y, mload(PS_R_EVALS), R_MOD) + tp_g1_x, tp_g1_y := point_mul(1, 2, aggregated_r_at_y) + ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y := point_sub( + ps_aggregated_commitment_g1_x, + ps_aggregated_commitment_g1_y, + tp_g1_x, + tp_g1_y + ) + // - ((Z_T(y)/Z_{T\S0}(y))*W(X)) + mstore(PS_VANISHING_AT_Y, mulmod(mload(PS_VANISHING_AT_Y), mload(PS_INV_ZTS0_AT_Y), R_MOD)) + tp_g1_x, tp_g1_y := point_mul( + mload(MEM_PROOF_COMMITMENT_2_G1_X), + mload(MEM_PROOF_COMMITMENT_2_G1_Y), + mload(PS_VANISHING_AT_Y) + ) + ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y := point_sub( + ps_aggregated_commitment_g1_x, + ps_aggregated_commitment_g1_y, + tp_g1_x, + tp_g1_y + ) + // L(X)/Z_{T\S0}(y) is aggregated + + // Now check W'(X) = L(X) / (Z_{T\S0}(y)*(x-y)) + // L(X)/Z_{T\S0}(y) + (y*W'(X)) - (x*W'(X)) = 0 + tp_g1_x, tp_g1_y := point_mul( + mload(MEM_PROOF_COMMITMENT_3_G1_X), + mload(MEM_PROOF_COMMITMENT_3_G1_Y), + mload(PVS_Y) + ) + ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y := point_add( + ps_aggregated_commitment_g1_x, + ps_aggregated_commitment_g1_y, + tp_g1_x, + tp_g1_y + ) + let is_zero_commitment + if iszero(mload(MEM_PROOF_COMMITMENT_3_G1_Y)) { + if gt(mload(MEM_PROOF_COMMITMENT_3_G1_X), 0) { + revertWithMessage(21, "non zero x value [CO]") + } + is_zero_commitment := 1 + } + + out := pairing_check(ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y, is_zero_commitment) + } + + /** + * @dev Generates the rolling hash using `val` and updates the transcript. + * The computation is done as follows: + * new_state_0 = keccak256(uint32(0) || old_state_0 || old_state_1 || value) + * new_state_1 = keccak256(uint32(1) || old_state_0 || old_state_1 || value) + * + * @notice The computation assumes that the memory slots 0x200 - 0x202 are clean and doesn't explicitly clean them + */ + function update_transcript(value) { + mstore8(TRANSCRIPT_DST_BYTE_SLOT, 0x00) + mstore(TRANSCRIPT_CHALLENGE_SLOT, value) + let newState0 := keccak256(TRANSCRIPT_BEGIN_SLOT, 0x64) + mstore8(TRANSCRIPT_DST_BYTE_SLOT, 0x01) + let newState1 := keccak256(TRANSCRIPT_BEGIN_SLOT, 0x64) + mstore(TRANSCRIPT_STATE_1_SLOT, newState1) + mstore(TRANSCRIPT_STATE_0_SLOT, newState0) + } + + /** + * @dev Generates a new challenge with (uint32(2) || state_0 || state_1 || uint32(challenge_counter)) + * The challenge_counter is incremented after every challenge + */ + function get_challenge(challenge_counter) -> challenge { + mstore8(TRANSCRIPT_DST_BYTE_SLOT, 0x02) + mstore(TRANSCRIPT_CHALLENGE_SLOT, shl(224, challenge_counter)) + challenge := and(keccak256(TRANSCRIPT_BEGIN_SLOT, 0x48), FR_MASK) + } + + /** + * @dev Performs scalar multiplication: point * scalar -> t + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + */ + function point_mul(p_x, p_y, s) -> t_x, t_y { + mstore(0x80, p_x) + mstore(0xa0, p_y) + mstore(0xc0, s) + + let success := staticcall(gas(), 7, 0x80, 0x60, 0x80, 0x40) + if iszero(success) { + revertWithMessage(27, "point multiplication failed") + } + t_x := mload(0x80) + t_y := mload(add(0x80, 0x20)) + } + + /** + * @dev Performs point addition: point 1 + point 2 -> t + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + */ + function point_add(p1_x, p1_y, p2_x, p2_y) -> t_x, t_y { + mstore(0x80, p1_x) + mstore(0xa0, p1_y) + mstore(0xc0, p2_x) + mstore(0xe0, p2_y) + + let success := staticcall(gas(), 6, 0x80, 0x80, 0x80, 0x40) + if iszero(success) { + revertWithMessage(21, "point addition failed") + } + + t_x := mload(0x80) + t_y := mload(add(0x80, 0x20)) + } + + /** + * @dev Performs point subtraction: point 1 + point 2 -> t + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + * @notice We don't consider the highly unlikely case where p2 can be a point-at-infinity and the function would revert. + */ + function point_sub(p1_x, p1_y, p2_x, p2_y) -> t_x, t_y { + mstore(0x80, p1_x) + mstore(0xa0, p1_y) + mstore(0xc0, p2_x) + mstore(0xe0, sub(Q_MOD, p2_y)) + + let success := staticcall(gas(), 6, 0x80, 0x80, 0x80, 0x40) + if iszero(success) { + revertWithMessage(24, "point subtraction failed") + } + + t_x := mload(0x80) + t_y := mload(add(0x80, 0x20)) + } + + /** + * @dev Calculates EC Pairing result following the EIP-197: https://eips.ethereum.org/EIPS/eip-197 + * Performs point negation before pairing calculation, if the flag `is_zero_commitment` is true + * + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + * While code reformatting consider not to overwrite the first constant-defined memory location, which is currently + * TRANSCRIPT_BEGIN_SLOT = 0x200 + */ + function pairing_check(p1_x, p1_y, is_zero_commitment) -> res { + mstore(0x80, p1_x) + mstore(0xa0, p1_y) + mstore(0xc0, VK_G2_ELEMENT_0_X1) + mstore(0xe0, VK_G2_ELEMENT_0_X2) + mstore(0x100, VK_G2_ELEMENT_0_Y1) + mstore(0x120, VK_G2_ELEMENT_0_Y2) + mstore(0x140, mload(MEM_PROOF_COMMITMENT_3_G1_X)) + mstore(0x160, mload(MEM_PROOF_COMMITMENT_3_G1_Y)) + if iszero(is_zero_commitment) { + mstore(0x160, sub(Q_MOD, mload(MEM_PROOF_COMMITMENT_3_G1_Y))) + } + mstore(0x180, VK_G2_ELEMENT_1_X1) + mstore(0x1a0, VK_G2_ELEMENT_1_X2) + mstore(0x1c0, VK_G2_ELEMENT_1_Y1) + mstore(0x1e0, VK_G2_ELEMENT_1_Y2) + + let success := staticcall(gas(), 8, 0x80, mul(12, 0x20), 0x80, 0x20) + + if iszero(success) { + revertWithMessage(20, "pairing check failed") + } + res := mload(0x80) + } + + /** + * @dev Reverts with the desired custom error string. + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + */ + function revertWithMessage(len, reason) { + // "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x80, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // Data offset + mstore(0x84, 0x0000000000000000000000000000000000000000000000000000000000000020) + // Length of revert string + mstore(0xa4, len) + // Revert reason + mstore(0xc4, reason) + // Revert + revert(0x80, 0x64) + } + + /** + * @dev Performs modular exponentiation using the formula (value ^ power) mod R_MOD. + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + */ + function modexp(value, power) -> res { + mstore(0x80, 0x20) + mstore(0xa0, 0x20) + mstore(0xc0, 0x20) + mstore(0xe0, value) + mstore(0x100, power) + mstore(0x120, R_MOD) + if iszero(staticcall(gas(), 5, 0x80, 0xc0, 0x80, 0x20)) { + revertWithMessage(24, "modexp precompile failed") + } + res := mload(0x80) + } + } + } +} diff --git a/l1-contracts/contracts/state-transition/Verifier.sol b/l1-contracts/contracts/state-transition/verifiers/VerifierPlonk.sol similarity index 99% rename from l1-contracts/contracts/state-transition/Verifier.sol rename to l1-contracts/contracts/state-transition/verifiers/VerifierPlonk.sol index a74ecb12c..a47208bcb 100644 --- a/l1-contracts/contracts/state-transition/Verifier.sol +++ b/l1-contracts/contracts/state-transition/verifiers/VerifierPlonk.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.24; -import {IVerifier} from "./chain-interfaces/IVerifier.sol"; +import {IVerifier} from "../chain-interfaces/IVerifier.sol"; /* solhint-disable max-line-length */ /// @author Matter Labs @@ -18,7 +18,7 @@ import {IVerifier} from "./chain-interfaces/IVerifier.sol"; /// * Plonk for ZKsync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf /// The notation used in the code is the same as in the papers. /* solhint-enable max-line-length */ -contract Verifier is IVerifier { +contract VerifierPlonk is IVerifier { /*////////////////////////////////////////////////////////////// Verification keys //////////////////////////////////////////////////////////////*/ diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index 416ffd6a3..7fa5a3ff9 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -10,8 +10,10 @@ import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/tran import {Utils} from "./Utils.sol"; import {Multicall3} from "contracts/dev-contracts/Multicall3.sol"; -import {Verifier} from "contracts/state-transition/Verifier.sol"; -import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {DualVerifier} from "contracts/state-transition/verifiers/DualVerifier.sol"; +import {VerifierPlonk} from "contracts/state-transition/verifiers/VerifierPlonk.sol"; +import {VerifierFFLONK} from "contracts/state-transition/verifiers/VerifierFFLONK.sol"; +import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; import {DefaultUpgrade} from "contracts/upgrades/DefaultUpgrade.sol"; import {Governance} from "contracts/governance/Governance.sol"; @@ -253,17 +255,32 @@ contract DeployL1Script is Script { } function deployVerifier() internal { + address verifierFflonk = deployVerifierFflonk(); + address verifierPlonk = deployVerifierPlonk(); bytes memory code; if (config.testnetVerifier) { code = type(TestnetVerifier).creationCode; } else { - code = type(Verifier).creationCode; + code = type(DualVerifier).creationCode; } + code = abi.encodePacked(code, abi.encode(verifierFflonk, verifierPlonk)); address contractAddress = deployViaCreate2(code); - console.log("Verifier deployed at:", contractAddress); + console.log("Dual verifier deployed at:", contractAddress); addresses.stateTransition.verifier = contractAddress; } + function deployVerifierFflonk() internal returns (address contractAddress) { + bytes memory code = type(VerifierFflonk).creationCode; + contractAddress = deployViaCreate2(code); + console.log("FFLONK verifier deployed at:", contractAddress); + } + + function deployVerifierPlonk() internal returns (address contractAddress) { + bytes memory code = type(VerifierPlonk).creationCode; + contractAddress = deployViaCreate2(code); + console.log("Plonk verifier deployed at:", contractAddress); + } + function deployDefaultUpgrade() internal { address contractAddress = deployViaCreate2(type(DefaultUpgrade).creationCode); console.log("DefaultUpgrade deployed at:", contractAddress); diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol index 6c6d8a935..def603caf 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Executor/ExecutorProof.t.sol @@ -9,7 +9,9 @@ import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; import {IExecutor, LogProcessingOutput} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; -import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {IVerifierV2} from "contracts/state-transition/chain-interfaces/IVerifierV2.sol"; +import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; contract TestExecutorFacet is ExecutorFacet { function createBatchCommitment( @@ -42,7 +44,7 @@ contract TestExecutorFacet is ExecutorFacet { contract ExecutorProofTest is Test { UtilsFacet internal utilsFacet; TestExecutorFacet internal executor; - address internal testnetVerifier = address(new TestnetVerifier()); + address internal testnetVerifier = address(new TestnetVerifier(IVerifierV2(address(0)), IVerifier(address(0)))); function getTestExecutorFacetSelectors() private pure returns (bytes4[] memory) { bytes4[] memory selectors = new bytes4[](3); diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol index d81e9cc30..49bca4eb6 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol @@ -18,8 +18,9 @@ import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox import {InitializeData} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; +import {IVerifierV2} from "contracts/state-transition/chain-interfaces/IVerifierV2.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; contract ExecutorTest is Test { address internal owner; @@ -155,8 +156,7 @@ contract ExecutorTest is Test { timestamp: 0, commitment: bytes32("") }); - - TestnetVerifier testnetVerifier = new TestnetVerifier(); + TestnetVerifier testnetVerifier = new TestnetVerifier(IVerifierV2(address(0)), IVerifier(address(0))); InitializeData memory params = InitializeData({ // TODO REVIEW diff --git a/l1-contracts/test/foundry/unit/concrete/Verifier/Verifier.t.sol b/l1-contracts/test/foundry/unit/concrete/Verifier/PlonkVerifier.t.sol similarity index 96% rename from l1-contracts/test/foundry/unit/concrete/Verifier/Verifier.t.sol rename to l1-contracts/test/foundry/unit/concrete/Verifier/PlonkVerifier.t.sol index 54ab49974..f6429ac64 100644 --- a/l1-contracts/test/foundry/unit/concrete/Verifier/Verifier.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Verifier/PlonkVerifier.t.sol @@ -3,10 +3,10 @@ pragma solidity 0.8.24; import {Test} from "forge-std/Test.sol"; -import {Verifier} from "contracts/state-transition/Verifier.sol"; -import {VerifierTest} from "contracts/dev-contracts/test/VerifierTest.sol"; +import {VerifierPlonk} from "contracts/state-transition/verifiers/VerifierPlonk.sol"; +import {PlonkVerifierTest} from "contracts/dev-contracts/test/PlonkVerifierTest.sol"; -contract VerifierTestTest is Test { +contract PlonkVerifierTestTest is Test { uint256 Q_MOD = 21888242871839275222246405745257275088696311157297823662689037894645226208583; uint256 R_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; @@ -14,7 +14,7 @@ contract VerifierTestTest is Test { uint256[] public serializedProof; uint256[] public recursiveAggregationInput; - Verifier public verifier; + VerifierPlonk public verifier; function setUp() public virtual { publicInputs.push(17257057577815541751225964212897374444694342989384539141520877492729); @@ -64,7 +64,7 @@ contract VerifierTestTest is Test { serializedProof.push(7419167499813234488108910149511390953153207250610705609008080038658070088540); serializedProof.push(11628425014048216611195735618191126626331446742771562481735017471681943914146); - verifier = new VerifierTest(); + verifier = new PlonkVerifierTest(); } function testShouldVerify() public view { diff --git a/l1-contracts/test/foundry/unit/concrete/Verifier/VerifierRecursive.t.sol b/l1-contracts/test/foundry/unit/concrete/Verifier/PlonkVerifierRecursive.t.sol similarity index 88% rename from l1-contracts/test/foundry/unit/concrete/Verifier/VerifierRecursive.t.sol rename to l1-contracts/test/foundry/unit/concrete/Verifier/PlonkVerifierRecursive.t.sol index 69bad2303..b87664efa 100644 --- a/l1-contracts/test/foundry/unit/concrete/Verifier/VerifierRecursive.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Verifier/PlonkVerifierRecursive.t.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {VerifierTestTest} from "./Verifier.t.sol"; -import {VerifierRecursiveTest} from "contracts/dev-contracts/test/VerifierRecursiveTest.sol"; +import {PlonkVerifierTestTest} from "./PlonkVerifier.t.sol"; +import {PlonkVerifierRecursiveTest} from "contracts/dev-contracts/test/PlonkVerifierRecursiveTest.sol"; -contract VerifierRecursiveTestTest is VerifierTestTest { +contract PlonkVerifierRecursiveTestTest is PlonkVerifierTestTest { function setUp() public override { super.setUp(); @@ -13,7 +13,7 @@ contract VerifierRecursiveTestTest is VerifierTestTest { recursiveAggregationInput.push(16188304989094043810949359833767911976672882599560690320245309499206765021563); recursiveAggregationInput.push(3201093556796962656759050531176732990872300033146738631772984017549903765305); - verifier = new VerifierRecursiveTest(); + verifier = new PlonkVerifierRecursiveTest(); } function testMoreThan4WordsRecursiveInput_shouldRevert() public { diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol index 999336642..b42493e72 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/_StateTransitionManager_Shared.t.sol @@ -17,8 +17,10 @@ import {GenesisUpgrade} from "contracts/upgrades/GenesisUpgrade.sol"; import {InitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; import {StateTransitionManager} from "contracts/state-transition/StateTransitionManager.sol"; import {StateTransitionManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IStateTransitionManager.sol"; -import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; import {ZeroAddress} from "contracts/common/L1ContractErrors.sol"; +import {IVerifierV2} from "contracts/state-transition/chain-interfaces/IVerifierV2.sol"; +import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; contract StateTransitionManagerTest is Test { StateTransitionManager internal stateTransitionManager; @@ -33,7 +35,7 @@ contract StateTransitionManagerTest is Test { address internal constant validator = address(0x5050505); address internal newChainAdmin; uint256 chainId = block.chainid; - address internal testnetVerifier = address(new TestnetVerifier()); + address internal testnetVerifier = address(new TestnetVerifier(IVerifierV2(address(0)), IVerifier(address(0)))); Diamond.FacetCut[] internal facetCuts; diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol index 8a50fd5d5..732234a40 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondInit/_DiamondInit_Shared.t.sol @@ -7,11 +7,13 @@ import {Utils} from "foundry-test/unit/concrete/Utils/Utils.sol"; import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; -import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; +import {IVerifierV2} from "contracts/state-transition/chain-interfaces/IVerifierV2.sol"; +import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; contract DiamondInitTest is Test { Diamond.FacetCut[] internal facetCuts; - address internal testnetVerifier = address(new TestnetVerifier()); + address internal testnetVerifier = address(new TestnetVerifier(IVerifierV2(address(0)), IVerifier(address(0)))); function setUp() public virtual { facetCuts.push( diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol index 4637faabd..a4750542d 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/DiamondProxy/DiamondProxy.t.sol @@ -11,8 +11,10 @@ import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; import {ZkSyncHyperchainBase} from "contracts/state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; -import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; import {FacetIsFrozen, ValueMismatch, InvalidSelector} from "contracts/common/L1ContractErrors.sol"; +import {IVerifierV2} from "contracts/state-transition/chain-interfaces/IVerifierV2.sol"; +import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; contract TestFacet is ZkSyncHyperchainBase { function func() public pure returns (bool) { @@ -25,7 +27,7 @@ contract TestFacet is ZkSyncHyperchainBase { contract DiamondProxyTest is Test { Diamond.FacetCut[] internal facetCuts; - address internal testnetVerifier = address(new TestnetVerifier()); + address internal testnetVerifier = address(new TestnetVerifier(IVerifierV2(address(0)), IVerifier(address(0)))); function getTestFacetSelectors() public pure returns (bytes4[] memory selectors) { selectors = new bytes4[](1); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol index a4419a342..d158ddd90 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Admin/_Admin_Shared.t.sol @@ -9,12 +9,14 @@ import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; -import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; +import {IVerifierV2} from "contracts/state-transition/chain-interfaces/IVerifierV2.sol"; +import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; contract AdminTest is Test { IAdmin internal adminFacet; UtilsFacet internal utilsFacet; - address internal testnetVerifier = address(new TestnetVerifier()); + address internal testnetVerifier = address(new TestnetVerifier(IVerifierV2(address(0)), IVerifier(address(0)))); function getAdminSelectors() public pure returns (bytes4[] memory) { bytes4[] memory selectors = new bytes4[](12); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol index 15fa32883..96c3e5c44 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Base/_Base_Shared.t.sol @@ -8,7 +8,9 @@ import {UtilsFacet} from "foundry-test/unit/concrete/Utils/UtilsFacet.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {ZkSyncHyperchainBase} from "contracts/state-transition/chain-deps/facets/Admin.sol"; -import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; +import {IVerifierV2} from "contracts/state-transition/chain-interfaces/IVerifierV2.sol"; +import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; contract TestBaseFacet is ZkSyncHyperchainBase { function functionWithOnlyAdminModifier() external onlyAdmin {} @@ -40,7 +42,7 @@ bytes constant ERROR_ONLY_VALIDATOR_OR_STATE_TRANSITION_MANAGER = "Hyperchain: O contract ZkSyncHyperchainBaseTest is Test { TestBaseFacet internal testBaseFacet; UtilsFacet internal utilsFacet; - address internal testnetVerifier = address(new TestnetVerifier()); + address internal testnetVerifier = address(new TestnetVerifier(IVerifierV2(address(0)), IVerifier(address(0)))); function getTestBaseFacetSelectors() public pure returns (bytes4[] memory selectors) { selectors = new bytes4[](6); diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol index 32a1e9c55..e963e31b7 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol @@ -10,7 +10,9 @@ import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; import {IGetters} from "contracts/state-transition/chain-interfaces/IGetters.sol"; -import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; +import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; +import {IVerifierV2} from "contracts/state-transition/chain-interfaces/IVerifierV2.sol"; +import {IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; contract MailboxTest is Test { IMailbox internal mailboxFacet; @@ -18,7 +20,7 @@ contract MailboxTest is Test { IGetters internal gettersFacet; address sender; uint256 constant eraChainId = 9; - address internal testnetVerifier = address(new TestnetVerifier()); + address internal testnetVerifier = address(new TestnetVerifier(IVerifierV2(address(0)), IVerifier(address(0)))); address diamondProxy; function setupDiamondProxy() public { From 065a9abaa4a77dc9854546563256d93038e49888 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Fri, 25 Oct 2024 10:47:15 +0200 Subject: [PATCH 02/15] TODO --- .github/workflows/l1-contracts-ci.yaml | 9 +- .../fflonk_verifier_contract_template.txt | 1600 +++++++++++++++++ ...uler_key.json => plonk_scheduler_key.json} | 0 ...t => plonk_verifier_contract_template.txt} | 4 +- tools/src/main.rs | 49 +- yarn.lock | 7 - 6 files changed, 1641 insertions(+), 28 deletions(-) create mode 100644 tools/data/fflonk_verifier_contract_template.txt rename tools/data/{scheduler_key.json => plonk_scheduler_key.json} (100%) rename tools/data/{verifier_contract_template.txt => plonk_verifier_contract_template.txt} (99%) diff --git a/.github/workflows/l1-contracts-ci.yaml b/.github/workflows/l1-contracts-ci.yaml index 5a27c65f8..deb4ab444 100644 --- a/.github/workflows/l1-contracts-ci.yaml +++ b/.github/workflows/l1-contracts-ci.yaml @@ -157,12 +157,15 @@ jobs: with: toolchain: 1.72.0 - - name: Generate Verifier.sol + - name: Generate verifiers working-directory: tools run: cargo run - - name: Compare - run: diff tools/data/Verifier.sol l1-contracts/contracts/state-transition/Verifier.sol + - name: Compare VerifierPlonk.sol + run: diff tools/data/VerifierPlonk.sol l1-contracts/contracts/state-transition/verifiers/VerifierPlonk.sol + + - name: Compare VerifierFflonk.sol + run: diff tools/data/VerifierFflonk.sol l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol coverage: defaults: diff --git a/tools/data/fflonk_verifier_contract_template.txt b/tools/data/fflonk_verifier_contract_template.txt new file mode 100644 index 000000000..5353032e5 --- /dev/null +++ b/tools/data/fflonk_verifier_contract_template.txt @@ -0,0 +1,1600 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IVerifierV2} from "../chain-interfaces/IVerifierV2.sol"; + +/// @title Fflonk Verifier Implementation +/// @author Matter Labs +/// @notice FFT inspired version of PlonK to optimize on-chain gas cost +/// @dev For better understanding of the protocol follow the below papers: +/// * Fflonk Paper: https://eprint.iacr.org/2021/1167 +/// @custom:security-contact security@matterlabs.dev +contract VerifierFflonk is IVerifierV2 { + // ================Constants================ + uint32 internal constant DST_0 = 0; + uint32 internal constant DST_1 = 1; + uint32 internal constant DST_CHALLENGE = 2; + uint256 internal constant FR_MASK = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + uint256 internal constant Q_MOD = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + uint256 internal constant R_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + uint256 internal constant BN254_B_COEFF = 3; + + // ================Verification Key================ + uint256 internal constant VK_NUM_INPUTS = 1; + // [C0]1 = qL(X^8)+ X*qR(X^8)+ X^2*qO(X^8)+ X^3*qM(X^8)+ X^4*qC(X^8)+ X^5*Sσ1(X^8)+ X^6*Sσ2(X^8)+ X^7*Sσ3(X^8) + uint256 internal constant VK_C0_G1_X = 0x15c99dbc62b8191204ff93984b0de4fb7c79ac7a1ef2c94f4ce940319a2408b2; + uint256 internal constant VK_C0_G1_Y = 0x0521b86a104e07c8971bf2e17d7665d59df7566c08e6e0c9750f584bb24084ce; + // k1 = 5, k2 = 7 + uint256 internal constant VK_NON_RESIDUES_0 = 0x0000000000000000000000000000000000000000000000000000000000000005; + uint256 internal constant VK_NON_RESIDUES_1 = 0x0000000000000000000000000000000000000000000000000000000000000007; + // G2 Elements = [1]_2, [s]_2 + uint256 internal constant VK_G2_ELEMENT_0_X1 = 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2; + uint256 internal constant VK_G2_ELEMENT_0_X2 = 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed; + uint256 internal constant VK_G2_ELEMENT_0_Y1 = 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b; + uint256 internal constant VK_G2_ELEMENT_0_Y2 = 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa; + uint256 internal constant VK_G2_ELEMENT_1_X1 = 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1; + uint256 internal constant VK_G2_ELEMENT_1_X2 = 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0; + uint256 internal constant VK_G2_ELEMENT_1_Y1 = 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4; + uint256 internal constant VK_G2_ELEMENT_1_Y2 = 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55; + + // Memory slots from 0x000 to 0x200 are reserved for intermediate computations and call to precompiles. + + // ================Transcript================ + // ================Constants================ + uint256 internal constant ONE = 1; + uint256 internal constant DOMAIN_SIZE = 8388608; + uint256 internal constant OMEGA = 0x1283ba6f4b7b1a76ba2008fe823128bea4adb9269cbfd7c41c223be65bc60863; + // ========================================= + uint256 internal constant TRANSCRIPT_BEGIN_SLOT = 0x200; + uint256 internal constant TRANSCRIPT_DST_BYTE_SLOT = 0x203; + uint256 internal constant TRANSCRIPT_STATE_0_SLOT = 0x204; + uint256 internal constant TRANSCRIPT_STATE_1_SLOT = 0x224; + uint256 internal constant TRANSCRIPT_CHALLENGE_SLOT = 0x244; + + // ================PartialVerifierState================ + // copy-permutation challenges + uint256 internal constant PVS_BETA = 0x264 + 0x00; + uint256 internal constant PVS_GAMMA = 0x264 + 0x20; + // evaluation challenges + uint256 internal constant PVS_R = 0x264 + 0x40; + uint256 internal constant PVS_Z = 0x264 + 0x60; + uint256 internal constant PVS_Z_OMEGA = 0x264 + 0x80; + // aggregation challenge + uint256 internal constant PVS_ALPHA_0 = 0x264 + 0xa0; + uint256 internal constant PVS_ALPHA_1 = 0x264 + 0xc0; + // final evaluation challenge + uint256 internal constant PVS_Y = 0x264 + 0xe0; + // convenience + uint256 internal constant PVS_VANISHING_AT_Z = 0x264 + 0x100; + uint256 internal constant PVS_VANISHING_AT_Z_INV = 0x264 + 0x120; + uint256 internal constant PVS_L_0_AT_Z = 0x264 + 0x140; + uint256 internal constant MAIN_GATE_QUOTIENT_AT_Z = 0x264 + 0x160; + uint256 internal constant COPY_PERM_FIRST_QUOTIENT_AT_Z = 0x264 + 0x180; + uint256 internal constant COPY_PERM_SECOND_QUOTIENT_AT_Z = 0x264 + 0x1a0; + // ================Opening State================ + // h0, h1, h2, h2_shifted + uint256 internal constant OPS_OPENING_POINTS = 0x264 + 0x1c0 + 0x00; // 4 slots + uint256 internal constant OPS_Y_POWS = 0x264 + 0x1c0 + 0x80; // 9 SLOTS + + // ================Pairing State================ + + uint256 internal constant PS_VANISHING_AT_Y = 0x264 + 0x1c0 + 0x1a0; + uint256 internal constant PS_INV_ZTS0_AT_Y = 0x264 + 0x1c0 + 0x1c0; + uint256 internal constant PS_SET_DIFFERENCES_AT_Y = 0x264 + 0x1c0 + 0x1e0; // 3 slots + uint256 internal constant PS_MINUS_Z = 0x264 + 0x1c0 + 0x240; // 2 slots + uint256 internal constant PS_R_EVALS = 0x264 + 0x1c0 + 0x280; // 3 slots + + // ================In Memory(from Proof)================ + uint256 internal constant MEM_PROOF_PUBLIC_INPUT_SLOT = 0x264 + 0x1c0 + 0x2e0; + + uint256 internal constant MEM_PROOF_COMMITMENT_0_G1_X = 0x264 + 0x1c0 + 0x2e0 + 0x20; + uint256 internal constant MEM_PROOF_COMMITMENT_0_G1_Y = 0x264 + 0x1c0 + 0x2e0 + 0x40; + uint256 internal constant MEM_PROOF_COMMITMENT_1_G1_X = 0x264 + 0x1c0 + 0x2e0 + 0x60; + uint256 internal constant MEM_PROOF_COMMITMENT_1_G1_Y = 0x264 + 0x1c0 + 0x2e0 + 0x80; + uint256 internal constant MEM_PROOF_COMMITMENT_2_G1_X = 0x264 + 0x1c0 + 0x2e0 + 0xa0; + uint256 internal constant MEM_PROOF_COMMITMENT_2_G1_Y = 0x264 + 0x1c0 + 0x2e0 + 0xc0; + uint256 internal constant MEM_PROOF_COMMITMENT_3_G1_X = 0x264 + 0x1c0 + 0x2e0 + 0xe0; + uint256 internal constant MEM_PROOF_COMMITMENT_3_G1_Y = 0x264 + 0x1c0 + 0x2e0 + 0x100; + + uint256 internal constant MEM_PROOF_EVALUATIONS = 0x264 + 0x1c0 + 0x2e0 + 0x120; // 15 slots + + uint256 internal constant MEM_PROOF_MONTGOMERY_LAGRANGE_BASIS_INVERSE = 0x264 + 0x1c0 + 0x2e0 + 0x120 + 0x1e0; // 1 slots + + uint256 internal constant MEM_LAGRANGE_BASIS_DENOMS = 0x264 + 0x1c0 + 0x2e0 + 0x120 + 0x200; //18 slots + uint256 internal constant MEM_LAGRANGE_BASIS_DENOM_PRODUCTS = 0x264 + 0x1c0 + 0x2e0 + 0x120 + 0x440; // 18 slots + uint256 internal constant MEM_PROOF_LAGRANGE_BASIS_EVALS = 0x264 + 0x1c0 + 0x2e0 + 0x120 + 0x680; // 18 Slots + + // ================Constants================ + uint256 internal constant PROOF_PUBLIC_INPUTS_LENGTH = 1; + uint256 internal constant PROOF_LENGTH = 24; + uint256 internal constant PROOF_EVALUATIONS_LENGTH = 15; + uint256 internal constant TOTAL_LAGRANGE_BASIS_INVERSES_LENGTH = 18; + + /// @inheritdoc IVerifierV2 + function verificationKeyHash() external pure returns (bytes32 vkHash) { + return + keccak256( + abi.encodePacked( + VK_NUM_INPUTS, + VK_C0_G1_X, + VK_C0_G1_Y, + VK_NON_RESIDUES_0, + VK_NON_RESIDUES_1, + VK_G2_ELEMENT_0_X1, + VK_G2_ELEMENT_0_X2, + VK_G2_ELEMENT_0_Y1, + VK_G2_ELEMENT_0_Y2, + VK_G2_ELEMENT_1_X1, + VK_G2_ELEMENT_1_X2, + VK_G2_ELEMENT_1_Y1, + VK_G2_ELEMENT_1_Y2 + ) + ); + } + + /// @inheritdoc IVerifierV2 + function verify( + uint256[] calldata, // _publicInputs + uint256[] calldata // _proof + ) public view virtual returns (bool) { + // Beginning of the big inline assembly block that makes all the verification work. + // Note: We use the custom memory layout, so the return value should be returned from the assembly, not + // Solidity code. + assembly { + // load public inputs and proof from the calldata + load_inputs() + initialize_transcript() + // identities at verifier's point + compute_main_gate_quotient() + compute_copy_permutation_quotients() + // openings + initialize_opening_state() + // final pairing + let result := check_openings() + mstore(0, result) + return(0, 0x20) + + function load_inputs() { + // 1. Load public inputs + let publicInputOffset := calldataload(0x04) + let publicInputLengthInWords := calldataload(add(publicInputOffset, 0x04)) + // We expect only one public input + if iszero(eq(publicInputLengthInWords, PROOF_PUBLIC_INPUTS_LENGTH)) { + revertWithMessage(32, "public input length is incorrect") + } + mstore(MEM_PROOF_PUBLIC_INPUT_SLOT, mod(calldataload(add(publicInputOffset, 0x24)), R_MOD)) + + // 2. Load proof + let proofLengthOffset := calldataload(0x24) + let proofLengthInWords := calldataload(add(proofLengthOffset, 0x04)) + + if iszero(eq(proofLengthInWords, PROOF_LENGTH)) { + revertWithMessage(25, "proof length is incorrect") + } + let proofOffset := add(proofLengthOffset, 0x24) + // Note: We don't accept the point-at-infinity as a valid input for the commitments considering the security risks involved, + // as it may aid in proof manipulation and final pairing computation. + { + let x := mod(calldataload(proofOffset), Q_MOD) + let y := mod(calldataload(add(proofOffset, 0x20)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + if iszero(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD))) { + revertWithMessage(28, "commitment 0 is not on curve") + } + mstore(MEM_PROOF_COMMITMENT_0_G1_Y, y) + mstore(MEM_PROOF_COMMITMENT_0_G1_X, x) + } + { + let x := mod(calldataload(add(proofOffset, 0x40)), Q_MOD) + let y := mod(calldataload(add(proofOffset, 0x60)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + if iszero(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD))) { + revertWithMessage(28, "commitment 1 is not on curve") + } + mstore(MEM_PROOF_COMMITMENT_1_G1_Y, y) + mstore(MEM_PROOF_COMMITMENT_1_G1_X, x) + } + { + let x := mod(calldataload(add(proofOffset, 0x80)), Q_MOD) + let y := mod(calldataload(add(proofOffset, 0xa0)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + if iszero(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD))) { + revertWithMessage(28, "commitment 2 is not on curve") + } + mstore(MEM_PROOF_COMMITMENT_2_G1_Y, y) + mstore(MEM_PROOF_COMMITMENT_2_G1_X, x) + } + { + let x := mod(calldataload(add(proofOffset, 0xc0)), Q_MOD) + let y := mod(calldataload(add(proofOffset, 0xe0)), Q_MOD) + let xx := mulmod(x, x, Q_MOD) + if iszero(eq(mulmod(y, y, Q_MOD), addmod(mulmod(x, xx, Q_MOD), 3, Q_MOD))) { + revertWithMessage(28, "commitment 3 is not on curve") + } + mstore(MEM_PROOF_COMMITMENT_3_G1_Y, y) + mstore(MEM_PROOF_COMMITMENT_3_G1_X, x) + } + proofOffset := add(proofOffset, 0x100) + + for { + let i := 0 + } lt(i, PROOF_EVALUATIONS_LENGTH) { + i := add(i, 1) + } { + let eval := mod(calldataload(add(proofOffset, mul(i, 0x20))), R_MOD) + let slot := add(MEM_PROOF_EVALUATIONS, mul(i, 0x20)) + mstore(slot, eval) + } + proofOffset := add(proofOffset, mul(PROOF_EVALUATIONS_LENGTH, 0x20)) + + mstore(MEM_PROOF_MONTGOMERY_LAGRANGE_BASIS_INVERSE, mod(calldataload(proofOffset), R_MOD)) + } + + /** + * @dev Commits data in the transcript then gets the challenges + * @notice that at this point, the transcript only has public inputs + * But luckily prover doesn't need any randomness in the first round + * so that prover has no control over the values because quotients are + * separated(there is no quotient aggregation neither in this round nor all rounds) + * + * w = 0x1283ba6f4b7b1a76ba2008fe823128bea4adb9269cbfd7c41c223be65bc60863 + */ + function initialize_transcript() { + if iszero(lt(DOMAIN_SIZE, R_MOD)) { + revertWithMessage(26, "Domain size >= R_MOD [ITS]") + } + if iszero(lt(OMEGA, R_MOD)) { + revertWithMessage(20, "Omega >= R_MOD [ITS]") + } + for { + let i := 0 + } lt(i, VK_NUM_INPUTS) { + i := add(i, 1) + } { + update_transcript(mload(add(MEM_PROOF_PUBLIC_INPUT_SLOT, mul(i, 0x20)))) + } + // commit first round commitment: preprocessed polynomials + update_transcript(VK_C0_G1_X) + update_transcript(VK_C0_G1_Y) + + // commit second round commitment: witnesses and gate identities + update_transcript(mload(MEM_PROOF_COMMITMENT_0_G1_X)) + update_transcript(mload(MEM_PROOF_COMMITMENT_0_G1_Y)) + + // copy-permutation challenges + mstore(PVS_BETA, get_challenge(0)) + mstore(PVS_GAMMA, get_challenge(1)) + // commit third round commitment: copy-perm + update_transcript(mload(MEM_PROOF_COMMITMENT_1_G1_X)) + update_transcript(mload(MEM_PROOF_COMMITMENT_1_G1_Y)) + // get evaluation challenge + // all system polynomials will be evaluated at z + // then combined polynomials will be opened at h_i = r^power_i + // then it becomes e.g C_i(X) = f_0(x^2) + x*f(x^2) in case of two polynomials + mstore(PVS_R, get_challenge(2)) + // commit all evaluations + for { + let i := 0 + } lt(i, PROOF_EVALUATIONS_LENGTH) { + i := add(i, 1) + } { + update_transcript(mload(add(MEM_PROOF_EVALUATIONS, mul(i, 0x20)))) + } + // get aggregation challenge + mstore(PVS_ALPHA_0, get_challenge(3)) + mstore(PVS_ALPHA_1, mulmod(mload(PVS_ALPHA_0), mload(PVS_ALPHA_0), R_MOD)) + // commit w(X) + update_transcript(mload(MEM_PROOF_COMMITMENT_2_G1_X)) + update_transcript(mload(MEM_PROOF_COMMITMENT_2_G1_Y)) + // opening challenge + mstore(PVS_Y, get_challenge(4)) + mstore(PVS_Z, modexp(mload(PVS_R), 24)) + // grand product of copy-permutation needs to be opened at shifted position + mstore(PVS_Z_OMEGA, mulmod(mload(PVS_Z), OMEGA, R_MOD)) + // Z_h(z) = X^N - 1 + mstore(PVS_VANISHING_AT_Z, addmod(modexp(mload(PVS_Z), DOMAIN_SIZE), sub(R_MOD, ONE), R_MOD)) + // L0(z) = 1/(N*(X-1)) * (X^N - 1) + mstore( + PVS_L_0_AT_Z, + modexp(mulmod(addmod(mload(PVS_Z), sub(R_MOD, ONE), R_MOD), DOMAIN_SIZE, R_MOD), sub(R_MOD, 2)) + ) + mstore(PVS_L_0_AT_Z, mulmod(mload(PVS_L_0_AT_Z), mload(PVS_VANISHING_AT_Z), R_MOD)) + mstore(PVS_VANISHING_AT_Z_INV, modexp(mload(PVS_VANISHING_AT_Z), sub(R_MOD, 2))) + } + + /** + * @dev Computes main gate quotient T0(ζ) + * T0(ζ) = (qm(ζ)*a(ζ)*b(ζ) + qa(ζ)*a(ζ) + qb(ζ)*b(ζ) + qc(ζ)*c(ζ) + qconst(ζ) + PI*L0(ζ)) * ZH(ζ)^-1 + */ + function compute_main_gate_quotient() { + // q_const + let rhs := mload(add(MEM_PROOF_EVALUATIONS, mul(4, 0x20))) + rhs := addmod(rhs, mulmod(mload(PVS_L_0_AT_Z), mload(MEM_PROOF_PUBLIC_INPUT_SLOT), R_MOD), R_MOD) + for { + let i := 0 + } lt(i, 3) { + i := add(i, 1) + } { + rhs := addmod( + rhs, + mulmod( + mload(add(MEM_PROOF_EVALUATIONS, mul(i, 0x20))), + mload(add(MEM_PROOF_EVALUATIONS, mul(add(8, i), 0x20))), + R_MOD + ), + R_MOD + ) + } + // q_m*A*B + rhs := mulmod( + addmod( + rhs, + mulmod( + mulmod( + mload(add(MEM_PROOF_EVALUATIONS, mul(3, 0x20))), + mload(add(MEM_PROOF_EVALUATIONS, mul(8, 0x20))), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(9, 0x20))), + R_MOD + ), + R_MOD + ), + mload(PVS_VANISHING_AT_Z_INV), + R_MOD + ) + mstore(MAIN_GATE_QUOTIENT_AT_Z, rhs) + } + + /** + * @dev Computes copy permutation quotients T1(ζ) & T2(ζ) + * T1(ζ) = ((z(ζ) * (a(ζ)+β*ζ+γ) * (b(ζ)+k1*β*ζ+γ) * (c(ζ)+k2*β*ζ+γ)) + * −(z(ζω) * (a(ζ)+β*sσ1(ζ)+γ) * (b(ζ)+β*sσ2(ζ)+γ) * (c(ζ)+β*sσ3(ζ)+γ)) * ZH(ζ)^-1 + * T2(ζ) = (z(ζ)−1)*L0(ζ)*ZH(ζ)^-1 + */ + function compute_copy_permutation_quotients() { + let tmp + let tmp2 + // (c(ζ)+k2*β*ζ+γ) + let rhs := addmod( + addmod( + mulmod(mulmod(mload(PVS_BETA), mload(PVS_Z), R_MOD), VK_NON_RESIDUES_1, R_MOD), + mload(PVS_GAMMA), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(add(8, 2), 0x20))), + R_MOD + ) + // (b(ζ)+k1*β*ζ+γ) + tmp := addmod( + addmod( + mulmod(mulmod(mload(PVS_BETA), mload(PVS_Z), R_MOD), VK_NON_RESIDUES_0, R_MOD), + mload(PVS_GAMMA), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(add(8, 1), 0x20))), + R_MOD + ) + // (b(ζ)+k1*β*ζ+γ) * (c(ζ)+k2*β*ζ+γ) + rhs := mulmod(rhs, tmp, R_MOD) + // (z(ζ) * (a(ζ)+β*ζ+γ) * (b(ζ)+k1*β*ζ+γ) * (c(ζ)+k2*β*ζ+γ) + rhs := mulmod( + mulmod( + rhs, + addmod( + addmod(mulmod(mload(PVS_BETA), mload(PVS_Z), R_MOD), mload(PVS_GAMMA), R_MOD), + mload(add(MEM_PROOF_EVALUATIONS, mul(8, 0x20))), + R_MOD + ), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(11, 0x20))), + R_MOD + ) + + // (z(ζω) * (b(ζ)+β*sσ2(ζ)+γ) * (c(ζ)+β*sσ3(ζ)+γ)) + tmp2 := mulmod( + mulmod( + addmod( + addmod( + mulmod(mload(PVS_BETA), mload(add(MEM_PROOF_EVALUATIONS, mul(add(5, 2), 0x20))), R_MOD), + mload(PVS_GAMMA), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(add(8, 2), 0x20))), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(12, 0x20))), + R_MOD + ), + addmod( + addmod( + mulmod(mload(PVS_BETA), mload(add(MEM_PROOF_EVALUATIONS, mul(add(5, 1), 0x20))), R_MOD), + mload(PVS_GAMMA), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(add(8, 1), 0x20))), + R_MOD + ), + R_MOD + ) + // (a(ζ)+β*sσ1(ζ)+γ) + tmp := addmod( + addmod( + mulmod(mload(PVS_BETA), mload(add(MEM_PROOF_EVALUATIONS, mul(5, 0x20))), R_MOD), + mload(PVS_GAMMA), + R_MOD + ), + mload(add(MEM_PROOF_EVALUATIONS, mul(8, 0x20))), + R_MOD + ) + // z(ζω) * (a(ζ)+β*sσ1(ζ)+γ) * (b(ζ)+β*sσ2(ζ)+γ) * (c(ζ)+β*sσ3(ζ)+γ) + tmp2 := mulmod(tmp2, tmp, R_MOD) + // −(z(ζω) * (a(ζ)+β*sσ1(ζ)+γ) * (b(ζ)+β*sσ2(ζ)+γ) * (c(ζ)+β*sσ3(ζ)+γ)) + tmp2 := sub(R_MOD, tmp2) + // ((z(ζ) * (a(ζ)+β*ζ+γ) * (b(ζ)+k1*β*ζ+γ) * (c(ζ)+k2*β*ζ+γ)) − (z(ζω) * (a(ζ)+β*sσ1(ζ)+γ) * (b(ζ)+β*sσ2(ζ)+γ) * (c(ζ)+β*sσ3(ζ)+γ)) * ZH(ζ)^-1 + rhs := mulmod(addmod(rhs, tmp2, R_MOD), mload(PVS_VANISHING_AT_Z_INV), R_MOD) + mstore(COPY_PERM_FIRST_QUOTIENT_AT_Z, rhs) + + // (z(ζ)−1)*L0(ζ)*ZH(ζ)^-1 + rhs := mulmod( + mulmod( + addmod(mload(add(MEM_PROOF_EVALUATIONS, mul(11, 0x20))), sub(R_MOD, 1), R_MOD), + mload(PVS_L_0_AT_Z), + R_MOD + ), + mload(PVS_VANISHING_AT_Z_INV), + R_MOD + ) + mstore(COPY_PERM_SECOND_QUOTIENT_AT_Z, rhs) + } + + /** + * @dev Computes partial lagrange basis evaluations Li(y)_numerator {i = [start..(start+num_polys))} using montgomery lagrange basis inverses sent with proof. + * Li(y)_numerator = (w_i * (y^{num_polys} - h^{num_polys})) + * Li(y)_denominator = (num_polys * h^{num_polys-1} * (y - (h * w_i))) + * Li(y) = Li(y)_numerator / Li(y)_denominator = (w_i * (y^{num_polys} - h^{num_polys})) / (num_polys * h^{num_polys-1} * (y - (h * w_i))) + * + * Also calculates the products of the denominators of the lagrange basis evaluations: + * Li(y)_denominators_product = Li(y)_previous_denominators_product * (∏(Li(y)_denominator {i = [start..(start+num_polys))})) + */ + function precompute_partial_lagrange_basis_evaluations(start, num_polys, y, omega, h, product) + -> interim_product + { + if gt(add(start, num_polys), TOTAL_LAGRANGE_BASIS_INVERSES_LENGTH) { + revertWithMessage(31, "Precompute Eval. Error [PLBEI1]") + } + let tmp := h + let loop_length := sub(num_polys, 2) + // h^{num_polys-1} + for { + let i := 0 + } lt(i, loop_length) { + i := add(i, 1) + } { + tmp := mulmod(tmp, h, R_MOD) + } + // num_polys * h^{num_polys-1} + let constant_part := mulmod(num_polys, tmp, R_MOD) + + // y^{num_polys} + let y_pow := mload(add(OPS_Y_POWS, mul(num_polys, 0x20))) + // h^{num_polys} + let num_at_y := mulmod(tmp, h, R_MOD) + // -h^{num_polys} + num_at_y := sub(R_MOD, num_at_y) + // (y^{num_polys} - h^{num_polys}) + num_at_y := addmod(num_at_y, y_pow, R_MOD) + + let current_omega := 1 + for { + let i := 0 + } lt(i, num_polys) { + i := add(i, 1) + } { + // h*w_i + tmp := mulmod(current_omega, h, R_MOD) + // -h*w_i + tmp := sub(R_MOD, tmp) + // y-(h*w_i) + tmp := addmod(tmp, y, R_MOD) + // (num_polys * h^{num_polys-1} * (y - (h * w_i))) + tmp := mulmod(tmp, constant_part, R_MOD) + + mstore(add(MEM_LAGRANGE_BASIS_DENOMS, mul(add(start, i), 0x20)), tmp) + + product := mulmod(product, tmp, R_MOD) + + mstore(add(MEM_LAGRANGE_BASIS_DENOM_PRODUCTS, mul(add(start, i), 0x20)), product) + // Li(y) = (W_i * (y^{num_polys} - h^{num_polys})) + mstore( + add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(start, i), 0x20)), + mulmod(num_at_y, current_omega, R_MOD) + ) + + // w_i {i = i+1} + current_omega := mulmod(current_omega, omega, R_MOD) + } + + interim_product := product + } + + /** + * @dev Computes partial lagrange basis evaluations Li(y)_numerator = {i = [start..(start+num_polys))} & Li(y)_numerator {i = [(start+num_polys)..(start+(2*num_polys)))} using montgomery lagrange basis inverses sent with proof. + * For Li(y)_numerator{i = [start..(start+num_polys))}: + * Li(y)_numerator = w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys}))) + * Li(y)_denominator = (num_polys * (h^{(2*num_polys)-1}-(h^{num_polys-1} * h_s^{num_polys})) * (y-(h*w_i))) + * Li(y) = Li(y)_numerator / Li(y)_denominator = (w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys})))) / (num_polys * (h^{(2*num_polys)-1}-(h^{num_polys-1} * h_s^{num_polys})) * (y-(h*w_i))) + * + * For Li(y)_numerator{i = [(start+num_polys)..(start+(2*num_polys)))} + * Li(y)_numerator = w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys}))) + * Li(y)_denominator = (num_polys * (h_s^{(2*num_polys)-1}-(h_s^{num_polys-1} * h^{num_polys})) * (y-(h_s*w_i))) + * Li(y) = Li(y)_numerator / Li(y)_denominator = (w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys}))) ) / (num_polys * (h_s^{(2*num_polys)-1}-(h_s^{num_polys-1} * h^{num_polys})) * (y-(h_s*w_i))) + * + * Also calculates the products of the denominators of the lagrange basis evaluations: + * Li(y)_denominators_product = Li(y)_previous_denominators_product * (∏(Li(y)_denominator {i = [start..(start+num_polys))})) * (∏(Li(y)_denominator {i = [(start+num_polys)..(start+(2*num_polys)))})) + */ + + function precompute_partial_lagrange_basis_evaluations_for_union_set( + start, + num_polys, + y, + omega, + h, + h_shifted, + product + ) -> final_product { + if gt(add(start, mul(2, num_polys)), TOTAL_LAGRANGE_BASIS_INVERSES_LENGTH) { + revertWithMessage(32, "Precompute Eval. Error [PLBEIU1]") + } + let h_pows_0 := h + let h_pows_1 := h_shifted + let loop_length := sub(num_polys, 2) + // h^{num_polys-1} & h_s^{num_polys-1} + for { + let i := 0 + } lt(i, loop_length) { + i := add(i, 1) + } { + h_pows_0 := mulmod(h_pows_0, h, R_MOD) + h_pows_1 := mulmod(h_pows_1, h_shifted, R_MOD) + } + let constant_parts_0 := h_pows_0 + let constant_parts_1 := h_pows_1 + // h^{num_polys} + h_pows_0 := mulmod(h_pows_0, h, R_MOD) + // h_s^{num_polys} + h_pows_1 := mulmod(h_pows_1, h_shifted, R_MOD) + + // h^{num_polys-1} * h_s^{num_polys} + constant_parts_0 := mulmod(constant_parts_0, h_pows_1, R_MOD) + // -h^{num_polys-1} * h_s^{num_polys} + constant_parts_0 := sub(R_MOD, constant_parts_0) + // h_s^{num_polys-1} * h^{num_polys} + constant_parts_1 := mulmod(constant_parts_1, h_pows_0, R_MOD) + // -h_s^{num_polys-1} * h^{num_polys} + constant_parts_1 := sub(R_MOD, constant_parts_1) + + // y^{num_polys} + let t_2 := mload(add(OPS_Y_POWS, mul(num_polys, 0x20))) + // h^{num_polys} * h_s^{num_polys} + let t_1 := mulmod(h_pows_0, h_pows_1, R_MOD) + // h^{num_polys} + h_s^{num_polys} + let t_0 := addmod(h_pows_0, h_pows_1, R_MOD) + // y^{num_polys} * (h^{num_polys} + h_s^{num_polys}) + t_0 := mulmod(t_0, t_2, R_MOD) + // - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys})) + t_0 := sub(R_MOD, t_0) + // h^{num_polys} * h_s^{num_polys} - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys})) + t_1 := addmod(t_1, t_0, R_MOD) + // y^{2*num_polys} + t_2 := mulmod(t_2, t_2, R_MOD) + // y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys})) + t_1 := addmod(t_1, t_2, R_MOD) + loop_length := sub(num_polys, 1) + // h^{(2*num_polys)-1} & h_s^{(2*num_polys)-1} + for { + let i := 0 + } lt(i, loop_length) { + i := add(i, 1) + } { + h_pows_0 := mulmod(h_pows_0, h, R_MOD) + h_pows_1 := mulmod(h_pows_1, h_shifted, R_MOD) + } + // h^{(2*num_polys)-1}-(h^{num_polys-1} * h_s^{num_polys}) + constant_parts_0 := addmod(constant_parts_0, h_pows_0, R_MOD) + // num_polys * (h^{(2*num_polys)-1}-(h^{num_polys-1} * h_s^{num_polys})) + constant_parts_0 := mulmod(constant_parts_0, num_polys, R_MOD) + // h_s^{(2*num_polys)-1}-(h_s^{num_polys-1} * h^{num_polys}) + constant_parts_1 := addmod(constant_parts_1, h_pows_1, R_MOD) + // num_polys * (h_s^{(2*num_polys)-1}-(h_s^{num_polys-1} * h^{num_polys})) + constant_parts_1 := mulmod(constant_parts_1, num_polys, R_MOD) + + let current_omega := 1 + let interim_product := product + for { + let i := 0 + } lt(i, num_polys) { + i := add(i, 1) + } { + t_0 := mulmod(current_omega, h, R_MOD) + t_0 := sub(R_MOD, t_0) + t_0 := addmod(t_0, y, R_MOD) + // (num_polys * (h^{(2*num_polys)-1}-(h^{num_polys-1} * h_s^{num_polys})) * (y-(h*w_i))) + t_0 := mulmod(t_0, constant_parts_0, R_MOD) + + mstore(add(MEM_LAGRANGE_BASIS_DENOMS, mul(add(start, i), 0x20)), t_0) + + interim_product := mulmod(interim_product, t_0, R_MOD) + + mstore(add(MEM_LAGRANGE_BASIS_DENOM_PRODUCTS, mul(add(start, i), 0x20)), interim_product) + // w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys}))) + mstore( + add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(start, i), 0x20)), + mulmod(t_1, current_omega, R_MOD) + ) + // w_i {i = i+1} + current_omega := mulmod(current_omega, omega, R_MOD) + } + + current_omega := 1 + for { + let i := 0 + } lt(i, num_polys) { + i := add(i, 1) + } { + t_0 := mulmod(current_omega, h_shifted, R_MOD) + t_0 := sub(R_MOD, t_0) + t_0 := addmod(t_0, y, R_MOD) + // (num_polys * (h_s^{(2*num_polys)-1}-(h_s^{num_polys-1} * h^{num_polys})) * (y-(h_s*w_i))) + t_0 := mulmod(t_0, constant_parts_1, R_MOD) + + mstore(add(MEM_LAGRANGE_BASIS_DENOMS, mul(add(add(start, num_polys), i), 0x20)), t_0) + + interim_product := mulmod(interim_product, t_0, R_MOD) + + mstore( + add(MEM_LAGRANGE_BASIS_DENOM_PRODUCTS, mul(add(add(start, num_polys), i), 0x20)), + interim_product + ) + // w_i * (y^{2*num_polys} + (h^{num_polys} * h_s^{num_polys}) - (y^{num_polys} * (h^{num_polys} + h_s^{num_polys}))) + mstore( + add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(start, num_polys), i), 0x20)), + mulmod(t_1, current_omega, R_MOD) + ) + // w_i {i = i+1} + current_omega := mulmod(current_omega, omega, R_MOD) + } + + final_product := interim_product + } + + /** + * @dev Computes lagrange basis evaluations using montgomery lagrange basis inverses sent with proof. + * @notice Check individual functions for more details + */ + function precompute_all_lagrange_basis_evaluations_from_inverses() { + let y := mload(PVS_Y) + // w8 = 0x2b337de1c8c14f22ec9b9e2f96afef3652627366f8170a0a948dad4ac1bd5e80 + // w4 = 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636 + // w3 = 0x0000000000000000b3c4d79d41a917585bfc41088d8daaa78b17ea66b99c90dd + let product_0_7 := precompute_partial_lagrange_basis_evaluations( + 0, + 8, + y, + 0x2b337de1c8c14f22ec9b9e2f96afef3652627366f8170a0a948dad4ac1bd5e80, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + 1 + ) + let product_0_11 := precompute_partial_lagrange_basis_evaluations( + 8, + 4, + y, + 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636, + mload(add(OPS_OPENING_POINTS, mul(1, 0x20))), + product_0_7 + ) + let product_0_17 := precompute_partial_lagrange_basis_evaluations_for_union_set( + add(8, 4), + 3, + y, + 0x0000000000000000b3c4d79d41a917585bfc41088d8daaa78b17ea66b99c90dd, + mload(add(OPS_OPENING_POINTS, mul(2, 0x20))), + mload(add(OPS_OPENING_POINTS, mul(3, 0x20))), + product_0_11 + ) + + let montgomery_inverse := mload(MEM_PROOF_MONTGOMERY_LAGRANGE_BASIS_INVERSE) + + if iszero(eq(mulmod(product_0_17, montgomery_inverse, R_MOD), 1)) { + revertWithMessage(30, "Precompute Eval. Error [PALBE]") + } + let temp := montgomery_inverse + let loop_length := sub(TOTAL_LAGRANGE_BASIS_INVERSES_LENGTH, 1) + for { + let i := loop_length + } gt(i, 0) { + i := sub(i, 1) + } { + mstore( + add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(i, 0x20)), + mulmod( + mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(i, 0x20))), + mulmod(mload(add(MEM_LAGRANGE_BASIS_DENOM_PRODUCTS, mul(sub(i, 1), 0x20))), temp, R_MOD), + R_MOD + ) + ) + temp := mulmod(temp, mload(add(MEM_LAGRANGE_BASIS_DENOMS, mul(i, 0x20))), R_MOD) + } + mstore( + add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(0, 0x20)), + mulmod(mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(0, 0x20))), temp, R_MOD) + ) + } + + /** + * @dev Computes opening points h0, h1, h2, h3 + */ + function compute_opening_points() { + // h = r^{power/num_polys} + let pvs_r := mload(PVS_R) + let r_2 := mulmod(pvs_r, pvs_r, R_MOD) + let r_3 := mulmod(r_2, pvs_r, R_MOD) + let r_6 := mulmod(r_3, r_3, R_MOD) + let r_8 := mulmod(r_6, r_2, R_MOD) + // h0 = pvs_r^3 + mstore(add(OPS_OPENING_POINTS, mul(0, 0x20)), r_3) + // h1 = pvs_r^6 + mstore(add(OPS_OPENING_POINTS, mul(1, 0x20)), r_6) + // h2 = pvs_r^8 + mstore(add(OPS_OPENING_POINTS, mul(2, 0x20)), r_8) + + // h3 (only round 2 needs opening at shifted point) + mstore( + add(OPS_OPENING_POINTS, mul(3, 0x20)), + mulmod(r_8, 0x0925f0bd364638ec3084b45fc27895f8f3f6f079096600fe946c8e9db9a47124, R_MOD) + ) + } + + /** + * @dev Initializes opening state OPS_Y_POWS[i] = y^i + * @notice only 9 powers are computed since the rest stay unused. + */ + function initialize_opening_state() { + compute_opening_points() + let acc := 1 + for { + let i := 0 + } lt(i, 9) { + i := add(i, 1) + } { + mstore(add(OPS_Y_POWS, mul(i, 0x20)), acc) + acc := mulmod(acc, mload(PVS_Y), R_MOD) + } + precompute_all_lagrange_basis_evaluations_from_inverses() + } + + /** + * @dev Computes r polynomial evaluations utilizing horner method + * (r*w)^{i}:{1, w*r, (w*r)^2, .. , (w*r)^{k-1}} + * horner: c0 + c1*(rw) + c2*(rw)^2 + c3*(rw)^3 -> (c0 + (rw)*(c1 + (rw)*(c2 + c3*(rw)))) + */ + function evaluate_r_polys_at_point_unrolled( + main_gate_quotient_at_z, + copy_perm_first_quotient_at_z, + copy_perm_second_quotient_at_z + ) { + let omega_h + let c + + // setup round + // r + + // w8^1 = 0x2b337de1c8c14f22ec9b9e2f96afef3652627366f8170a0a948dad4ac1bd5e80 + // w8^2 = 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636 + // w8^3 = 0x1d59376149b959ccbd157ac850893a6f07c2d99b3852513ab8d01be8e846a566 + // w8^4 = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000 + // w8^5 = 0x0530d09118705106cbb4a786ead16926d5d174e181a26686af5448492e42a181 + // w8^6 = 0x0000000000000000b3c4d79d41a91758cb49c3517c4604a520cff123608fc9cb + // w8^7 = 0x130b17119778465cfb3acaee30f81dee20710ead41671f568b11d9ab07b95a9b + omega_h := mload(add(OPS_OPENING_POINTS, mul(0, 0x20))) + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(0, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x2b337de1c8c14f22ec9b9e2f96afef3652627366f8170a0a948dad4ac1bd5e80, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(1, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(2, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x1d59376149b959ccbd157ac850893a6f07c2d99b3852513ab8d01be8e846a566, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(3, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(4, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x0530d09118705106cbb4a786ead16926d5d174e181a26686af5448492e42a181, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(5, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x0000000000000000b3c4d79d41a91758cb49c3517c4604a520cff123608fc9cb, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(6, 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x130b17119778465cfb3acaee30f81dee20710ead41671f568b11d9ab07b95a9b, + mload(add(OPS_OPENING_POINTS, mul(0, 0x20))), + R_MOD + ) + + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(7, 0x20))), omega_h, R_MOD) + for { + let i := 1 + } lt(i, 7) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(7, i), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(MEM_PROOF_EVALUATIONS), R_MOD) + mstore( + PS_R_EVALS, + addmod( + mload(PS_R_EVALS), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(7, 0x20))), R_MOD), + R_MOD + ) + ) + + // first round + // r + + // w4^1 = 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636 + // w4^2 = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000 + // w4^3 = 0x0000000000000000b3c4d79d41a91758cb49c3517c4604a520cff123608fc9cb + omega_h := mload(add(OPS_OPENING_POINTS, mul(1, 0x20))) + + c := mulmod(main_gate_quotient_at_z, omega_h, R_MOD) + for { + let i := 1 + } lt(i, 3) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), i), 1), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), 3), 1), 0x20))), R_MOD) + + mstore( + add(PS_R_EVALS, mul(1, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(1, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(8, 0), 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x30644e72e131a029048b6e193fd841045cea24f6fd736bec231204708f703636, + mload(add(OPS_OPENING_POINTS, mul(1, 0x20))), + R_MOD + ) + + c := mulmod(main_gate_quotient_at_z, omega_h, R_MOD) + for { + let i := 1 + } lt(i, 3) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), i), 1), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), 3), 1), 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(1, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(1, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(8, 1), 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000, + mload(add(OPS_OPENING_POINTS, mul(1, 0x20))), + R_MOD + ) + + c := mulmod(main_gate_quotient_at_z, omega_h, R_MOD) + for { + let i := 1 + } lt(i, 3) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), i), 1), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), 3), 1), 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(1, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(1, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(8, 2), 0x20))), R_MOD), + R_MOD + ) + ) + omega_h := mulmod( + 0x0000000000000000b3c4d79d41a91758cb49c3517c4604a520cff123608fc9cb, + mload(add(OPS_OPENING_POINTS, mul(1, 0x20))), + R_MOD + ) + + c := mulmod(main_gate_quotient_at_z, omega_h, R_MOD) + for { + let i := 1 + } lt(i, 3) { + i := add(i, 1) + } { + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), i), 1), 0x20))), R_MOD), + omega_h, + R_MOD + ) + } + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(sub(sub(add(8, 4), 3), 1), 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(1, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(1, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(8, 3), 0x20))), R_MOD), + R_MOD + ) + ) + + // second round + // c2 + // r + omega_h := mload(add(OPS_OPENING_POINTS, mul(2, 0x20))) + let omega_h_shifted := mload(add(OPS_OPENING_POINTS, mul(3, 0x20))) + c := mulmod(copy_perm_second_quotient_at_z, omega_h, R_MOD) + c := mulmod(addmod(c, copy_perm_first_quotient_at_z, R_MOD), omega_h, R_MOD) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(11, 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(8, 4), 0), 0x20))), R_MOD), + R_MOD + ) + ) + // c2 shifted + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 2), 0x20))), omega_h_shifted, R_MOD) + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 1), 0x20))), R_MOD), + omega_h_shifted, + R_MOD + ) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(12, 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod( + c, + mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(add(8, 4), 3), 0), 0x20))), + R_MOD + ), + R_MOD + ) + ) + // c2 + omega_h := mulmod( + 0x0000000000000000b3c4d79d41a917585bfc41088d8daaa78b17ea66b99c90dd, + mload(add(OPS_OPENING_POINTS, mul(2, 0x20))), + R_MOD + ) + omega_h_shifted := mulmod( + 0x0000000000000000b3c4d79d41a917585bfc41088d8daaa78b17ea66b99c90dd, + mload(add(OPS_OPENING_POINTS, mul(3, 0x20))), + R_MOD + ) + + c := mulmod(copy_perm_second_quotient_at_z, omega_h, R_MOD) + c := mulmod(addmod(c, copy_perm_first_quotient_at_z, R_MOD), omega_h, R_MOD) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(11, 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(8, 4), 1), 0x20))), R_MOD), + R_MOD + ) + ) + // c2 shifted + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 2), 0x20))), omega_h_shifted, R_MOD) + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 1), 0x20))), R_MOD), + omega_h_shifted, + R_MOD + ) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(12, 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod( + c, + mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(add(8, 4), 3), 1), 0x20))), + R_MOD + ), + R_MOD + ) + ) + // c2 + omega_h := mulmod( + 0x30644e72e131a029048b6e193fd84104cc37a73fec2bc5e9b8ca0b2d36636f23, + mload(add(OPS_OPENING_POINTS, mul(2, 0x20))), + R_MOD + ) + omega_h_shifted := mulmod( + 0x30644e72e131a029048b6e193fd84104cc37a73fec2bc5e9b8ca0b2d36636f23, + mload(add(OPS_OPENING_POINTS, mul(3, 0x20))), + R_MOD + ) + + c := mulmod(copy_perm_second_quotient_at_z, omega_h, R_MOD) + c := mulmod(addmod(c, copy_perm_first_quotient_at_z, R_MOD), omega_h, R_MOD) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(11, 0x20))), R_MOD) + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod(c, mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(8, 4), 2), 0x20))), R_MOD), + R_MOD + ) + ) + // c2 shifted + c := mulmod(mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 2), 0x20))), omega_h_shifted, R_MOD) + c := mulmod( + addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(add(12, 1), 0x20))), R_MOD), + omega_h_shifted, + R_MOD + ) + c := addmod(c, mload(add(MEM_PROOF_EVALUATIONS, mul(12, 0x20))), R_MOD) + + mstore( + add(PS_R_EVALS, mul(2, 0x20)), + addmod( + mload(add(PS_R_EVALS, mul(2, 0x20))), + mulmod( + c, + mload(add(MEM_PROOF_LAGRANGE_BASIS_EVALS, mul(add(add(add(8, 4), 3), 2), 0x20))), + R_MOD + ), + R_MOD + ) + ) + } + + /** + * @dev Computes the openings and returns the result of pairing computation + */ + function check_openings() -> out { + // f(X) = (Z_{T\S0}(y) * (C0(X) - r0(y))) + (alpha*(Z_{T\S1}(y)*(C1(X) - r1(y)))) + (alpha^{2}*(Z_{T\S2}(y)*(C2(X) - r2(y)))) + // Note that, in our case set differences(Z_T\{S_i}) are: + // - Z_{T\S0}(y): (y^{k1}-ζ)*(y^{k2}-ζ)*(y^{k2}-(ζ*w)) + // - Z_{T\S1}(y): (y^{k0}-ζ)*(y^{k2}-ζ)*(y^{k2}-(ζ*w)) + // - Z_{T\S2}(y): (y^{k0}-ζ)*(y^{k1}-ζ) where + // k0=8, k1=4, and k2=3 are number of the polynomials for setup, first and second round respectively + + let tmp + evaluate_r_polys_at_point_unrolled( + mload(MAIN_GATE_QUOTIENT_AT_Z), + mload(COPY_PERM_FIRST_QUOTIENT_AT_Z), + mload(COPY_PERM_SECOND_QUOTIENT_AT_Z) + ) + + // -ζ + mstore(add(PS_MINUS_Z, mul(0, 0x20)), sub(R_MOD, mload(PVS_Z))) + // -(ζ*w) + mstore(add(PS_MINUS_Z, mul(1, 0x20)), sub(R_MOD, mload(PVS_Z_OMEGA))) + + // Z_{T\S0}(y) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20)), + addmod(mload(add(OPS_Y_POWS, mul(3, 0x20))), mload(add(PS_MINUS_Z, mul(1, 0x20))), R_MOD) + ) + tmp := addmod(mload(add(OPS_Y_POWS, mul(3, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20)), + mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20))), tmp, R_MOD) + ) + tmp := addmod(mload(add(OPS_Y_POWS, mul(4, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20)), + mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20))), tmp, R_MOD) + ) + mstore(PS_VANISHING_AT_Y, mload(add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20)))) + mstore(PS_INV_ZTS0_AT_Y, modexp(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(0, 0x20))), sub(R_MOD, 2))) + + // Z_{T\S1}(y) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20)), + addmod(mload(add(OPS_Y_POWS, mul(3, 0x20))), mload(add(PS_MINUS_Z, mul(1, 0x20))), R_MOD) + ) + tmp := addmod(mload(add(OPS_Y_POWS, mul(3, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20)), + mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20))), tmp, R_MOD) + ) + tmp := addmod(mload(add(OPS_Y_POWS, mul(8, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20)), + mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20))), tmp, R_MOD) + ) + mstore(PS_VANISHING_AT_Y, mulmod(mload(PS_VANISHING_AT_Y), tmp, R_MOD)) + + // // Z_{T\S2}(y) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(2, 0x20)), + addmod(mload(add(OPS_Y_POWS, mul(4, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + ) + tmp := addmod(mload(add(OPS_Y_POWS, mul(8, 0x20))), mload(add(PS_MINUS_Z, mul(0, 0x20))), R_MOD) + mstore( + add(PS_SET_DIFFERENCES_AT_Y, mul(2, 0x20)), + mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(2, 0x20))), tmp, R_MOD) + ) + + // W(X) = f(X) / Z_T(y) where Z_T(y) = (y^{k0}-ζ)*(y^{k1}-ζ)*(y^{k2}-ζ)*(y^{k2}-(ζ*w)) + // we need to check that + // f(X) - W(X) * Z_T(y) = 0 + + // W'(X) = L(X) / (Z_{T\S0}(y)*(X-y)) + // L(X)/Z_{T\S0}(y) = (C0(X) - r0(y)) + (alpha*(Z_{T\S1}(y)/Z_{T\S0}(y))*(C1(X) - r1(y))) + (alpha^{2}*(Z_{T\S2}(y)/Z_{T\S0}(y))*(C2(X) - r2(y))) - ((Z_T(y)/Z_{T\S0}(y))*W(X)) + + // the identity check is reduced into following + // L(X) - W'(X)*Z_{T\S0}(y)(X-y) == 0 + // verifier has commitments to the C_i(X) polynomials + // verifer also recomputed r_i(y) + // group constant and commitment parts + // first prepare L(X)/Z_{T\S0}(y) + // C(X) = C0(X) + ((alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*C1(X)) + ((alpha^2*Z_{T\S2}(y)/Z_{T\S0}(y))*C2(X)) + // r(y) = r0(y) + ((alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*r1(y)) + ((alpha^2*Z_{T\S2}(y)/Z_{T\S0}(y))*r2(y)) + // now construct + // L(X)/Z_{T\S0}(y) = C(X) - r(y) - ((Z_T(y)/Z_{T\S0}(y))*W(X)) + // now check following identity + // C(X) - r(y) - ((Z_t(y)/Z_{T\S0}(y))*W(X)) - (W'(X)*(X-y)) = 0 + // [C(X)] - [r(y)*G1] - (Z_T(y)/Z_{T\S0}(y))*[W] - [(X-y)*W'] = 0 + // [C(X)] - [r(y)*G1] - (Z_T(y)/Z_{T\S0}(y))*[W] - [X*W'] + [y*W]' = 0 + // [C(X)] - [r(y)*G1] - (Z_T(y)/Z_{T\S0}(y))*[W] + [y*W'] - [X*W'] = 0 + // points with X will be multiplied in the exponent via pairing + // so final pairing would ne + // e([C(X)] - [r(y)*G1] - [Z_T(y)/(Z_{T\S0}(y)*W)] + [y*W'], G2)*e(-W', X*G2) = 1 + + // C0 + let ps_aggregated_commitment_g1_x := VK_C0_G1_X + let ps_aggregated_commitment_g1_y := VK_C0_G1_Y + + // ((alpha^{2}*Z_{T\S2}(y))/Z_{T\S0}(y)) + let aggregated_r_at_y := mulmod( + mload(add(PS_SET_DIFFERENCES_AT_Y, mul(2, 0x20))), + mload(PS_INV_ZTS0_AT_Y), + R_MOD + ) + aggregated_r_at_y := mulmod(aggregated_r_at_y, mload(PVS_ALPHA_1), R_MOD) + + // ((alpha^{2}*Z_{T\S2}(y))/Z_{T\S0}(y))*C2 + let tp_g1_x, tp_g1_y := point_mul( + mload(MEM_PROOF_COMMITMENT_1_G1_X), + mload(MEM_PROOF_COMMITMENT_1_G1_Y), + aggregated_r_at_y + ) + // c0 + (((alpha^{2}*Z_{T\S2}(y))/Z_{T\S0}(y))*C2) + ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y := point_add( + ps_aggregated_commitment_g1_x, + ps_aggregated_commitment_g1_y, + tp_g1_x, + tp_g1_y + ) + // ((alpha^{2}*Z_{T\S2}(y))/Z_{T\S0}(y))*r2 + aggregated_r_at_y := mulmod(aggregated_r_at_y, mload(add(PS_R_EVALS, mul(2, 0x20))), R_MOD) + + // (alpha*Z_{T\S1}(y)/Z_{T\S0}(y)) + tmp := mulmod(mload(add(PS_SET_DIFFERENCES_AT_Y, mul(1, 0x20))), mload(PS_INV_ZTS0_AT_Y), R_MOD) + tmp := mulmod(tmp, mload(PVS_ALPHA_0), R_MOD) + + // (alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*C1 + tp_g1_x, tp_g1_y := point_mul( + mload(MEM_PROOF_COMMITMENT_0_G1_X), + mload(MEM_PROOF_COMMITMENT_0_G1_Y), + tmp + ) + // c0 + ((alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*C1) + (((alpha^{2}*Z_{T\S2}(y))/Z_{T\S0}(y))*C2) + ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y := point_add( + ps_aggregated_commitment_g1_x, + ps_aggregated_commitment_g1_y, + tp_g1_x, + tp_g1_y + ) + // (alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*r1 + tmp := mulmod(tmp, mload(add(PS_R_EVALS, mul(1, 0x20))), R_MOD) + // ((alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*r1) + ((alpha^{2}*Z_{T\S2}(y)/Z_{T\S0}(y))*r2) + aggregated_r_at_y := addmod(aggregated_r_at_y, tmp, R_MOD) + // r0 + (alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*r1 + ((alpha^{2}*Z_{T\S2}(y)/Z_{T\S0}(y))*r2) + aggregated_r_at_y := addmod(aggregated_r_at_y, mload(PS_R_EVALS), R_MOD) + tp_g1_x, tp_g1_y := point_mul(1, 2, aggregated_r_at_y) + ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y := point_sub( + ps_aggregated_commitment_g1_x, + ps_aggregated_commitment_g1_y, + tp_g1_x, + tp_g1_y + ) + // - ((Z_T(y)/Z_{T\S0}(y))*W(X)) + mstore(PS_VANISHING_AT_Y, mulmod(mload(PS_VANISHING_AT_Y), mload(PS_INV_ZTS0_AT_Y), R_MOD)) + tp_g1_x, tp_g1_y := point_mul( + mload(MEM_PROOF_COMMITMENT_2_G1_X), + mload(MEM_PROOF_COMMITMENT_2_G1_Y), + mload(PS_VANISHING_AT_Y) + ) + ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y := point_sub( + ps_aggregated_commitment_g1_x, + ps_aggregated_commitment_g1_y, + tp_g1_x, + tp_g1_y + ) + // L(X)/Z_{T\S0}(y) is aggregated + + // Now check W'(X) = L(X) / (Z_{T\S0}(y)*(x-y)) + // L(X)/Z_{T\S0}(y) + (y*W'(X)) - (x*W'(X)) = 0 + tp_g1_x, tp_g1_y := point_mul( + mload(MEM_PROOF_COMMITMENT_3_G1_X), + mload(MEM_PROOF_COMMITMENT_3_G1_Y), + mload(PVS_Y) + ) + ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y := point_add( + ps_aggregated_commitment_g1_x, + ps_aggregated_commitment_g1_y, + tp_g1_x, + tp_g1_y + ) + let is_zero_commitment + if iszero(mload(MEM_PROOF_COMMITMENT_3_G1_Y)) { + if gt(mload(MEM_PROOF_COMMITMENT_3_G1_X), 0) { + revertWithMessage(21, "non zero x value [CO]") + } + is_zero_commitment := 1 + } + + out := pairing_check(ps_aggregated_commitment_g1_x, ps_aggregated_commitment_g1_y, is_zero_commitment) + } + + /** + * @dev Generates the rolling hash using `val` and updates the transcript. + * The computation is done as follows: + * new_state_0 = keccak256(uint32(0) || old_state_0 || old_state_1 || value) + * new_state_1 = keccak256(uint32(1) || old_state_0 || old_state_1 || value) + * + * @notice The computation assumes that the memory slots 0x200 - 0x202 are clean and doesn't explicitly clean them + */ + function update_transcript(value) { + mstore8(TRANSCRIPT_DST_BYTE_SLOT, 0x00) + mstore(TRANSCRIPT_CHALLENGE_SLOT, value) + let newState0 := keccak256(TRANSCRIPT_BEGIN_SLOT, 0x64) + mstore8(TRANSCRIPT_DST_BYTE_SLOT, 0x01) + let newState1 := keccak256(TRANSCRIPT_BEGIN_SLOT, 0x64) + mstore(TRANSCRIPT_STATE_1_SLOT, newState1) + mstore(TRANSCRIPT_STATE_0_SLOT, newState0) + } + + /** + * @dev Generates a new challenge with (uint32(2) || state_0 || state_1 || uint32(challenge_counter)) + * The challenge_counter is incremented after every challenge + */ + function get_challenge(challenge_counter) -> challenge { + mstore8(TRANSCRIPT_DST_BYTE_SLOT, 0x02) + mstore(TRANSCRIPT_CHALLENGE_SLOT, shl(224, challenge_counter)) + challenge := and(keccak256(TRANSCRIPT_BEGIN_SLOT, 0x48), FR_MASK) + } + + /** + * @dev Performs scalar multiplication: point * scalar -> t + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + */ + function point_mul(p_x, p_y, s) -> t_x, t_y { + mstore(0x80, p_x) + mstore(0xa0, p_y) + mstore(0xc0, s) + + let success := staticcall(gas(), 7, 0x80, 0x60, 0x80, 0x40) + if iszero(success) { + revertWithMessage(27, "point multiplication failed") + } + t_x := mload(0x80) + t_y := mload(add(0x80, 0x20)) + } + + /** + * @dev Performs point addition: point 1 + point 2 -> t + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + */ + function point_add(p1_x, p1_y, p2_x, p2_y) -> t_x, t_y { + mstore(0x80, p1_x) + mstore(0xa0, p1_y) + mstore(0xc0, p2_x) + mstore(0xe0, p2_y) + + let success := staticcall(gas(), 6, 0x80, 0x80, 0x80, 0x40) + if iszero(success) { + revertWithMessage(21, "point addition failed") + } + + t_x := mload(0x80) + t_y := mload(add(0x80, 0x20)) + } + + /** + * @dev Performs point subtraction: point 1 + point 2 -> t + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + * @notice We don't consider the highly unlikely case where p2 can be a point-at-infinity and the function would revert. + */ + function point_sub(p1_x, p1_y, p2_x, p2_y) -> t_x, t_y { + mstore(0x80, p1_x) + mstore(0xa0, p1_y) + mstore(0xc0, p2_x) + mstore(0xe0, sub(Q_MOD, p2_y)) + + let success := staticcall(gas(), 6, 0x80, 0x80, 0x80, 0x40) + if iszero(success) { + revertWithMessage(24, "point subtraction failed") + } + + t_x := mload(0x80) + t_y := mload(add(0x80, 0x20)) + } + + /** + * @dev Calculates EC Pairing result following the EIP-197: https://eips.ethereum.org/EIPS/eip-197 + * Performs point negation before pairing calculation, if the flag `is_zero_commitment` is true + * + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + * While code reformatting consider not to overwrite the first constant-defined memory location, which is currently + * TRANSCRIPT_BEGIN_SLOT = 0x200 + */ + function pairing_check(p1_x, p1_y, is_zero_commitment) -> res { + mstore(0x80, p1_x) + mstore(0xa0, p1_y) + mstore(0xc0, VK_G2_ELEMENT_0_X1) + mstore(0xe0, VK_G2_ELEMENT_0_X2) + mstore(0x100, VK_G2_ELEMENT_0_Y1) + mstore(0x120, VK_G2_ELEMENT_0_Y2) + mstore(0x140, mload(MEM_PROOF_COMMITMENT_3_G1_X)) + mstore(0x160, mload(MEM_PROOF_COMMITMENT_3_G1_Y)) + if iszero(is_zero_commitment) { + mstore(0x160, sub(Q_MOD, mload(MEM_PROOF_COMMITMENT_3_G1_Y))) + } + mstore(0x180, VK_G2_ELEMENT_1_X1) + mstore(0x1a0, VK_G2_ELEMENT_1_X2) + mstore(0x1c0, VK_G2_ELEMENT_1_Y1) + mstore(0x1e0, VK_G2_ELEMENT_1_Y2) + + let success := staticcall(gas(), 8, 0x80, mul(12, 0x20), 0x80, 0x20) + + if iszero(success) { + revertWithMessage(20, "pairing check failed") + } + res := mload(0x80) + } + + /** + * @dev Reverts with the desired custom error string. + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + */ + function revertWithMessage(len, reason) { + // "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)"))) + mstore(0x80, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // Data offset + mstore(0x84, 0x0000000000000000000000000000000000000000000000000000000000000020) + // Length of revert string + mstore(0xa4, len) + // Revert reason + mstore(0xc4, reason) + // Revert + revert(0x80, 0x64) + } + + /** + * @dev Performs modular exponentiation using the formula (value ^ power) mod R_MOD. + * @notice Stores values starting from the initial free memory pointer i.e., 0x80. + * The free memory pointer is not updated as it stays unused throughout the code execution. + */ + function modexp(value, power) -> res { + mstore(0x80, 0x20) + mstore(0xa0, 0x20) + mstore(0xc0, 0x20) + mstore(0xe0, value) + mstore(0x100, power) + mstore(0x120, R_MOD) + if iszero(staticcall(gas(), 5, 0x80, 0xc0, 0x80, 0x20)) { + revertWithMessage(24, "modexp precompile failed") + } + res := mload(0x80) + } + } + } +} diff --git a/tools/data/scheduler_key.json b/tools/data/plonk_scheduler_key.json similarity index 100% rename from tools/data/scheduler_key.json rename to tools/data/plonk_scheduler_key.json diff --git a/tools/data/verifier_contract_template.txt b/tools/data/plonk_verifier_contract_template.txt similarity index 99% rename from tools/data/verifier_contract_template.txt rename to tools/data/plonk_verifier_contract_template.txt index 5ef32b2c5..dc3b64d53 100644 --- a/tools/data/verifier_contract_template.txt +++ b/tools/data/plonk_verifier_contract_template.txt @@ -2,7 +2,7 @@ pragma solidity 0.8.24; -import {IVerifier} from "./chain-interfaces/IVerifier.sol"; +import {IVerifier} from "../chain-interfaces/IVerifier.sol"; /* solhint-disable max-line-length */ /// @author Matter Labs @@ -18,7 +18,7 @@ import {IVerifier} from "./chain-interfaces/IVerifier.sol"; /// * Plonk for ZKsync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf /// The notation used in the code is the same as in the papers. /* solhint-enable max-line-length */ -contract Verifier is IVerifier { +contract VerifierPlonk is IVerifier { /*////////////////////////////////////////////////////////////// Verification keys //////////////////////////////////////////////////////////////*/ diff --git a/tools/src/main.rs b/tools/src/main.rs index 746373fe4..f69448d87 100644 --- a/tools/src/main.rs +++ b/tools/src/main.rs @@ -106,38 +106,55 @@ lazy_static! { struct Opt { /// Input path to scheduler verification key file. #[structopt( - short = "i", - long = "input_path", - default_value = "data/scheduler_key.json" + long = "plonk_input_path", + default_value = "data/plonk_scheduler_key.json" )] - input_path: String, + plonk_input_path: String, + + /// Input path to scheduler verification key file for . + #[structopt( + long = "fflonk_input_path", + default_value = "data/fflonk_scheduler_key.json" + )] + fflonk_input_path: String, + + /// Output path to verifier contract file. + #[structopt(long = "fflonk_output_path", default_value = "data/VerifierFflonk.sol")] + fflonk_output_path: String, /// Output path to verifier contract file. - #[structopt(short = "o", long = "output_path", default_value = "data/Verifier.sol")] - output_path: String, + #[structopt(long = "plonk_output_path", default_value = "data/VerifierPlonk.sol")] + plonk_output_path: String, } fn main() -> Result<(), Box> { let opt = Opt::from_args(); - let reader = BufReader::new(File::open(&opt.input_path)?); + let plonk_reader = BufReader::new(File::open(&opt.plonk_input_path)?); + let fflonk_reader = BufReader::new(File::open(&opt.fflonk_input_path)?); - let vk: HashMap = from_reader(reader)?; + let plonk_vk: HashMap = from_reader(plonk_reader)?; + let fflonk_vk: HashMap = from_reader(fflonk_reader)?; - let verifier_contract_template = fs::read_to_string("data/verifier_contract_template.txt")?; + let plonk_verifier_contract_template = fs::read_to_string("data/plonk_verifier_contract_template.txt")?; + let fflonk_verifier_contract_template = fs::read_to_string("data/fflonk_verifier_contract_template.txt")?; - let verification_key = fs::read_to_string(&opt.input_path) - .expect(&format!("Unable to read from {}", &opt.input_path)); + let plonk_verification_key = fs::read_to_string(&opt.plonk_input_path) + .expect(&format!("Unable to read from {}", &opt.plonk_input_path)); + let fflonk_verification_key = fs::read_to_string(&opt.fflonk_input_path) + .expect(&format!("Unable to read from {}", &opt.fflonk_input_path)); - let verification_key: VerificationKey = - serde_json::from_str(&verification_key).unwrap(); + let plonk_verification_key: VerificationKey = + serde_json::from_str(&plonk_verification_key).unwrap(); + let fflonk_verification_key: VerificationKey = + serde_json::from_str(&fflonk_verification_key).unwrap(); - let vk_hash = hex::encode(calculate_verification_key_hash(verification_key).to_fixed_bytes()); + let plonk_vk_hash = hex::encode(calculate_verification_key_hash(verification_key).to_fixed_bytes()); let verifier_contract_template = - insert_residue_elements_and_commitments(&verifier_contract_template, &vk, &vk_hash)?; + insert_residue_elements_and_commitments(&verifier_contract_template, &vk, &plonk_vk_hash)?; - let mut file = File::create(opt.output_path)?; + let mut file = File::create(opt.plonk_output_path)?; file.write_all(verifier_contract_template.as_bytes())?; Ok(()) diff --git a/yarn.lock b/yarn.lock index 66cd67b1a..6ab96b427 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7931,10 +7931,3 @@ zksync-ethers@^5.9.0: integrity sha512-Y2Mx6ovvxO6UdC2dePLguVzvNToOY8iLWeq5ne+jgGSJxAi/f4He/NF6FNsf6x1aWX0o8dy4Df8RcOQXAkj5qw== dependencies: ethers "~5.7.0" - -zksync-web3@^0.15.4: - version "0.15.5" - resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.15.5.tgz#aabe379464963ab573e15948660a709f409b5316" - integrity sha512-97gB7OKJL4spegl8fGO54g6cvTd/75G6yFWZWEa2J09zhjTrfqabbwE/GwiUJkFQ5BbzoH4JaTlVz1hoYZI+DQ== - dependencies: - ethers "~5.7.0" From fb3254b900e1c8c3ba957fc859b10eb5348ae325 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Mon, 28 Oct 2024 03:06:23 +0300 Subject: [PATCH 03/15] Fix lints --- .../verifiers/DualVerifier.sol | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol index 7e8a37507..a3f47de2b 100644 --- a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol +++ b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol @@ -15,22 +15,22 @@ import {UnknownVerifierType, EmptyRecursiveAggregationInputLength} from "../../c /// needed for the smooth transition from PLONK based verifier to the FFLONK verifier. contract DualVerifier is IVerifier { /// @notice The latest FFLONK verifier contract. - IVerifierV2 public immutable fflonkVerifier; + IVerifierV2 public immutable FFLONK_VERIFIER; /// @notice PLONK verifier contract. - IVerifier public immutable plonkVerifier; + IVerifier public immutable PLONK_VERIFIER; /// @notice Type of verification for FFLONK verifier. - uint256 constant FFLONK_VERIFICATION_TYPE = 0; + uint256 internal constant FFLONK_VERIFICATION_TYPE = 0; /// @notice Type of verification for PLONK verifier. - uint256 constant PLONK_VERIFICATION_TYPE = 1; + uint256 internal constant PLONK_VERIFICATION_TYPE = 1; /// @param _fflonkVerifier The address of the FFLONK verifier contract. /// @param _plonkVerifier The address of the PLONK verifier contract. constructor(IVerifierV2 _fflonkVerifier, IVerifier _plonkVerifier) { - fflonkVerifier = _fflonkVerifier; - plonkVerifier = _plonkVerifier; + FFLONK_VERIFIER = _fflonkVerifier; + PLONK_VERIFIER = _plonkVerifier; } /// @notice Routes zk-SNARK proof verification to the appropriate verifier (FFLONK or PLONK) based on the proof type. @@ -55,10 +55,10 @@ contract DualVerifier is IVerifier { // The first element of `_recursiveAggregationInput` determines the verifier type (either FFLONK or PLONK). uint256 verifierType = _recursiveAggregationInput[0]; if (verifierType == FFLONK_VERIFICATION_TYPE) { - return fflonkVerifier.verify(_publicInputs, _proof); + return FFLONK_VERIFIER.verify(_publicInputs, _proof); } else if (_recursiveAggregationInput[1] == PLONK_VERIFICATION_TYPE) { return - plonkVerifier.verify( + PLONK_VERIFIER.verify( _publicInputs, _proof, _extractRecursiveAggregationInput(_recursiveAggregationInput) @@ -89,16 +89,16 @@ contract DualVerifier is IVerifier { /// @inheritdoc IVerifier function verificationKeyHash() external view returns (bytes32) { - return plonkVerifier.verificationKeyHash(); + return PLONK_VERIFIER.verificationKeyHash(); } /// @notice Calculates a keccak256 hash of the runtime loaded verification keys from the selected verifier. /// @return The keccak256 hash of the loaded verification keys based on the verifier. function verificationKeyHash(uint256 _verifierType) external view returns (bytes32) { if (_verifierType == FFLONK_VERIFICATION_TYPE) { - return fflonkVerifier.verificationKeyHash(); + return FFLONK_VERIFIER.verificationKeyHash(); } else if (_verifierType == PLONK_VERIFICATION_TYPE) { - return plonkVerifier.verificationKeyHash(); + return PLONK_VERIFIER.verificationKeyHash(); } // If the verifier type is unknown, revert with an error. else { From 37719a9065ecf91768c49b3c0f58c51b89735b46 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Mon, 28 Oct 2024 04:01:23 +0300 Subject: [PATCH 04/15] Fix CI --- .github/workflows/slither.yaml | 10 ++++++---- .../state-transition/verifiers/VerifierFflonk.sol | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/slither.yaml b/.github/workflows/slither.yaml index 50c9194dc..25e5b60fe 100644 --- a/.github/workflows/slither.yaml +++ b/.github/workflows/slither.yaml @@ -42,10 +42,12 @@ jobs: - name: Remove non-compiled files run: | rm -rf ./l1-contracts/contracts/state-transition/utils/ - rm -rf ./l1-contracts/contracts/state-transition/Verifier.sol - rm -rf ./l1-contracts/contracts/state-transition/TestnetVerifier.sol - rm -rf ./l1-contracts/contracts/dev-contracts/test/VerifierTest.sol - rm -rf ./l1-contracts/contracts/dev-contracts/test/VerifierRecursiveTest.sol + rm -rf ./l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol + rm -rf ./l1-contracts/contracts/state-transition/verifiers/VerifierPlonk.sol + rm -rf ./l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol + rm -rf ./l1-contracts/contracts/state-transition/verifiers/TestnetVerifier.sol + rm -rf ./l1-contracts/contracts/dev-contracts/test/PlonkVerifierTest.sol + rm -rf ./l1-contracts/contracts/dev-contracts/test/PlonkVerifierRecursiveTest.sol - name: Run Slither working-directory: l1-contracts diff --git a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol index 5353032e5..b6fbc6be6 100644 --- a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol +++ b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol @@ -1326,7 +1326,7 @@ contract VerifierFflonk is IVerifierV2 { // the identity check is reduced into following // L(X) - W'(X)*Z_{T\S0}(y)(X-y) == 0 // verifier has commitments to the C_i(X) polynomials - // verifer also recomputed r_i(y) + // verifier also recomputed r_i(y) // group constant and commitment parts // first prepare L(X)/Z_{T\S0}(y) // C(X) = C0(X) + ((alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*C1(X)) + ((alpha^2*Z_{T\S2}(y)/Z_{T\S0}(y))*C2(X)) From 57bea64d67a70e5fa9dd684fb37c398670bcb8cf Mon Sep 17 00:00:00 2001 From: vladbochok Date: Tue, 29 Oct 2024 20:22:39 +0300 Subject: [PATCH 05/15] Fix the issue --- .../contracts/state-transition/verifiers/DualVerifier.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol index a3f47de2b..ce581be81 100644 --- a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol +++ b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol @@ -56,7 +56,7 @@ contract DualVerifier is IVerifier { uint256 verifierType = _recursiveAggregationInput[0]; if (verifierType == FFLONK_VERIFICATION_TYPE) { return FFLONK_VERIFIER.verify(_publicInputs, _proof); - } else if (_recursiveAggregationInput[1] == PLONK_VERIFICATION_TYPE) { + } else if (verifierType == PLONK_VERIFICATION_TYPE) { return PLONK_VERIFIER.verify( _publicInputs, From 44be50c5012c06c5260fc101757ccb67848dee21 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Tue, 12 Nov 2024 02:01:03 +0700 Subject: [PATCH 06/15] Fix empty recursive aggregation input --- .../contracts/state-transition/verifiers/DualVerifier.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol index ce581be81..5c8143f91 100644 --- a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol +++ b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol @@ -48,7 +48,7 @@ contract DualVerifier is IVerifier { ) public view virtual returns (bool) { // Ensure the recursive aggregation input has a valid length (at least one element // for the proof system differentiator). - if (_recursiveAggregationInput.length > 0) { + if (_recursiveAggregationInput.length == 0) { revert EmptyRecursiveAggregationInputLength(); } From 858da06e9c0585b12c5b9f399aaed52eb97a3306 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Tue, 12 Nov 2024 02:03:57 +0700 Subject: [PATCH 07/15] Fix ordering and visibility --- .../verifiers/DualVerifier.sol | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol index 5c8143f91..f1b669381 100644 --- a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol +++ b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol @@ -45,7 +45,7 @@ contract DualVerifier is IVerifier { uint256[] calldata _publicInputs, uint256[] calldata _proof, uint256[] calldata _recursiveAggregationInput - ) public view virtual returns (bool) { + ) external view virtual returns (bool) { // Ensure the recursive aggregation input has a valid length (at least one element // for the proof system differentiator). if (_recursiveAggregationInput.length == 0) { @@ -70,23 +70,6 @@ contract DualVerifier is IVerifier { } } - /// @notice Extract the recursive aggregation input by removing the first element (proof type differentiator). - /// @param _recursiveAggregationInput The original recursive aggregation input array. - /// @return result A new array with the first element removed. The first element was used as a hack for - /// differentiator between FFLONK and PLONK proofs. - function _extractRecursiveAggregationInput( - uint256[] calldata _recursiveAggregationInput - ) internal pure returns (uint256[] memory result) { - uint256 length = _recursiveAggregationInput.length; - // Allocate memory for the new array (length - 1) since the first element is omitted. - result = new uint256[](length - 1); - - // Copy elements starting from index 1 (the second element) of the original array. - for (uint256 i = 1; i < length; ++i) { - result[i - 1] = _recursiveAggregationInput[i]; - } - } - /// @inheritdoc IVerifier function verificationKeyHash() external view returns (bytes32) { return PLONK_VERIFIER.verificationKeyHash(); @@ -105,4 +88,21 @@ contract DualVerifier is IVerifier { revert UnknownVerifierType(); } } + + /// @notice Extract the recursive aggregation input by removing the first element (proof type differentiator). + /// @param _recursiveAggregationInput The original recursive aggregation input array. + /// @return result A new array with the first element removed. The first element was used as a hack for + /// differentiator between FFLONK and PLONK proofs. + function _extractRecursiveAggregationInput( + uint256[] calldata _recursiveAggregationInput + ) internal pure returns (uint256[] memory result) { + uint256 length = _recursiveAggregationInput.length; + // Allocate memory for the new array (length - 1) since the first element is omitted. + result = new uint256[](length - 1); + + // Copy elements starting from index 1 (the second element) of the original array. + for (uint256 i = 1; i < length; ++i) { + result[i - 1] = _recursiveAggregationInput[i]; + } + } } From d7fabf85b4692d338d3c627f11df718ef960a6a3 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Tue, 12 Nov 2024 02:05:04 +0700 Subject: [PATCH 08/15] Remove unused error --- l1-contracts/contracts/common/L1ContractErrors.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/l1-contracts/contracts/common/L1ContractErrors.sol b/l1-contracts/contracts/common/L1ContractErrors.sol index b98b21d8f..5f3e95614 100644 --- a/l1-contracts/contracts/common/L1ContractErrors.sol +++ b/l1-contracts/contracts/common/L1ContractErrors.sol @@ -305,8 +305,6 @@ error ZeroAddress(); error ZeroBalance(); // 0xc84885d4 error ZeroChainId(); -// 0xe2e6f808 -error WrongRecursiveAggregationInputLength(); // 0xc352bb73 error UnknownVerifierType(); // 0xdf320f0a From 703acc170143d914b8c8e8b81829a3f335faa81e Mon Sep 17 00:00:00 2001 From: vladbochok Date: Tue, 12 Nov 2024 02:07:08 +0700 Subject: [PATCH 09/15] Fix function visibility overly permissive --- .../contracts/state-transition/verifiers/VerifierFflonk.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol index b6fbc6be6..3e0f5a573 100644 --- a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol +++ b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol @@ -137,7 +137,7 @@ contract VerifierFflonk is IVerifierV2 { function verify( uint256[] calldata, // _publicInputs uint256[] calldata // _proof - ) public view virtual returns (bool) { + ) external view virtual returns (bool) { // Beginning of the big inline assembly block that makes all the verification work. // Note: We use the custom memory layout, so the return value should be returned from the assembly, not // Solidity code. From 0b273979488ef3675c68b29599bee35c8f2c3330 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Fri, 22 Nov 2024 22:04:16 +0800 Subject: [PATCH 10/15] Update tool --- .gitignore | 2 +- .../verifiers/DualVerifier.sol | 2 +- tools/data/fflonk_scheduler_key.json | 100 ++++++ tools/src/fflonk.rs | 152 +++++++++ tools/src/main.rs | 308 ++---------------- tools/src/plonk.rs | 238 ++++++++++++++ tools/src/types.rs | 13 + tools/src/utils.rs | 37 +++ 8 files changed, 561 insertions(+), 291 deletions(-) create mode 100644 tools/data/fflonk_scheduler_key.json create mode 100644 tools/src/fflonk.rs create mode 100644 tools/src/plonk.rs create mode 100644 tools/src/types.rs create mode 100644 tools/src/utils.rs diff --git a/.gitignore b/.gitignore index 21c173828..aaaeefbbf 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ era_test_node.log* node_modules/ out/ target/ -tools/data/Verifier.sol +tools/data/*.sol typechain/ yarn-debug.log* yarn-error.log* diff --git a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol index f1b669381..776bf66a6 100644 --- a/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol +++ b/l1-contracts/contracts/state-transition/verifiers/DualVerifier.sol @@ -45,7 +45,7 @@ contract DualVerifier is IVerifier { uint256[] calldata _publicInputs, uint256[] calldata _proof, uint256[] calldata _recursiveAggregationInput - ) external view virtual returns (bool) { + ) public view virtual returns (bool) { // Ensure the recursive aggregation input has a valid length (at least one element // for the proof system differentiator). if (_recursiveAggregationInput.length == 0) { diff --git a/tools/data/fflonk_scheduler_key.json b/tools/data/fflonk_scheduler_key.json new file mode 100644 index 000000000..2e449c652 --- /dev/null +++ b/tools/data/fflonk_scheduler_key.json @@ -0,0 +1,100 @@ +{ + "n": 4095, + "c0": { + "x": [ + 17767763437950677547, + 12573007107480222784, + 289878165523007019, + 1573623620240242191 + ], + "y": [ + 14987375107961242666, + 17322044171909551213, + 8670314411921782092, + 2416181480258874015 + ], + "infinity": false + }, + "num_inputs": 1, + "num_state_polys": 3, + "num_witness_polys": 0, + "total_lookup_entries_length": 0, + "non_residues": [ + [ + 5, + 0, + 0, + 0 + ], + [ + 7, + 0, + 0, + 0 + ] + ], + "g2_elements": [ + { + "x": { + "c0": [ + 5106727233969649389, + 7440829307424791261, + 4785637993704342649, + 1729627375292849782 + ], + "c1": [ + 10945020018377822914, + 17413811393473931026, + 8241798111626485029, + 1841571559660931130 + ] + }, + "y": { + "c0": [ + 5541340697920699818, + 16416156555105522555, + 5380518976772849807, + 1353435754470862315 + ], + "c1": [ + 6173549831154472795, + 13567992399387660019, + 17050234209342075797, + 650358724130500725 + ] + }, + "infinity": false + }, + { + "x": { + "c0": [ + 15451167643329881831, + 9228925060279880290, + 4433306395751374620, + 1255845450637707535 + ], + "c1": [ + 476882876249840273, + 16793192598945834288, + 8893001436335237180, + 1329697912054486455 + ] + }, + "y": { + "c0": [ + 15206726185137783154, + 1494909695784804695, + 10827978837991643599, + 532622142151833136 + ], + "c1": [ + 12161637074950439387, + 14517847680076186446, + 5367707808722889925, + 2675751259853944939 + ] + }, + "infinity": false + } + ] +} diff --git a/tools/src/fflonk.rs b/tools/src/fflonk.rs new file mode 100644 index 000000000..e013a091d --- /dev/null +++ b/tools/src/fflonk.rs @@ -0,0 +1,152 @@ +use handlebars::Handlebars; +use serde_json::json; + +use std::collections::HashMap; +use std::error::Error; + +use lazy_static::lazy_static; +use serde_json::Value; + +use crate::types::{CommitmentSlot, G2Elements}; +use crate::utils::{format_const, convert_list_to_hexadecimal, create_hash_map}; + +lazy_static! { + static ref G2_ELEMENTS: HashMap<&'static str, G2Elements> = create_hash_map(&[( + "g2_elements", + G2Elements { + x1: "VK_G2_ELEMENT_{}_X1", + x2: "VK_G2_ELEMENT_{}_X2", + y1: "VK_G2_ELEMENT_{}_Y1", + y2: "VK_G2_ELEMENT_{}_Y2", + } + ),]); + static ref NON_RESIDUES: HashMap<&'static str, &'static str> = + create_hash_map(&[("non_residues", "VK_NON_RESIDUES_{}"),]); + static ref COMMITMENT: HashMap<&'static str, CommitmentSlot> = + create_hash_map(&[( + "c0", + CommitmentSlot { + x: "VK_C0_G1_X", + y: "VK_C0_G1_Y" + } + ),]); +} + +pub fn insert_residue_elements_and_commitments( + template: &str, + vk: &HashMap, + vk_hash: &str, +) -> Result> { + let reg = Handlebars::new(); + let residue_g2_elements = generate_residue_g2_elements(vk); + let commitments = generate_commitments(vk); + + let verifier_contract_template = + template.replace("{{residue_g2_elements}}", &residue_g2_elements); + + Ok(reg.render_template( + &verifier_contract_template, + &json!({"residue_g2_elements": residue_g2_elements, "c0": commitments, + "vk_hash": vk_hash}), + )?) +} + + +fn extract_non_residues(items: &[Value], slot_name: &str) -> String { + items + .iter() + .enumerate() + .map(|(idx, item)| { + let hex_value = convert_list_to_hexadecimal( + item.as_array().expect("Failed to parse item as array"), + ); + format_const(&hex_value, &slot_name.replace("{}", &idx.to_string())) + }) + .collect::>() + .join("") +} + +fn extract_g2_elements(elements: &[Value], g2_elements: G2Elements) -> String { + let slots: [&str; 4] = [ + g2_elements.x1, + g2_elements.x2, + g2_elements.y1, + g2_elements.y2, + ]; + let xy_pairs = [("x", "c1"), ("x", "c0"), ("y", "c1"), ("y", "c0")]; + + elements + .iter() + .enumerate() + .flat_map(|(idx, element)| { + xy_pairs.iter().enumerate().map(move |(i, &(xy, c))| { + let field = element + .get(xy) + .unwrap_or_else(|| panic!("{} value not found", xy)) + .as_object() + .unwrap_or_else(|| panic!("{} value not an object", xy)); + + let c_value = convert_list_to_hexadecimal( + field + .get(c) + .unwrap_or_else(|| panic!("{} value not found", c)) + .as_array() + .unwrap_or_else(|| panic!("{} value not an array", c)), + ); + format_const(&c_value, &slots[i].replace("{}", &idx.to_string())) + }) + }) + .collect::>() + .join("") +} + +fn generate_residue_g2_elements(vk: &HashMap) -> String { + let mut residue_g2_elements = String::new(); + + let vk_non_residues = vk.get("non_residues").unwrap().as_array().unwrap(); + residue_g2_elements.push_str("// k1 = 5, k2 = 7\n"); + residue_g2_elements.push_str(&extract_non_residues( + vk_non_residues, + NON_RESIDUES["non_residues"], + )); + + let vk_g2_elements = vk.get("g2_elements").unwrap().as_array().unwrap(); + residue_g2_elements.push_str("\n // G2 Elements = [1]_2, [s]_2\n"); + residue_g2_elements.push_str(&extract_g2_elements( + vk_g2_elements, + G2_ELEMENTS["g2_elements"], + )); + + residue_g2_elements +} + +fn generate_commitments(vk: &HashMap) -> String { + let mut commitment = String::new(); + + let c0 = vk.get("c0").unwrap(); + commitment.push_str("// [C0]1 = qL(X^8)+ X*qR(X^8)+ X^2*qO(X^8)+ X^3*qM(X^8)+ X^4*qC(X^8)+ X^5*Sσ1(X^8)+ X^6*Sσ2(X^8)+ X^7*Sσ3(X^8)\n"); + commitment.push_str(&extract_commitment_slots( + &[c0.clone()], + COMMITMENT["c0"], + )); + + commitment +} + +fn extract_commitment_slots(items: &[Value], slot_tuple: CommitmentSlot) -> String { + items + .iter() + .enumerate() + .map(|(idx, item)| { + let map = item.as_object().unwrap(); + let x = map.get("x").unwrap().as_array().unwrap(); + let y = map.get("y").unwrap().as_array().unwrap(); + let x = convert_list_to_hexadecimal(x); + let mstore_x = format_const(&x, &slot_tuple.x.replace("{}", &idx.to_string())); + let y = convert_list_to_hexadecimal(y); + let mstore_y = format_const(&y, &slot_tuple.y.replace("{}", &idx.to_string())); + format!("{}{}", mstore_x, mstore_y) + }) + .collect::>() + .join("") +} diff --git a/tools/src/main.rs b/tools/src/main.rs index f69448d87..af3e9de12 100644 --- a/tools/src/main.rs +++ b/tools/src/main.rs @@ -1,8 +1,6 @@ use circuit_definitions::snark_wrapper::franklin_crypto::bellman::plonk::better_better_cs::setup::VerificationKey; use circuit_definitions::snark_wrapper::franklin_crypto::bellman::pairing::bn256::Bn256; use circuit_definitions::circuit_definitions::aux_layer::ZkSyncSnarkWrapperCircuit; -use handlebars::Handlebars; -use serde_json::json; use zksync_crypto::calculate_verification_key_hash; use std::collections::HashMap; use std::error::Error; @@ -10,93 +8,15 @@ use std::fs; use std::fs::File; use std::io::{BufReader, Write}; -use lazy_static::lazy_static; +pub mod plonk; +pub mod fflonk; +pub mod types; +pub mod utils; + use serde_json::{from_reader, Value}; use structopt::StructOpt; - -#[derive(Debug, Clone, Copy)] -struct CommitmentSlot { - x: &'static str, - y: &'static str, -} - -#[derive(Debug, Clone, Copy)] -struct G2Elements { - x1: &'static str, - x2: &'static str, - y1: &'static str, - y2: &'static str, -} - -fn create_hash_map( - key_value_pairs: &[(&'static str, Type)], -) -> HashMap<&'static str, Type> { - let mut hash_map = HashMap::new(); - for &(key, value) in key_value_pairs { - hash_map.insert(key, value); - } - hash_map -} - -lazy_static! { - static ref COMMITMENTS_SLOTS: HashMap<&'static str, CommitmentSlot> = create_hash_map(&[ - ( - "gate_setup_commitments", - CommitmentSlot { - x: "VK_GATE_SETUP_{}_X_SLOT", - y: "VK_GATE_SETUP_{}_Y_SLOT" - } - ), - ( - "gate_selectors_commitments", - CommitmentSlot { - x: "VK_GATE_SELECTORS_{}_X_SLOT", - y: "VK_GATE_SELECTORS_{}_Y_SLOT" - } - ), - ( - "permutation_commitments", - CommitmentSlot { - x: "VK_PERMUTATION_{}_X_SLOT", - y: "VK_PERMUTATION_{}_Y_SLOT" - } - ), - ( - "lookup_tables_commitments", - CommitmentSlot { - x: "VK_LOOKUP_TABLE_{}_X_SLOT", - y: "VK_LOOKUP_TABLE_{}_Y_SLOT" - } - ), - ]); - static ref INDIVIDUAL_COMMITMENTS: HashMap<&'static str, CommitmentSlot> = create_hash_map(&[ - ( - "lookup_selector_commitment", - CommitmentSlot { - x: "VK_LOOKUP_SELECTOR_X_SLOT", - y: "VK_LOOKUP_SELECTOR_Y_SLOT" - } - ), - ( - "lookup_table_type_commitment", - CommitmentSlot { - x: "VK_LOOKUP_TABLE_TYPE_X_SLOT", - y: "VK_LOOKUP_TABLE_TYPE_Y_SLOT" - } - ), - ]); - static ref G2_ELEMENTS: HashMap<&'static str, G2Elements> = create_hash_map(&[( - "g2_elements", - G2Elements { - x1: "G2_ELEMENTS_{}_X1", - x2: "G2_ELEMENTS_{}_X2", - y1: "G2_ELEMENTS_{}_Y1", - y2: "G2_ELEMENTS_{}_Y2", - } - ),]); - static ref NON_RESIDUES: HashMap<&'static str, &'static str> = - create_hash_map(&[("non_residues", "NON_RESIDUES_{}"),]); -} +use plonk::insert_residue_elements_and_commitments as plonk_insert_residue_elements_and_commitments; +use fflonk::insert_residue_elements_and_commitments as fflonk_insert_residue_elements_and_commitments; #[derive(Debug, StructOpt)] #[structopt( @@ -140,214 +60,24 @@ fn main() -> Result<(), Box> { let fflonk_verifier_contract_template = fs::read_to_string("data/fflonk_verifier_contract_template.txt")?; let plonk_verification_key = fs::read_to_string(&opt.plonk_input_path) - .expect(&format!("Unable to read from {}", &opt.plonk_input_path)); - let fflonk_verification_key = fs::read_to_string(&opt.fflonk_input_path) - .expect(&format!("Unable to read from {}", &opt.fflonk_input_path)); + .unwrap_or_else(|_| panic!("Unable to read from {}", &opt.plonk_input_path)); let plonk_verification_key: VerificationKey = serde_json::from_str(&plonk_verification_key).unwrap(); - let fflonk_verification_key: VerificationKey = - serde_json::from_str(&fflonk_verification_key).unwrap(); - - let plonk_vk_hash = hex::encode(calculate_verification_key_hash(verification_key).to_fixed_bytes()); - - let verifier_contract_template = - insert_residue_elements_and_commitments(&verifier_contract_template, &vk, &plonk_vk_hash)?; - let mut file = File::create(opt.plonk_output_path)?; + let plonk_vk_hash = hex::encode(calculate_verification_key_hash(plonk_verification_key).to_fixed_bytes()); - file.write_all(verifier_contract_template.as_bytes())?; - Ok(()) -} - -fn insert_residue_elements_and_commitments( - template: &str, - vk: &HashMap, - vk_hash: &str, -) -> Result> { - let reg = Handlebars::new(); - let residue_g2_elements = generate_residue_g2_elements(vk); - let commitments = generate_commitments(vk); + let plonk_verifier_contract_template = + plonk_insert_residue_elements_and_commitments(&plonk_verifier_contract_template, &plonk_vk, &plonk_vk_hash)?; + // TODO: use fflonk vk hash + let fflonk_verifier_contract_template = + fflonk_insert_residue_elements_and_commitments(&fflonk_verifier_contract_template, &fflonk_vk, &plonk_vk_hash)?; - let verifier_contract_template = - template.replace("{{residue_g2_elements}}", &residue_g2_elements); + let mut plonk_file = File::create(opt.plonk_output_path)?; + plonk_file.write_all(plonk_verifier_contract_template.as_bytes())?; - Ok(reg.render_template( - &verifier_contract_template, - &json!({"residue_g2_elements": residue_g2_elements, - "commitments": commitments, - "vk_hash": vk_hash}), - )?) -} - -fn format_mstore(hex_value: &str, slot: &str) -> String { - format!(" mstore({}, 0x{})\n", slot, hex_value) -} + let mut fflonk_file = File::create(opt.fflonk_output_path)?; + fflonk_file.write_all(fflonk_verifier_contract_template.as_bytes())?; -fn format_const(hex_value: &str, slot_name: &str) -> String { - let hex_value = hex_value.trim_start_matches('0'); - let formatted_hex_value = if hex_value.len() < 64 && hex_value.len() >= 1 { - format!("0{}", hex_value) - } else { - String::from(hex_value) - }; - format!( - " uint256 internal constant {} = 0x{};\n", - slot_name, formatted_hex_value - ) -} - -fn convert_list_to_hexadecimal(numbers: &Vec) -> String { - numbers - .iter() - .map(|v| format!("{:01$x}", v.as_u64().expect("Failed to parse as u64"), 16)) - .rev() - .collect::() -} - -fn extract_commitment_slots(items: &[Value], slot_tuple: CommitmentSlot) -> String { - items - .iter() - .enumerate() - .map(|(idx, item)| { - let map = item.as_object().unwrap(); - let x = map.get("x").unwrap().as_array().unwrap(); - let y = map.get("y").unwrap().as_array().unwrap(); - let x = convert_list_to_hexadecimal(x); - let mstore_x = format_mstore(&x, &slot_tuple.x.replace("{}", &idx.to_string())); - let y = convert_list_to_hexadecimal(y); - let mstore_y = format_mstore(&y, &slot_tuple.y.replace("{}", &idx.to_string())); - format!("{}{}", mstore_x, mstore_y) - }) - .collect::>() - .join("") -} - -fn extract_non_residues(items: &[Value], slot_name: &str) -> String { - items - .iter() - .enumerate() - .map(|(idx, item)| { - let hex_value = convert_list_to_hexadecimal( - item.as_array().expect("Failed to parse item as array"), - ); - format_const(&hex_value, &slot_name.replace("{}", &idx.to_string())) - }) - .collect::>() - .join("") -} - -fn extract_individual_commitments(item: &Value, commitment_slot: CommitmentSlot) -> String { - let x = convert_list_to_hexadecimal( - item.get("x") - .expect("x value not found") - .as_array() - .expect("x value not an array"), - ); - let mut output = format_mstore(&x, commitment_slot.x); - - let y = convert_list_to_hexadecimal( - item.get("y") - .expect("y value not found") - .as_array() - .expect("y value not an array"), - ); - output.push_str(&format_mstore(&y, commitment_slot.y)); - - output -} - -fn extract_g2_elements(elements: &[Value], g2_elements: G2Elements) -> String { - let slots: [&str; 4] = [ - g2_elements.x1, - g2_elements.x2, - g2_elements.y1, - g2_elements.y2, - ]; - let xy_pairs = [("x", "c1"), ("x", "c0"), ("y", "c1"), ("y", "c0")]; - - elements - .iter() - .enumerate() - .flat_map(|(idx, element)| { - xy_pairs.iter().enumerate().map(move |(i, &(xy, c))| { - let field = element - .get(xy) - .expect(&format!("{} value not found", xy)) - .as_object() - .expect(&format!("{} value not an object", xy)); - - let c_value = convert_list_to_hexadecimal( - field - .get(c) - .expect(&format!("{} value not found", c)) - .as_array() - .expect(&format!("{} value not an array", c)), - ); - format_const(&c_value, &slots[i].replace("{}", &idx.to_string())) - }) - }) - .collect::>() - .join("") -} - -fn generate_commitments(vk: &HashMap) -> String { - let commitments_data = [ - ("gate_setup_commitments", "gate setup commitments"), - ("gate_selectors_commitments", "gate selectors commitments"), - ("permutation_commitments", "permutation commitments"), - ("lookup_tables_commitments", "lookup tables commitments"), - ]; - - let individual_commitments_data = [ - ("lookup_selector_commitment", "lookup selector commitment"), - ("lookup_table_type_commitment", "table type commitment"), - ]; - - let commitments = commitments_data - .iter() - .map(|(key, comment)| { - let data = vk.get(*key).unwrap().as_array().unwrap(); - format!( - " // {}\n{}", - comment, - extract_commitment_slots(data, COMMITMENTS_SLOTS[*key]) - ) - }) - .collect::>() - .join("\n"); - - let individual_commitments = individual_commitments_data - .iter() - .map(|(key, comment)| { - format!( - "\n // {}\n{}", - comment, - extract_individual_commitments(vk.get(*key).unwrap(), INDIVIDUAL_COMMITMENTS[*key]) - ) - }) - .collect::>() - .join(""); - - format!("{}{}", commitments, individual_commitments) -} - -fn generate_residue_g2_elements(vk: &HashMap) -> String { - let mut residue_g2_elements = String::new(); - - let vk_non_residues = vk.get("non_residues").unwrap().as_array().unwrap(); - residue_g2_elements.push_str("// non residues\n"); - residue_g2_elements.push_str(&extract_non_residues( - vk_non_residues, - NON_RESIDUES["non_residues"], - )); - - let vk_g2_elements = vk.get("g2_elements").unwrap().as_array().unwrap(); - residue_g2_elements.push_str("\n // trusted setup g2 elements\n"); - residue_g2_elements.push_str(&extract_g2_elements( - vk_g2_elements, - G2_ELEMENTS["g2_elements"], - )); - - residue_g2_elements + Ok(()) } diff --git a/tools/src/plonk.rs b/tools/src/plonk.rs new file mode 100644 index 000000000..41fb606ff --- /dev/null +++ b/tools/src/plonk.rs @@ -0,0 +1,238 @@ +use handlebars::Handlebars; +use serde_json::json; + +use std::collections::HashMap; +use std::error::Error; + +use lazy_static::lazy_static; +use serde_json::Value; + +use crate::types::{CommitmentSlot, G2Elements}; +use crate::utils::{format_mstore, format_const, convert_list_to_hexadecimal, create_hash_map}; + +lazy_static! { + static ref COMMITMENTS_SLOTS: HashMap<&'static str, CommitmentSlot> = create_hash_map(&[ + ( + "gate_setup_commitments", + CommitmentSlot { + x: "VK_GATE_SETUP_{}_X_SLOT", + y: "VK_GATE_SETUP_{}_Y_SLOT" + } + ), + ( + "gate_selectors_commitments", + CommitmentSlot { + x: "VK_GATE_SELECTORS_{}_X_SLOT", + y: "VK_GATE_SELECTORS_{}_Y_SLOT" + } + ), + ( + "permutation_commitments", + CommitmentSlot { + x: "VK_PERMUTATION_{}_X_SLOT", + y: "VK_PERMUTATION_{}_Y_SLOT" + } + ), + ( + "lookup_tables_commitments", + CommitmentSlot { + x: "VK_LOOKUP_TABLE_{}_X_SLOT", + y: "VK_LOOKUP_TABLE_{}_Y_SLOT" + } + ), + ]); + static ref INDIVIDUAL_COMMITMENTS: HashMap<&'static str, CommitmentSlot> = create_hash_map(&[ + ( + "lookup_selector_commitment", + CommitmentSlot { + x: "VK_LOOKUP_SELECTOR_X_SLOT", + y: "VK_LOOKUP_SELECTOR_Y_SLOT" + } + ), + ( + "lookup_table_type_commitment", + CommitmentSlot { + x: "VK_LOOKUP_TABLE_TYPE_X_SLOT", + y: "VK_LOOKUP_TABLE_TYPE_Y_SLOT" + } + ), + ]); + static ref G2_ELEMENTS: HashMap<&'static str, G2Elements> = create_hash_map(&[( + "g2_elements", + G2Elements { + x1: "G2_ELEMENTS_{}_X1", + x2: "G2_ELEMENTS_{}_X2", + y1: "G2_ELEMENTS_{}_Y1", + y2: "G2_ELEMENTS_{}_Y2", + } + ),]); + static ref NON_RESIDUES: HashMap<&'static str, &'static str> = + create_hash_map(&[("non_residues", "NON_RESIDUES_{}"),]); +} + +pub fn insert_residue_elements_and_commitments( + template: &str, + vk: &HashMap, + vk_hash: &str, +) -> Result> { + let reg = Handlebars::new(); + let residue_g2_elements = generate_residue_g2_elements(vk); + let commitments = generate_commitments(vk); + + let verifier_contract_template = + template.replace("{{residue_g2_elements}}", &residue_g2_elements); + + Ok(reg.render_template( + &verifier_contract_template, + &json!({"residue_g2_elements": residue_g2_elements, + "commitments": commitments, + "vk_hash": vk_hash}), + )?) +} + +fn extract_commitment_slots(items: &[Value], slot_tuple: CommitmentSlot) -> String { + items + .iter() + .enumerate() + .map(|(idx, item)| { + let map = item.as_object().unwrap(); + let x = map.get("x").unwrap().as_array().unwrap(); + let y = map.get("y").unwrap().as_array().unwrap(); + let x = convert_list_to_hexadecimal(x); + let mstore_x = format_mstore(&x, &slot_tuple.x.replace("{}", &idx.to_string())); + let y = convert_list_to_hexadecimal(y); + let mstore_y = format_mstore(&y, &slot_tuple.y.replace("{}", &idx.to_string())); + format!("{}{}", mstore_x, mstore_y) + }) + .collect::>() + .join("") +} + +fn extract_non_residues(items: &[Value], slot_name: &str) -> String { + items + .iter() + .enumerate() + .map(|(idx, item)| { + let hex_value = convert_list_to_hexadecimal( + item.as_array().expect("Failed to parse item as array"), + ); + format_const(&hex_value, &slot_name.replace("{}", &idx.to_string())) + }) + .collect::>() + .join("") +} + +fn extract_individual_commitments(item: &Value, commitment_slot: CommitmentSlot) -> String { + let x = convert_list_to_hexadecimal( + item.get("x") + .expect("x value not found") + .as_array() + .expect("x value not an array"), + ); + let mut output = format_mstore(&x, commitment_slot.x); + + let y = convert_list_to_hexadecimal( + item.get("y") + .expect("y value not found") + .as_array() + .expect("y value not an array"), + ); + output.push_str(&format_mstore(&y, commitment_slot.y)); + + output +} + +fn extract_g2_elements(elements: &[Value], g2_elements: G2Elements) -> String { + let slots: [&str; 4] = [ + g2_elements.x1, + g2_elements.x2, + g2_elements.y1, + g2_elements.y2, + ]; + let xy_pairs = [("x", "c1"), ("x", "c0"), ("y", "c1"), ("y", "c0")]; + + elements + .iter() + .enumerate() + .flat_map(|(idx, element)| { + xy_pairs.iter().enumerate().map(move |(i, &(xy, c))| { + let field = element + .get(xy) + .unwrap_or_else(|| panic!("{} value not found", xy)) + .as_object() + .unwrap_or_else(|| panic!("{} value not an object", xy)); + + let c_value = convert_list_to_hexadecimal( + field + .get(c) + .unwrap_or_else(|| panic!("{} value not found", c)) + .as_array() + .unwrap_or_else(|| panic!("{} value not an array", c)), + ); + format_const(&c_value, &slots[i].replace("{}", &idx.to_string())) + }) + }) + .collect::>() + .join("") +} + +fn generate_commitments(vk: &HashMap) -> String { + let commitments_data = [ + ("gate_setup_commitments", "gate setup commitments"), + ("gate_selectors_commitments", "gate selectors commitments"), + ("permutation_commitments", "permutation commitments"), + ("lookup_tables_commitments", "lookup tables commitments"), + ]; + + let individual_commitments_data = [ + ("lookup_selector_commitment", "lookup selector commitment"), + ("lookup_table_type_commitment", "table type commitment"), + ]; + + let commitments = commitments_data + .iter() + .map(|(key, comment)| { + let data = vk.get(*key).unwrap().as_array().unwrap(); + format!( + " // {}\n{}", + comment, + extract_commitment_slots(data, COMMITMENTS_SLOTS[*key]) + ) + }) + .collect::>() + .join("\n"); + + let individual_commitments = individual_commitments_data + .iter() + .map(|(key, comment)| { + format!( + "\n // {}\n{}", + comment, + extract_individual_commitments(vk.get(*key).unwrap(), INDIVIDUAL_COMMITMENTS[*key]) + ) + }) + .collect::>() + .join(""); + + format!("{}{}", commitments, individual_commitments) +} + +fn generate_residue_g2_elements(vk: &HashMap) -> String { + let mut residue_g2_elements = String::new(); + + let vk_non_residues = vk.get("non_residues").unwrap().as_array().unwrap(); + residue_g2_elements.push_str("// non residues\n"); + residue_g2_elements.push_str(&extract_non_residues( + vk_non_residues, + NON_RESIDUES["non_residues"], + )); + + let vk_g2_elements = vk.get("g2_elements").unwrap().as_array().unwrap(); + residue_g2_elements.push_str("\n // trusted setup g2 elements\n"); + residue_g2_elements.push_str(&extract_g2_elements( + vk_g2_elements, + G2_ELEMENTS["g2_elements"], + )); + + residue_g2_elements +} diff --git a/tools/src/types.rs b/tools/src/types.rs new file mode 100644 index 000000000..1bcabcd25 --- /dev/null +++ b/tools/src/types.rs @@ -0,0 +1,13 @@ +#[derive(Debug, Clone, Copy)] +pub struct CommitmentSlot { + pub x: &'static str, + pub y: &'static str, +} + +#[derive(Debug, Clone, Copy)] +pub struct G2Elements { + pub x1: &'static str, + pub x2: &'static str, + pub y1: &'static str, + pub y2: &'static str, +} diff --git a/tools/src/utils.rs b/tools/src/utils.rs new file mode 100644 index 000000000..9f9a5be98 --- /dev/null +++ b/tools/src/utils.rs @@ -0,0 +1,37 @@ +use std::collections::HashMap; +use serde_json::{Value}; + +pub fn format_mstore(hex_value: &str, slot: &str) -> String { + format!(" mstore({}, 0x{})\n", slot, hex_value) +} + +pub fn format_const(hex_value: &str, slot_name: &str) -> String { + let hex_value = hex_value.trim_start_matches('0'); + let formatted_hex_value = if hex_value.len() < 64 && !hex_value.is_empty() { + format!("0{}", hex_value) + } else { + String::from(hex_value) + }; + format!( + " uint256 internal constant {} = 0x{};\n", + slot_name, formatted_hex_value + ) +} + +pub fn convert_list_to_hexadecimal(numbers: &Vec) -> String { + numbers + .iter() + .map(|v| format!("{:01$x}", v.as_u64().expect("Failed to parse as u64"), 16)) + .rev() + .collect::() +} + +pub fn create_hash_map( + key_value_pairs: &[(&'static str, Type)], +) -> HashMap<&'static str, Type> { + let mut hash_map = HashMap::new(); + for &(key, value) in key_value_pairs { + hash_map.insert(key, value); + } + hash_map +} From a60586d15b68e4579ff48ce15ba937daf895b01a Mon Sep 17 00:00:00 2001 From: vladbochok Date: Fri, 22 Nov 2024 22:06:53 +0800 Subject: [PATCH 11/15] Update template --- .../fflonk_verifier_contract_template.txt | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/tools/data/fflonk_verifier_contract_template.txt b/tools/data/fflonk_verifier_contract_template.txt index 5353032e5..fea1d4b66 100644 --- a/tools/data/fflonk_verifier_contract_template.txt +++ b/tools/data/fflonk_verifier_contract_template.txt @@ -22,21 +22,8 @@ contract VerifierFflonk is IVerifierV2 { // ================Verification Key================ uint256 internal constant VK_NUM_INPUTS = 1; - // [C0]1 = qL(X^8)+ X*qR(X^8)+ X^2*qO(X^8)+ X^3*qM(X^8)+ X^4*qC(X^8)+ X^5*Sσ1(X^8)+ X^6*Sσ2(X^8)+ X^7*Sσ3(X^8) - uint256 internal constant VK_C0_G1_X = 0x15c99dbc62b8191204ff93984b0de4fb7c79ac7a1ef2c94f4ce940319a2408b2; - uint256 internal constant VK_C0_G1_Y = 0x0521b86a104e07c8971bf2e17d7665d59df7566c08e6e0c9750f584bb24084ce; - // k1 = 5, k2 = 7 - uint256 internal constant VK_NON_RESIDUES_0 = 0x0000000000000000000000000000000000000000000000000000000000000005; - uint256 internal constant VK_NON_RESIDUES_1 = 0x0000000000000000000000000000000000000000000000000000000000000007; - // G2 Elements = [1]_2, [s]_2 - uint256 internal constant VK_G2_ELEMENT_0_X1 = 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2; - uint256 internal constant VK_G2_ELEMENT_0_X2 = 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed; - uint256 internal constant VK_G2_ELEMENT_0_Y1 = 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b; - uint256 internal constant VK_G2_ELEMENT_0_Y2 = 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa; - uint256 internal constant VK_G2_ELEMENT_1_X1 = 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1; - uint256 internal constant VK_G2_ELEMENT_1_X2 = 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0; - uint256 internal constant VK_G2_ELEMENT_1_Y1 = 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4; - uint256 internal constant VK_G2_ELEMENT_1_Y2 = 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55; + {{c0}} + {{residue_g2_elements}} // Memory slots from 0x000 to 0x200 are reserved for intermediate computations and call to precompiles. @@ -1326,7 +1313,7 @@ contract VerifierFflonk is IVerifierV2 { // the identity check is reduced into following // L(X) - W'(X)*Z_{T\S0}(y)(X-y) == 0 // verifier has commitments to the C_i(X) polynomials - // verifer also recomputed r_i(y) + // verifier also recomputed r_i(y) // group constant and commitment parts // first prepare L(X)/Z_{T\S0}(y) // C(X) = C0(X) + ((alpha*Z_{T\S1}(y)/Z_{T\S0}(y))*C1(X)) + ((alpha^2*Z_{T\S2}(y)/Z_{T\S0}(y))*C2(X)) From 774fdc4b8e0ff82a6f0849aaa9e1daa07812ab8a Mon Sep 17 00:00:00 2001 From: vladbochok Date: Fri, 22 Nov 2024 22:19:02 +0800 Subject: [PATCH 12/15] Update template & vks --- .../verifiers/VerifierFflonk.sol | 19 +++++++++++-------- .../fflonk_verifier_contract_template.txt | 6 +++--- tools/src/fflonk.rs | 5 +---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol index 3e0f5a573..25f64175f 100644 --- a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol +++ b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol @@ -23,20 +23,23 @@ contract VerifierFflonk is IVerifierV2 { // ================Verification Key================ uint256 internal constant VK_NUM_INPUTS = 1; // [C0]1 = qL(X^8)+ X*qR(X^8)+ X^2*qO(X^8)+ X^3*qM(X^8)+ X^4*qC(X^8)+ X^5*Sσ1(X^8)+ X^6*Sσ2(X^8)+ X^7*Sσ3(X^8) - uint256 internal constant VK_C0_G1_X = 0x15c99dbc62b8191204ff93984b0de4fb7c79ac7a1ef2c94f4ce940319a2408b2; - uint256 internal constant VK_C0_G1_Y = 0x0521b86a104e07c8971bf2e17d7665d59df7566c08e6e0c9750f584bb24084ce; + uint256 internal constant VK_C0_G1_X = 0x15d6a2585e95760f0405daa7dc24062bae7c4b597fd1fc40f693c6b58c39322b; + uint256 internal constant VK_C0_G1_Y = 0x218800413bb52a9f78532505358c354cf0644365f24b546dcffdda41118fc42a; + // k1 = 5, k2 = 7 - uint256 internal constant VK_NON_RESIDUES_0 = 0x0000000000000000000000000000000000000000000000000000000000000005; - uint256 internal constant VK_NON_RESIDUES_1 = 0x0000000000000000000000000000000000000000000000000000000000000007; + uint256 internal constant VK_NON_RESIDUES_0 = 0x05; + uint256 internal constant VK_NON_RESIDUES_1 = 0x07; + // G2 Elements = [1]_2, [s]_2 uint256 internal constant VK_G2_ELEMENT_0_X1 = 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2; uint256 internal constant VK_G2_ELEMENT_0_X2 = 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed; uint256 internal constant VK_G2_ELEMENT_0_Y1 = 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b; uint256 internal constant VK_G2_ELEMENT_0_Y2 = 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa; - uint256 internal constant VK_G2_ELEMENT_1_X1 = 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1; - uint256 internal constant VK_G2_ELEMENT_1_X2 = 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0; - uint256 internal constant VK_G2_ELEMENT_1_Y1 = 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4; - uint256 internal constant VK_G2_ELEMENT_1_Y2 = 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55; + uint256 internal constant VK_G2_ELEMENT_1_X1 = 0x12740934ba9615b77b6a49b06fcce83ce90d67b1d0e2a530069e3a7306569a91; + uint256 internal constant VK_G2_ELEMENT_1_X2 = 0x116da8c89a0d090f3d8644ada33a5f1c8013ba7204aeca62d66d931b99afe6e7; + uint256 internal constant VK_G2_ELEMENT_1_Y1 = 0x25222d9816e5f86b4a7dedd00d04acc5c979c18bd22b834ea8c6d07c0ba441db; + uint256 internal constant VK_G2_ELEMENT_1_Y2 = 0x076441042e77b6309644b56251f059cf14befc72ac8a6157d30924e58dc4c172; + // Memory slots from 0x000 to 0x200 are reserved for intermediate computations and call to precompiles. diff --git a/tools/data/fflonk_verifier_contract_template.txt b/tools/data/fflonk_verifier_contract_template.txt index fea1d4b66..55338615e 100644 --- a/tools/data/fflonk_verifier_contract_template.txt +++ b/tools/data/fflonk_verifier_contract_template.txt @@ -22,8 +22,8 @@ contract VerifierFflonk is IVerifierV2 { // ================Verification Key================ uint256 internal constant VK_NUM_INPUTS = 1; - {{c0}} - {{residue_g2_elements}} + {{{c0}}} + {{{residue_g2_elements}}} // Memory slots from 0x000 to 0x200 are reserved for intermediate computations and call to precompiles. @@ -124,7 +124,7 @@ contract VerifierFflonk is IVerifierV2 { function verify( uint256[] calldata, // _publicInputs uint256[] calldata // _proof - ) public view virtual returns (bool) { + ) external view virtual returns (bool) { // Beginning of the big inline assembly block that makes all the verification work. // Note: We use the custom memory layout, so the return value should be returned from the assembly, not // Solidity code. diff --git a/tools/src/fflonk.rs b/tools/src/fflonk.rs index e013a091d..b713d72a4 100644 --- a/tools/src/fflonk.rs +++ b/tools/src/fflonk.rs @@ -41,11 +41,8 @@ pub fn insert_residue_elements_and_commitments( let residue_g2_elements = generate_residue_g2_elements(vk); let commitments = generate_commitments(vk); - let verifier_contract_template = - template.replace("{{residue_g2_elements}}", &residue_g2_elements); - Ok(reg.render_template( - &verifier_contract_template, + &template, &json!({"residue_g2_elements": residue_g2_elements, "c0": commitments, "vk_hash": vk_hash}), )?) From fd1c5df06548777b46746f5841640e1e54667da7 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Fri, 22 Nov 2024 22:54:22 +0800 Subject: [PATCH 13/15] Try to fix L1 CI --- .../contracts/state-transition/verifiers/VerifierFflonk.sol | 2 +- l1-contracts/deploy-scripts/DeployL1.s.sol | 1 - tools/data/fflonk_verifier_contract_template.txt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol index 25f64175f..6b0523d81 100644 --- a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol +++ b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol @@ -40,7 +40,6 @@ contract VerifierFflonk is IVerifierV2 { uint256 internal constant VK_G2_ELEMENT_1_Y1 = 0x25222d9816e5f86b4a7dedd00d04acc5c979c18bd22b834ea8c6d07c0ba441db; uint256 internal constant VK_G2_ELEMENT_1_Y2 = 0x076441042e77b6309644b56251f059cf14befc72ac8a6157d30924e58dc4c172; - // Memory slots from 0x000 to 0x200 are reserved for intermediate computations and call to precompiles. // ================Transcript================ @@ -118,6 +117,7 @@ contract VerifierFflonk is IVerifierV2 { function verificationKeyHash() external pure returns (bytes32 vkHash) { return keccak256( + // solhint-disable-next-line func-named-parameters abi.encodePacked( VK_NUM_INPUTS, VK_C0_G1_X, diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index 7fa5a3ff9..c128200a4 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -12,7 +12,6 @@ import {Utils} from "./Utils.sol"; import {Multicall3} from "contracts/dev-contracts/Multicall3.sol"; import {DualVerifier} from "contracts/state-transition/verifiers/DualVerifier.sol"; import {VerifierPlonk} from "contracts/state-transition/verifiers/VerifierPlonk.sol"; -import {VerifierFFLONK} from "contracts/state-transition/verifiers/VerifierFFLONK.sol"; import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; import {DefaultUpgrade} from "contracts/upgrades/DefaultUpgrade.sol"; diff --git a/tools/data/fflonk_verifier_contract_template.txt b/tools/data/fflonk_verifier_contract_template.txt index 55338615e..f0887ebe0 100644 --- a/tools/data/fflonk_verifier_contract_template.txt +++ b/tools/data/fflonk_verifier_contract_template.txt @@ -24,7 +24,6 @@ contract VerifierFflonk is IVerifierV2 { uint256 internal constant VK_NUM_INPUTS = 1; {{{c0}}} {{{residue_g2_elements}}} - // Memory slots from 0x000 to 0x200 are reserved for intermediate computations and call to precompiles. // ================Transcript================ @@ -102,6 +101,7 @@ contract VerifierFflonk is IVerifierV2 { function verificationKeyHash() external pure returns (bytes32 vkHash) { return keccak256( + // solhint-disable-next-line func-named-parameters abi.encodePacked( VK_NUM_INPUTS, VK_C0_G1_X, From 0a7d9709c6cbef8c10f41cd619c576a307923046 Mon Sep 17 00:00:00 2001 From: vladbochok Date: Fri, 22 Nov 2024 22:58:24 +0800 Subject: [PATCH 14/15] Fix scripts --- l1-contracts/deploy-scripts/DeployL1.s.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index c128200a4..96ca6946c 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -12,6 +12,7 @@ import {Utils} from "./Utils.sol"; import {Multicall3} from "contracts/dev-contracts/Multicall3.sol"; import {DualVerifier} from "contracts/state-transition/verifiers/DualVerifier.sol"; import {VerifierPlonk} from "contracts/state-transition/verifiers/VerifierPlonk.sol"; +import {VerifierFflonk} from "contracts/state-transition/verifiers/VerifierFflonk.sol"; import {TestnetVerifier} from "contracts/state-transition/verifiers/TestnetVerifier.sol"; import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; import {DefaultUpgrade} from "contracts/upgrades/DefaultUpgrade.sol"; From c2c4c0f8b5c5e3acd31c2354095b79032fff218c Mon Sep 17 00:00:00 2001 From: vladbochok Date: Tue, 26 Nov 2024 14:49:39 +0800 Subject: [PATCH 15/15] Update verification keys and README --- .../verifiers/VerifierFflonk.sol | 4 ++-- tools/README.md | 6 +++--- tools/data/fflonk_scheduler_key.json | 18 +++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol index 6b0523d81..fa98455af 100644 --- a/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol +++ b/l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol @@ -23,8 +23,8 @@ contract VerifierFflonk is IVerifierV2 { // ================Verification Key================ uint256 internal constant VK_NUM_INPUTS = 1; // [C0]1 = qL(X^8)+ X*qR(X^8)+ X^2*qO(X^8)+ X^3*qM(X^8)+ X^4*qC(X^8)+ X^5*Sσ1(X^8)+ X^6*Sσ2(X^8)+ X^7*Sσ3(X^8) - uint256 internal constant VK_C0_G1_X = 0x15d6a2585e95760f0405daa7dc24062bae7c4b597fd1fc40f693c6b58c39322b; - uint256 internal constant VK_C0_G1_Y = 0x218800413bb52a9f78532505358c354cf0644365f24b546dcffdda41118fc42a; + uint256 internal constant VK_C0_G1_X = 0x01d6dcc891e4d98fe2cbe0c77d424256931d16ac0e8b7f5834ecb1f68657e218; + uint256 internal constant VK_C0_G1_Y = 0x018cb64f30946fa1011c26bc90e314ddf529a5eaff15d4cb546030887a51e41a; // k1 = 5, k2 = 7 uint256 internal constant VK_NON_RESIDUES_0 = 0x05; diff --git a/tools/README.md b/tools/README.md index 081ab8d70..abf93e9b5 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,9 +1,9 @@ -# Tool for generating `Verifier.sol` using json Verification key +# Tool for generating Plonk & Fflonk verifier contracts using json verification keys -`cargo run --bin zksync_verifier_contract_generator --release -- --input_path /path/to/scheduler_verification_key.json --output_path /path/to/Verifier.sol` +`cargo run --bin zksync_verifier_contract_generator --release -- --plonk_input_path /path/to/plonk_scheduler_verification_key.json --fflonk_input_path /path/to/fflonk_scheduler_verification_key.json --plonk_output_path /path/to/VerifierPlonk.sol --fflonk_output_path /path/to/VerifierFflonk.sol` To generate the verifier from the scheduler key in 'data' directory, just run: ```shell -cargo run --bin zksync_verifier_contract_generator --release -- --input_path data/scheduler_key.json --output_path ../l1-contracts/contracts/state-transition/Verifier.sol +cargo run --bin zksync_verifier_contract_generator --release -- --plonk_input_path data/plonk_scheduler_key.json --fflonk_input_path data/fflonk_scheduler_key.json --plonk_output_path ../l1-contracts/contracts/state-transition/verifiers/VerifierPlonk.sol --fflonk_output_path ../l1-contracts/contracts/state-transition/verifiers/VerifierFflonk.sol ``` diff --git a/tools/data/fflonk_scheduler_key.json b/tools/data/fflonk_scheduler_key.json index 2e449c652..4fc6c3c87 100644 --- a/tools/data/fflonk_scheduler_key.json +++ b/tools/data/fflonk_scheduler_key.json @@ -1,17 +1,17 @@ { - "n": 4095, + "n": 8388607, "c0": { "x": [ - 17767763437950677547, - 12573007107480222784, - 289878165523007019, - 1573623620240242191 + 3813618656849945112, + 10600654026134224728, + 16342402820248584790, + 132535993053272463 ], "y": [ - 14987375107961242666, - 17322044171909551213, - 8670314411921782092, - 2416181480258874015 + 6079912859676042266, + 17665833442058032331, + 79981484712334557, + 111664542011125665 ], "infinity": false },