diff --git a/l1-contracts/.gitignore b/l1-contracts/.gitignore index a12c2738449..df962e9cfd6 100644 --- a/l1-contracts/.gitignore +++ b/l1-contracts/.gitignore @@ -16,7 +16,6 @@ release-out/ .env /generated -/src/core/libraries/ConstantsGen.sol lcov.info # Local foundry env diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index f89302906ca..835637e0187 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -75,6 +75,7 @@ variable_override_spacing = false wrap_comments = true number_underscore = "thousands" override_spacing = false +ignore = ["script/deploy/HonkVerifier.sol"] [lint] ignore = ["./lib/**"] diff --git a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol new file mode 100644 index 00000000000..c5a1e7093f2 --- /dev/null +++ b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +// solhint-disable comprehensive-interface +pragma solidity >=0.8.27; + +import {Script} from "forge-std/Script.sol"; +import {StdAssertions} from "forge-std/StdAssertions.sol"; +import {console} from "forge-std/console.sol"; + +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; + +import {Rollup} from "@aztec/core/Rollup.sol"; +import {EscapeHatch} from "@aztec/core/EscapeHatch.sol"; +import {IEscapeHatch} from "@aztec/core/interfaces/IEscapeHatch.sol"; +import {IInstance} from "@aztec/core/interfaces/IInstance.sol"; +import {IVerifier} from "@aztec/core/interfaces/IVerifier.sol"; +import {GenesisState, RollupConfigInput} from "@aztec/core/interfaces/IRollup.sol"; +import {EthValue, EthPerFeeAssetE12} from "@aztec/core/libraries/rollup/FeeLib.sol"; +import {RewardConfig, Bps} from "@aztec/core/libraries/rollup/RewardLib.sol"; +import {IBoosterCore, RewardBoostConfig} from "@aztec/core/reward-boost/RewardBooster.sol"; +import {StakingQueueConfig} from "@aztec/core/libraries/compressed-data/StakingQueueConfig.sol"; +import {Inbox, INBOX_BUCKET_RING_SIZE} from "@aztec/core/messagebridge/Inbox.sol"; +import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; +import {Slasher} from "@aztec/core/slashing/Slasher.sol"; +import {SlashingProposer} from "@aztec/core/slashing/SlashingProposer.sol"; +import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol"; + +import {GSE} from "@aztec/governance/GSE.sol"; +import {Registry} from "@aztec/governance/Registry.sol"; +import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; + +import {HonkVerifier} from "./HonkVerifier.sol"; + +import {FlushRewarder} from "@aztec/periphery/FlushRewarder.sol"; +import {V6UpgradePayload} from "@aztec/periphery/V6UpgradePayload.sol"; + +import {V6UpgradeSimulation} from "./V6UpgradeSimulation.sol"; + +/** + * @title DeployRollupForUpgradeV6 + * @author Aztec Labs + * @notice Deploys the v6 rollup and the governance payload that makes it canonical. + * + * @dev Usage: + * REGISTRY_ADDRESS=0x... forge script script/deploy/DeployRollupForUpgradeV6.s.sol \ + * --rpc-url $L1_RPC_URL --private-key $KEY --broadcast + * Re-check an already deployed rollup without deploying anything: + * REGISTRY_ADDRESS=0x... forge script ... --sig 'verify(address)' + * + * `REGISTRY_ADDRESS` is the only environment input. Every configuration value is a literal in + * {_config} below, so reviewing this file is sufficient to review the deployment: there are no + * env-var defaults and no `network-defaults.json` fallbacks that could change what is + * deployed. {verify} reads each value back off the deployed contracts and asserts it matches + * the table, so a typo in the table or a drift in how the Rollup consumes it fails loudly. + * + * Deploy order matters: the rollup is constructed owned by the deployer, the EscapeHatch is + * deployed and registered while that is still true, and only then is ownership handed to + * governance. Anything owner-gated that is not done before that line can only be done by a + * governance proposal afterwards. + * + * Not covered here, deliberately: the protocol fee margin and recipient keep their + * constructor values (0 and a placeholder address). Setting them is a governance call, so it + * belongs in {V6UpgradePayload}. + */ +contract DeployRollupForUpgradeV6 is Script, StdAssertions { + /// @notice Thrown when run against a chain this script has no configuration for. + error DeployRollupForUpgradeV6__UnsupportedChain(uint256 chainId); + + uint256 internal constant MAINNET_CHAIN_ID = 1; + uint256 internal constant SEPOLIA_CHAIN_ID = 11_155_111; + + /// @notice Every value that determines what the v6 rollup is. One field per configurable knob. + struct Config { + // Genesis state of the protocol circuits. Produced by the v6 noir-projects build, not chosen. + // {run} refuses to deploy while any of them is zero, because the Rollup accepts zeros silently + // and a wrong genesis is only detectable after the fact. + bytes32 vkTreeRoot; + bytes32 protocolContractsHash; + bytes32 genesisArchiveRoot; + // Time. `ethereumSlotDuration` is the L1 slot length and is used to derive the inbox + // censorship cutoff; the rest define the Aztec slot/epoch clock. + uint256 ethereumSlotDuration; + uint256 aztecSlotDuration; + uint256 aztecEpochDuration; + uint256 aztecProofSubmissionEpochs; + // Committee selection. + uint256 targetCommitteeSize; + uint256 lagInEpochsForValidatorSet; + uint256 lagInEpochsForRandao; + // Staking. `localEjectionThreshold` is in staking-asset units and is the only one of these + // three the rollup sets. The other two are immutables of the existing GSE that this rollup + // inherits by being constructed against it: they are recorded here so {verify} fails if the + // GSE behind `REGISTRY_ADDRESS` is not the one these numbers were chosen against. Nothing in + // the contracts enforces a relationship between the three, so this file is the only place the + // choice of `localEjectionThreshold` can be checked against the thresholds it sits between. + uint256 localEjectionThreshold; + uint256 expectedGseActivationThreshold; + uint256 expectedGseEjectionThreshold; + uint256 exitDelaySeconds; + // Entry queue. Bootstrap values apply until the validator set reaches + // `bootstrapValidatorSetSize`; afterwards each flush admits + // max(normalFlushSizeMin, setSize / normalFlushSizeQuotient), capped by `maxFlushSize`. + uint256 entryQueueBootstrapValidatorSetSize; + uint256 entryQueueBootstrapFlushSize; + uint256 entryQueueNormalFlushSizeMin; + uint256 entryQueueNormalFlushSizeQuotient; + uint256 entryQueueMaxFlushSize; + // Fees. `initialEthPerFeeAsset` is an E12 fixed-point ETH-per-fee-asset price, a point-in-time + // market value; the mainnet literal records where and when it was read. + uint256 manaTarget; + uint256 provingCostPerMana; + uint256 initialEthPerFeeAsset; + // Rewards. `sequencerBps` is the sequencer's share of each checkpoint reward in basis points; + // the remainder goes to provers. A fresh RewardBooster is deployed with `rewardBoost`. + uint16 sequencerBps; + uint96 checkpointReward; + RewardBoostConfig rewardBoost; + // The initial ISequencerRewardCalculator, or zero for none. Per-registry reward policy is no + // longer built into the rollup (AZIP-31): the rollup instead calls a governance-set calculator + // once per epoch proof. Zero means every proposer receives the default sequencer share, which + // is also what the rollup falls back to if a configured calculator reverts, runs out of its + // stipend, or answers in the wrong shape -- so a wrong value here fails silently, not loudly. + // Governance can install or replace the calculator later via `setSequencerRewardCalculator`, + // so this does not have to be final at deploy. + address sequencerRewardCalculator; + // Slashing. `slashingRoundSizeInEpochs` is multiplied by `aztecEpochDuration` to get the round + // size in slots, which is what the SlashingProposer actually stores. + bool slasherEnabled; + uint256 slashingRoundSizeInEpochs; + uint256 slashingQuorum; + uint256 slashingLifetimeInRounds; + uint256 slashingExecutionDelayInRounds; + uint256 slashingOffsetInRounds; + uint256 slashingDisableDuration; + address slashingVetoer; + uint256 slashAmountSmall; + uint256 slashAmountMedium; + uint256 slashAmountLarge; + // Escape hatch. The bond is denominated in the staking asset. The EscapeHatch constructor + // rejects a configuration that breaks any of these, so they are constraints, not preferences: + // `escapeHatchActiveDuration` >= `aztecProofSubmissionEpochs` + 1, `escapeHatchFrequency` > + // both `EscapeHatch.LAG_IN_EPOCHS_FOR_SET_SIZE` (a constant 2) and `escapeHatchActiveDuration`, + // `escapeHatchLagInHatches` >= 1, withdrawal tax and failed-hatch punishment <= bond size, and + // `escapeHatchProposingExitDelay` <= 30 days. + uint96 escapeHatchBondSize; + uint96 escapeHatchWithdrawalTax; + uint96 escapeHatchFailedHatchPunishment; + uint256 escapeHatchFrequency; + uint256 escapeHatchActiveDuration; + uint256 escapeHatchLagInHatches; + uint256 escapeHatchProposingExitDelay; + // Restricts governance execution of the payload to UK office hours (Mon-Fri, 08:00-17:00 + // London, DST-aware). Enforced as the payload's first action, so a rejected attempt reverts + // the whole execution and leaves the proposal executable again when the window next opens. + bool enforcePayloadExecutionWindow; + // The proof-of-possession gas cap the payload sets on the shared GSE, which applies to every + // rollup it serves, the outgoing one included. Zero omits the action and leaves the cap alone; + // otherwise the payload constructor rejects anything not above the GSE's cap at deployment. + uint64 proofOfPossessionGasLimit; + // Reward-pool balance the payload reserves for the OUTGOING rollup before it stops being + // canonical. Zero omits the reservation. Whatever is reserved is subtracted from what the new + // rollup can claim, and the pool is live, so this must be set against the balance at execution + // time rather than a figure from deploy day. The distributor and the reward asset are NOT + // configured: the payload reads both off the registry and the rollup, which is what makes this + // work unchanged on a chain whose distributor and fee asset differ. + uint256 earmarkAmountForPredecessor; + // Rewrites the OUTGOING rollup's reward split as part of the upgrade. Nothing here touches the + // new rollup, which keeps `sequencerBps` and `checkpointReward` from this same table. Gated by + // a flag because both 0 and 10000 bps are real splits, so no value can mean "leave it alone". + bool retunePredecessorRewards; + uint16 predecessorSequencerBps; + uint96 predecessorCheckpointReward; + // The flush rewarder serving the rollup being replaced, or zero on a chain that has none. + // A FlushRewarder is immutably bound to one rollup, so v6 needs its own; the payload deploys + // the replacement and moves the outgoing one's unowed balance across. + address oldFlushRewarder; + } + + /** + * @notice The configuration this script deploys. This is the table to review. + * @dev The literal below is the mainnet configuration; Sepolia is expressed as a short list of + * overrides underneath it, so the only thing a reviewer has to check for Sepolia is that + * list. A chain with neither branch reverts rather than silently deploying mainnet values. + * Values track v5 production unless a comment says otherwise; each line records the v5 + * value so any intentional v6 divergence is visible as a divergence. + */ + /// @dev `virtual` so a test can zero the genesis roots and exercise `run()`'s refusal to deploy + /// without them. Nothing else about it is overridable, and nothing in the deploy path + /// overrides it. + function _config() internal view virtual returns (Config memory c) { + c = Config({ + // Read off the v6 release build at 42fae4eb3feb5a493f2e8a2baf78b4716e323c40, the same build + // the pinned ./HonkVerifier.sol and ConstantsGen.sol come from; all of them move together. + // `run()` only guards against zero, so a root from a different build deploys silently. See + // "Fill in the inputs" in V6_UPGRADE_RUNBOOK.md. + // Source: `getVKTreeRoot()` and `protocolContractsHash` from the built packages, + // `GENESIS_ARCHIVE_ROOT` from the protocol constants. + vkTreeRoot: 0x22fff5de6ce590153df4468f7f0b152188a803d91b3c9c648b91d93d5913f219, + protocolContractsHash: 0x0f54271c52865841a77aaa66036eed901aff22fdce9f08e123ec8b205c9854d7, + genesisArchiveRoot: 0x29eb2c527f8d45276430363214e6c8d709ef3f657a3670ebac3179373d41e5c4, + ethereumSlotDuration: 12, // L1 slot time; no v5 equivalent, the v5 inbox took an explicit lag instead + aztecSlotDuration: 72, + aztecEpochDuration: 32, + aztecProofSubmissionEpochs: 1, + targetCommitteeSize: 48, + lagInEpochsForValidatorSet: 2, + lagInEpochsForRandao: 1, + localEjectionThreshold: 190_000e18, // v5 production: 190_000e18 (mainnet), 199_000e18 (sepolia) + expectedGseActivationThreshold: 200_000e18, // not set here; asserted against the existing GSE. v5 production: + // 200_000e18 + expectedGseEjectionThreshold: 100_000e18, // not set here; asserted against the existing GSE. v5 production: + // 100_000e18 + exitDelaySeconds: 345_600, // 4 days. v5 production: 345_600 (mainnet), 172_800 (sepolia) + entryQueueBootstrapValidatorSetSize: 500, // v5 production: 500. + entryQueueBootstrapFlushSize: 4, // v5 production: 4 + entryQueueNormalFlushSizeMin: 1, // v5 production: 1 + entryQueueNormalFlushSizeQuotient: 400, // v5 production: 400 + entryQueueMaxFlushSize: 4, // v5 production: 4 + manaTarget: 75_000_000, // v5 production: 75_000_000 + provingCostPerMana: 12_500_000, // v5 production: 12_500_000 (set by AZIP-16) + // AZTEC/ETH Uniswap v4 pool 0xce2899b16743cfd5a954d8122d5e07f410305b1aebee39fd73d9f3b9ebf10c2f + // at mainnet block 26148995 (2026-10-08): 0.0000059342 ETH per AZTEC. v5 production: 9_512_195. + initialEthPerFeeAsset: 5_934_240, + sequencerBps: 9000, // 90% sequencer / 10% prover. v5 production: 7000 + checkpointReward: 500e18, // same as v5 + rewardBoost: RewardBoostConfig({ + increment: 101_400, maxScore: 367_500, a: 250_000, minimum: 10_000, k: 1_000_000 + }), // AZIP-5; same as v5 + sequencerRewardCalculator: address(0), + slasherEnabled: true, + slashingRoundSizeInEpochs: 4, + slashingQuorum: 65, // of the 128-slot round (4 epochs x 32 slots) + slashingLifetimeInRounds: 34, // v5 production: 34 (mainnet), 5 (sepolia) + slashingExecutionDelayInRounds: 28, // v5 production: 28 (mainnet), 2 (sepolia) + slashingOffsetInRounds: 2, + slashingDisableDuration: 259_200, // 3 days. v5 production: 259_200 (mainnet), 432_000 (sepolia) + slashingVetoer: 0xBbB4aF368d02827945748b28CD4b2D42e4A37480, // v5 production (mainnet) + slashAmountSmall: 2000e18, // v5 production: 2000e18 (mainnet), 100_000e18 (sepolia) + slashAmountMedium: 5000e18, // v5 production: 5000e18 (mainnet), 250_000e18 (sepolia) + slashAmountLarge: 5000e18, // v5 production: 5000e18 (mainnet), 250_000e18 (sepolia) + escapeHatchBondSize: 332_000_000e18, // v5 production + escapeHatchWithdrawalTax: 1_660_000e18, // v5 production + escapeHatchFailedHatchPunishment: 9_600_000e18, // v5 production + escapeHatchFrequency: 112, // epochs between hatches. v5 production + escapeHatchActiveDuration: 2, // epochs. v5 production; also the minimum allowed here, being + // aztecProofSubmissionEpochs + 1 + escapeHatchLagInHatches: 1, // v5 production + escapeHatchProposingExitDelay: 30 days, // v5 production; also the maximum the constructor allows + enforcePayloadExecutionWindow: true, + // Up from the GSE's 250_000, which honest keys whose hashToPoint loop runs long exceed under + // Osaka modexp pricing (EIP-7883): about 1 in 28.6k keys at 250k, about 1 in 2.5M at 300k. A + // deposit that fails verification can burn at most 50k more gas in a flush as a result. + proofOfPossessionGasLimit: 300_000, + earmarkAmountForPredecessor: 1_800_000e18, + retunePredecessorRewards: true, + predecessorSequencerBps: 7000, // v5 keeps the 7000 it already runs; only the reward below moves + predecessorCheckpointReward: 50e18, // down from the 500e18 v5 runs on, and the 500e18 v6 starts on + oldFlushRewarder: 0x5B98cA4dcE7b59CCf241D12f81d3d2eCF14e410e // bound to the v5 rollup + }); + + if (block.chainid == MAINNET_CHAIN_ID) { + return c; + } + + if (block.chainid == SEPOLIA_CHAIN_ID) { + // Every field Sepolia diverges on, and nothing else. Values are v5's Sepolia production + // values; + c.localEjectionThreshold = 199_000e18; + c.exitDelaySeconds = 172_800; // 2 days + c.slashingLifetimeInRounds = 5; + c.slashingExecutionDelayInRounds = 2; + c.slashingDisableDuration = 432_000; // 5 days + c.slashingVetoer = 0xdfe19Da6a717b7088621d8bBB66be59F2d78e924; + c.slashAmountSmall = 100_000e18; + c.slashAmountMedium = 250_000e18; + c.slashAmountLarge = 250_000e18; + // Testnet upgrades are executed on demand, so the office-hours restriction is mainnet only. + c.enforcePayloadExecutionWindow = false; + // The Sepolia distributor was funded with ~100.98M FEE, all of it un-earmarked, so this + // draws from the implicit pool with room to spare. + c.earmarkAmountForPredecessor = 5_000_000e18; + // Set here rather than inherited from the mainnet market price: testnet fees are not priced + // against a real market, and this value only sets the starting point the fee oracle moves from. + c.initialEthPerFeeAsset = 10_000_000; + c.retunePredecessorRewards = true; + c.predecessorSequencerBps = 8000; // 80% sequencer / 20% prover, down from the 9000 v6 runs on + c.predecessorCheckpointReward = 100e18; // down from the 500e18 v6 runs on + // Sepolia has no flush rewarder to migrate, so the payload skips that action entirely. + // Leaving the mainnet address here would make the payload constructor revert, since it + // reads the outgoing rewarder's asset and rate. + c.oldFlushRewarder = address(0); + // No sequencer reward calculator on Sepolia either, matching mainnet. + c.sequencerRewardCalculator = address(0); + // Admit 12 validators per epoch rather than mainnet's 4, so testnet's queue drains at a + // rate that suits testing. All three move together: the bootstrap phase returns + // `bootstrapFlushSize` directly, the normal phase floors at `normalFlushSizeMin`, and + // `maxFlushSize` caps both, so raising fewer than three leaves the old value binding. + // `normalFlushSizeQuotient` stays at 400: `setSize / 400` only passes 12 above 4800 + // validators, and the cap holds it at 12 there anyway. + c.entryQueueBootstrapFlushSize = 12; // v5 Sepolia production: 4 + c.entryQueueNormalFlushSizeMin = 12; // v5 Sepolia production: 1 + c.entryQueueMaxFlushSize = 12; // v5 Sepolia production: 4 + return c; + } + + revert DeployRollupForUpgradeV6__UnsupportedChain(block.chainid); + } + + /// @notice The rollup {run} deployed. Zero until it has run. + /// @dev Mirrors `rollupOutput()` on DeployRollupForUpgrade: the addresses are logged for an + /// operator, and exposed here for anything that has to read them back -- a test, or tooling + /// that follows the deploy. + Rollup public deployedRollup; + + /// @notice The payload {run} deployed. Zero until it has run. + V6UpgradePayload public deployedPayload; + + /// @notice Deploys the verifier, the rollup, and the governance payload, then verifies the result. + function run() public { + Config memory c = _config(); + + // The three genesis roots are the only values the Rollup accepts silently when wrong, so they + // are gated here rather than trusted. + require(c.vkTreeRoot != bytes32(0), "vkTreeRoot not set"); + require(c.protocolContractsHash != bytes32(0), "protocolContractsHash not set"); + require(c.genesisArchiveRoot != bytes32(0), "genesisArchiveRoot not set"); + + Registry registry = Registry(vm.envAddress("REGISTRY_ADDRESS")); + // Everything the new rollup must share with the old one is read off the chain rather than + // configured, so the new rollup cannot be pointed at the wrong asset or GSE by a typo. + IInstance current = IInstance(address(registry.getCanonicalRollup())); + address governance = registry.getGovernance(); + + // The account `--private-key` / `--sender` resolves to. Every contract below is deployed by it. + address deployer = msg.sender; + + vm.startBroadcast(deployer); + + // Always the real verifier: this script exists for upgrades of live networks. + IVerifier verifier = IVerifier(address(new HonkVerifier())); + + // The 5th argument is GOVERNANCE, and it is load-bearing twice: it becomes the Rollup's + // `Ownable` owner AND the Slasher's immutable `GOVERNANCE`, which may execute any slash + // payload with no vote, no round and no delay (Slasher.sol:58). + // + // So it MUST be governance, not the deployer. `transferOwnership` moves only the Ownable + // half; the Slasher's copy is immutable and would leave the deploy key able to slash every + // validator for the life of the rollup. Recovery would take 60 days to queue a replacement + // Slasher plus a 30-day window in which the old one still works. + // + // The cost is that no owner-gated setup can happen here. `setEscapeHatch` therefore moved + // into the payload, where governance performs it. + Rollup rollup = new Rollup( + current.getFeeAsset(), + current.getStakingAsset(), + current.getGSE(), + verifier, + governance, + _genesisState(c), + _rollupConfigInput(c, registry.getRewardDistributor()) + ); + + // Deployed after the rollup because its constructor reads `getProofSubmissionEpochs()` off it + // to check `escapeHatchActiveDuration`, and because it must be built for this exact rollup. + EscapeHatch escapeHatch = new EscapeHatch( + address(rollup), + address(current.getStakingAsset()), + c.escapeHatchBondSize, + c.escapeHatchWithdrawalTax, + c.escapeHatchFailedHatchPunishment, + c.escapeHatchFrequency, + c.escapeHatchActiveDuration, + c.escapeHatchLagInHatches, + c.escapeHatchProposingExitDelay + ); + + // The rollup has been owned by governance since construction, so nothing owner-gated happens + // here and there is no ownership to transfer. The hatch is installed by the payload. + // Constructed in a helper purely to keep `run` inside the stack limit. + V6UpgradePayload payload = _deployPayload(c, registry, rollup, escapeHatch); + + vm.stopBroadcast(); + + verify(address(rollup)); + + console.log("rollup ", address(rollup)); + console.log("verifier ", address(verifier)); + console.log("inbox ", address(rollup.getInbox())); + console.log("outbox ", address(rollup.getOutbox())); + console.log("feeJuicePortal ", address(rollup.getFeeAssetPortal())); + console.log("slasher ", rollup.getSlasher()); + console.log("escapeHatch ", address(escapeHatch), "(installed by the payload)"); + console.log("rewardBooster ", address(rollup.getRewardConfig().booster)); + console.log("payload ", address(payload)); + console.log("newFlushRewarder ", address(payload.NEW_FLUSH_REWARDER())); + verifyEscapeHatch(address(rollup), address(payload)); + verifyFlushRewarder(address(rollup), address(payload)); + + _simulate(address(payload)); + console.log("version ", rollup.getVersion()); + + deployedRollup = rollup; + deployedPayload = payload; + } + + /** + * @notice Runs the payload through the real governance lifecycle against a state snapshot and + * reverts it, so a deploy cannot succeed while producing a payload that would fail. + * @dev Requires FORKED state -- it needs a governance with real voters and mainnet timings; see + * V6UpgradeSimulation. `virtual` so a test exercising the config table against a local stack + * can skip it, which is the only thing in `run()` that a local stack cannot satisfy. + */ + function _simulate(address _payload) internal virtual { + new V6UpgradeSimulation().simulate(_payload); + } + + /// @notice Asserts every configurable value of an already-deployed rollup against {_config}. + function verify(address _rollup) public view { + Config memory c = _config(); + Rollup rollup = Rollup(_rollup); + Registry registry = Registry(vm.envAddress("REGISTRY_ADDRESS")); + + _verifyGenesisAndTime(rollup, c); + _verifyStakingAndFees(rollup, c); + _verifyRewards(rollup, c); + _verifySlashing(rollup, c, registry.getGovernance()); + _verifySubContracts(rollup, registry); + } + + /// @notice Asserts the payload's replacement flush rewarder is wired to this rollup. + /// @dev Separate from {verify} because it needs the payload address, which {verify} is not given. + function verifyFlushRewarder(address _rollup, address _payload) public view { + Config memory c = _config(); + FlushRewarder newRewarder = V6UpgradePayload(_payload).NEW_FLUSH_REWARDER(); + + assertEq( + V6UpgradePayload(_payload).ENFORCE_EXECUTION_WINDOW(), + c.enforcePayloadExecutionWindow, + "payload execution window flag" + ); + + // The earmark wiring: the amount is configured, the other three are derived, so asserting them + // is what proves the payload is pointed at this chain's distributor rather than another's. + V6UpgradePayload p = V6UpgradePayload(_payload); + Registry registry = Registry(vm.envAddress("REGISTRY_ADDRESS")); + assertEq(p.EARMARK_AMOUNT(), c.earmarkAmountForPredecessor, "earmark amount"); + assertEq(p.PREDECESSOR(), address(registry.getCanonicalRollup()), "earmark recipient is not the outgoing rollup"); + assertEq(p.RETUNE_PREDECESSOR_REWARDS(), c.retunePredecessorRewards, "retune flag"); + assertEq(p.PREDECESSOR_SEQUENCER_BPS(), c.predecessorSequencerBps, "predecessor sequencerBps"); + assertEq(p.PREDECESSOR_CHECKPOINT_REWARD(), c.predecessorCheckpointReward, "predecessor checkpointReward"); + assertEq(p.PROOF_OF_POSSESSION_GAS_LIMIT(), c.proofOfPossessionGasLimit, "proofOfPossessionGasLimit"); + + if (c.earmarkAmountForPredecessor > 0) { + assertEq(address(p.REWARD_DISTRIBUTOR()), address(registry.getRewardDistributor()), "earmark distributor"); + assertEq(address(p.REWARD_ASSET()), address(Rollup(_rollup).getFeeAsset()), "earmark reward asset"); + } + + if (c.oldFlushRewarder == address(0)) { + assertEq(address(newRewarder), address(0), "flush rewarder deployed on a chain with none to migrate"); + return; + } + + FlushRewarder oldRewarder = FlushRewarder(c.oldFlushRewarder); + assertEq(address(newRewarder.ROLLUP()), _rollup, "new flush rewarder bound to another rollup"); + assertEq(address(newRewarder.REWARD_ASSET()), address(oldRewarder.REWARD_ASSET()), "flush reward asset"); + assertEq(newRewarder.rewardPerInsertion(), oldRewarder.rewardPerInsertion(), "rewardPerInsertion"); + // Governance must own it, or the reward rate can never be changed again. + assertEq(newRewarder.owner(), Registry(vm.envAddress("REGISTRY_ADDRESS")).getGovernance(), "flush rewarder owner"); + } + + /** + * @notice Asserts the payload's escape hatch is built for this rollup. + * @dev Separate from {verify} for the same reason as {verifyFlushRewarder}: the hatch is + * installed BY the payload, so the rollup does not point at it until governance executes. + * Installation itself is asserted post-execution by V6UpgradeSimulation. + */ + function verifyEscapeHatch(address _rollup, address _payload) public view { + _verifyEscapeHatch(EscapeHatch(V6UpgradePayload(_payload).ESCAPE_HATCH()), Rollup(_rollup), _config()); + } + + function _verifyEscapeHatch(EscapeHatch hatch, Rollup _rollup, Config memory _c) private view { + assertTrue(address(hatch) != address(0), "payload has no escape hatch"); + assertEq(hatch.getRollup(), address(_rollup), "escape hatch points at another rollup"); + assertEq(hatch.getBondToken(), address(_rollup.getStakingAsset()), "escape hatch bond token"); + assertEq(hatch.getBondSize(), _c.escapeHatchBondSize, "escapeHatchBondSize"); + assertEq(hatch.getWithdrawalTax(), _c.escapeHatchWithdrawalTax, "escapeHatchWithdrawalTax"); + assertEq(hatch.getFailedHatchPunishment(), _c.escapeHatchFailedHatchPunishment, "escapeHatchFailedHatchPunishment"); + assertEq(hatch.getFrequency(), _c.escapeHatchFrequency, "escapeHatchFrequency"); + assertEq(hatch.getActiveDuration(), _c.escapeHatchActiveDuration, "escapeHatchActiveDuration"); + assertEq(hatch.getLagInHatches(), _c.escapeHatchLagInHatches, "escapeHatchLagInHatches"); + assertEq(hatch.getProposingExitDelay(), _c.escapeHatchProposingExitDelay, "escapeHatchProposingExitDelay"); + } + + function _verifyGenesisAndTime(Rollup _rollup, Config memory _c) private view { + assertEq(_rollup.getVkTreeRoot(), _c.vkTreeRoot, "vkTreeRoot mismatch"); + assertEq(_rollup.getProtocolContractsHash(), _c.protocolContractsHash, "protocolContractsHash"); + // The genesis archive root is not exposed by a named getter; it is the archive of checkpoint 0. + assertEq(_rollup.archiveAt(0), _c.genesisArchiveRoot, "genesisArchiveRoot mismatch"); + assertEq(_rollup.getSlotDuration(), _c.aztecSlotDuration, "aztecSlotDuration mismatch"); + assertEq(_rollup.getEpochDuration(), _c.aztecEpochDuration, "aztecEpochDuration mismatch"); + assertEq(_rollup.getProofSubmissionEpochs(), _c.aztecProofSubmissionEpochs, "aztecProofSubmissionEpochs mismatch"); + assertEq(_rollup.getTargetCommitteeSize(), _c.targetCommitteeSize, "targetCommitteeSize mismatch"); + assertEq( + _rollup.getLagInEpochsForValidatorSet(), _c.lagInEpochsForValidatorSet, "lagInEpochsForValidatorSet mismatch" + ); + assertEq(_rollup.getLagInEpochsForRandao(), _c.lagInEpochsForRandao, "lagInEpochsForRandao"); + } + + function _verifyStakingAndFees(Rollup _rollup, Config memory _c) private view { + assertEq(_rollup.getLocalEjectionThreshold(), _c.localEjectionThreshold, "localEjectionThreshold mismatch"); + // These two read straight off the GSE's immutables, so they are readable as soon as the rollup + // is constructed and do not depend on the GSE or Registry having registered it. + assertEq(_rollup.getActivationThreshold(), _c.expectedGseActivationThreshold, "gse activationThreshold mismatch"); + assertEq(_rollup.getEjectionThreshold(), _c.expectedGseEjectionThreshold, "gse ejectionThreshold mismatch"); + assertEq(Timestamp.unwrap(_rollup.getExitDelay()), _c.exitDelaySeconds, "exitDelaySeconds mismatch"); + assertEq(_rollup.getManaTarget(), _c.manaTarget, "manaTarget mismatch"); + assertEq( + EthValue.unwrap(_rollup.getProvingCostPerManaInEth()), _c.provingCostPerMana, "provingCostPerMana mismatch" + ); + assertEq(EthPerFeeAssetE12.unwrap(_rollup.getEthPerFeeAsset()), _c.initialEthPerFeeAsset, "initialEthPerFeeAsset"); + + // The protocol fee margin and recipient are not deploy-time config; these assertions record + // that the rollup starts with the constructor's values and still needs a governance call. + assertEq(_rollup.getProtocolFeeMargin(), 0, "protocol fee margin should start at 0"); + assertEq( + _rollup.getProtocolFeeRecipient(), + address(bytes20("CUAUHXICALLI")), + "protocol fee recipient should still be the constructor placeholder" + ); + } + + function _verifyRewards(Rollup _rollup, Config memory _c) private view { + RewardConfig memory rc = _rollup.getRewardConfig(); + assertEq(Bps.unwrap(rc.sequencerBps), _c.sequencerBps, "sequencerBps"); + assertEq(rc.checkpointReward, _c.checkpointReward, "checkpointReward"); + // A booster address of zero in the config makes the Rollup deploy a fresh one, so a non-zero + // address here confirms that happened. + assertTrue(address(rc.booster) != address(0), "reward booster not deployed"); + + // Unlike the rest of this config, the calculator is mutable after deployment, so this asserts + // what the rollup launches with rather than something fixed at construction. Zero is the + // intended launch value; a non-zero address here would mean a calculator was wired in without + // the config saying so. + assertEq(_rollup.getSequencerRewardCalculator(), _c.sequencerRewardCalculator, "sequencerRewardCalculator"); + } + + function _verifySlashing(Rollup _rollup, Config memory _c, address _governance) private view { + // With slashing disabled the Rollup deploys no slasher at all, so there is nothing else to read. + if (!_c.slasherEnabled) { + assertEq(_rollup.getSlasher(), address(0), "slasher deployed despite slasherEnabled = false"); + return; + } + + Slasher slasher = Slasher(_rollup.getSlasher()); + // The one that matters most and is easiest to get wrong: GOVERNANCE is immutable and is taken + // from the Rollup constructor's owner argument, so a rollup constructed by the deployer hands + // the deploy key unilateral, permanent slashing power that no later transfer can revoke. + assertEq(slasher.GOVERNANCE(), _governance, "slasher GOVERNANCE is not governance"); + assertEq(slasher.VETOER(), _c.slashingVetoer, "slashingVetoer"); + assertEq(slasher.SLASHING_DISABLE_DURATION(), _c.slashingDisableDuration, "slashingDisableDuration"); + + SlashingProposer proposer = SlashingProposer(slasher.PROPOSER()); + assertEq(proposer.INSTANCE(), address(_rollup), "slashing proposer points at another rollup"); + assertEq(proposer.QUORUM(), _c.slashingQuorum, "slashingQuorum"); + assertEq(proposer.ROUND_SIZE_IN_EPOCHS(), _c.slashingRoundSizeInEpochs, "slashingRoundSizeInEpochs"); + assertEq(proposer.ROUND_SIZE(), _c.slashingRoundSizeInEpochs * _c.aztecEpochDuration, "slashingRoundSize"); + assertEq(proposer.LIFETIME_IN_ROUNDS(), _c.slashingLifetimeInRounds, "slashingLifetimeInRounds"); + assertEq(proposer.EXECUTION_DELAY_IN_ROUNDS(), _c.slashingExecutionDelayInRounds, "slashingExecutionDelayInRounds"); + assertEq(proposer.SLASH_OFFSET_IN_ROUNDS(), _c.slashingOffsetInRounds, "slashingOffsetInRounds"); + assertEq(proposer.SLASH_AMOUNT_SMALL(), _c.slashAmountSmall, "slashAmountSmall"); + assertEq(proposer.SLASH_AMOUNT_MEDIUM(), _c.slashAmountMedium, "slashAmountMedium"); + assertEq(proposer.SLASH_AMOUNT_LARGE(), _c.slashAmountLarge, "slashAmountLarge"); + } + + function _verifySubContracts(Rollup _rollup, Registry _registry) private view { + IInstance current = IInstance(address(_registry.getCanonicalRollup())); + uint256 version = _rollup.getVersion(); + + // Inherited from the chain, not configured: assert they match the rollup being replaced. + assertEq(address(_rollup.getFeeAsset()), address(current.getFeeAsset()), "feeAsset"); + assertEq(address(_rollup.getStakingAsset()), address(current.getStakingAsset()), "stakingAsset"); + assertEq(address(_rollup.getGSE()), address(current.getGSE()), "gse"); + assertEq(address(_rollup.getRewardDistributor()), address(_registry.getRewardDistributor()), "rewardDistributor"); + + // The Inbox and Outbox are deployed by the Rollup's constructor and are only correct if they + // point back at it and carry its version, which is what scopes L1<>L2 messages to this rollup. + Inbox inbox = Inbox(address(_rollup.getInbox())); + assertEq(inbox.ROLLUP(), address(_rollup), "inbox rollup"); + assertEq(inbox.VERSION(), version, "inbox version"); + assertEq(inbox.BUCKET_RING_SIZE(), INBOX_BUCKET_RING_SIZE, "inbox bucket ring size"); + assertEq(inbox.FEE_ASSET_PORTAL(), address(_rollup.getFeeAssetPortal()), "fee juice portal"); + + Outbox outbox = Outbox(address(_rollup.getOutbox())); + assertEq(address(outbox.ROLLUP()), address(_rollup), "outbox rollup"); + assertEq(outbox.VERSION(), version, "outbox version"); + + // The deploy is only safe to hand to governance if governance actually controls it. + assertEq(_rollup.owner(), _registry.getGovernance(), "rollup owner should be governance"); + } + + /// @dev Split out of {run} only so that function stays within the EVM stack limit. + function _deployPayload(Config memory _c, Registry _registry, Rollup _rollup, EscapeHatch _escapeHatch) + private + returns (V6UpgradePayload) + { + return new V6UpgradePayload( + _registry, + IInstance(address(_rollup)), + IEscapeHatch(address(_escapeHatch)), + FlushRewarder(_c.oldFlushRewarder), + _c.enforcePayloadExecutionWindow, + _c.earmarkAmountForPredecessor, + _c.retunePredecessorRewards, + _c.predecessorSequencerBps, + _c.predecessorCheckpointReward, + _c.proofOfPossessionGasLimit + ); + } + + function _genesisState(Config memory _c) private pure returns (GenesisState memory) { + return GenesisState({ + vkTreeRoot: _c.vkTreeRoot, + protocolContractsHash: _c.protocolContractsHash, + genesisArchiveRoot: _c.genesisArchiveRoot + }); + } + + function _rollupConfigInput(Config memory _c, IRewardDistributor _rewardDistributor) + private + pure + returns (RollupConfigInput memory config) + { + config.ethereumSlotDuration = _c.ethereumSlotDuration; + config.aztecSlotDuration = _c.aztecSlotDuration; + config.aztecEpochDuration = _c.aztecEpochDuration; + config.aztecProofSubmissionEpochs = _c.aztecProofSubmissionEpochs; + config.targetCommitteeSize = _c.targetCommitteeSize; + config.lagInEpochsForValidatorSet = _c.lagInEpochsForValidatorSet; + config.lagInEpochsForRandao = _c.lagInEpochsForRandao; + config.localEjectionThreshold = _c.localEjectionThreshold; + config.exitDelaySeconds = _c.exitDelaySeconds; + config.manaTarget = _c.manaTarget; + config.provingCostPerMana = EthValue.wrap(_c.provingCostPerMana); + config.initialEthPerFeeAsset = EthPerFeeAssetE12.wrap(_c.initialEthPerFeeAsset); + + config.slasherEnabled = _c.slasherEnabled; + config.slashingVetoer = _c.slashingVetoer; + config.slashingDisableDuration = _c.slashingDisableDuration; + config.slashingQuorum = _c.slashingQuorum; + // The Rollup stores the round size in slots; epochs are the reviewable unit. + config.slashingRoundSize = _c.slashingRoundSizeInEpochs * _c.aztecEpochDuration; + config.slashingLifetimeInRounds = _c.slashingLifetimeInRounds; + config.slashingExecutionDelayInRounds = _c.slashingExecutionDelayInRounds; + config.slashingOffsetInRounds = _c.slashingOffsetInRounds; + config.slashAmounts = [_c.slashAmountSmall, _c.slashAmountMedium, _c.slashAmountLarge]; + + config.stakingQueueConfig = StakingQueueConfig({ + bootstrapValidatorSetSize: _c.entryQueueBootstrapValidatorSetSize, + bootstrapFlushSize: _c.entryQueueBootstrapFlushSize, + normalFlushSizeMin: _c.entryQueueNormalFlushSizeMin, + normalFlushSizeQuotient: _c.entryQueueNormalFlushSizeQuotient, + maxQueueFlushSize: _c.entryQueueMaxFlushSize + }); + + config.rewardConfig = RewardConfig({ + rewardDistributor: _rewardDistributor, + sequencerBps: Bps.wrap(_c.sequencerBps), + // Zero makes the Rollup's constructor deploy a fresh RewardBooster from `rewardBoostConfig`. + booster: IBoosterCore(address(0)), + checkpointReward: _c.checkpointReward + }); + config.rewardBoostConfig = _c.rewardBoost; + + // Assigned explicitly even though the config's zero value matches the struct's: this field is + // the last one on `RollupConfigInput`, and leaving it out would read as an oversight rather + // than the decision it is. + config.sequencerRewardCalculator = _c.sequencerRewardCalculator; + + // The version identifies this rollup in the Registry and scopes its Inbox/Outbox messages. It + // is derived from the configuration so that two rollups with different configuration cannot + // collide. `config.version` is still zero at this point (memory structs start zeroed and + // nothing above assigns it), which is what `RollupConfiguration._computeConfigVersion` also + // hashes over, so the two agree. + config.version = uint32(bytes4(keccak256(abi.encode(config, _genesisState(_c))))); + } +} diff --git a/l1-contracts/script/deploy/HonkVerifier.sol b/l1-contracts/script/deploy/HonkVerifier.sol new file mode 100644 index 00000000000..54f5c4aa1e3 --- /dev/null +++ b/l1-contracts/script/deploy/HonkVerifier.sol @@ -0,0 +1,5723 @@ + +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Aztec +pragma solidity ^0.8.27; + +// Note: copied from v6@42fae4eb3feb5a493f2e8a2baf78b4716e323c40 + +interface IVerifier { + function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool); +} + + + +uint256 constant NUMBER_OF_SUBRELATIONS = 31; +uint256 constant BATCHED_RELATION_PARTIAL_LENGTH = 8; +uint256 constant ZK_BATCHED_RELATION_PARTIAL_LENGTH = 9; +uint256 constant NUMBER_OF_ENTITIES = 41; +uint256 constant NUMBER_UNSHIFTED = 36; +uint256 constant NUMBER_TO_BE_SHIFTED = 5; +uint256 constant PAIRING_POINTS_SIZE = 8; + +uint256 constant VK_HASH = 0x1fd4eb1d14be45e05dc78c448eb4f29d7f63e4de95129c4a5e1058cd9837f8a6; +uint256 constant CIRCUIT_SIZE = 16777216; +uint256 constant LOG_N = 24; +uint256 constant NUMBER_PUBLIC_INPUTS = 121; +uint256 constant REAL_NUMBER_PUBLIC_INPUTS = 113; +uint256 constant PUBLIC_INPUTS_OFFSET = 5; // NUM_DISABLED_ROWS_IN_SUMCHECK + NUM_ZERO_ROWS = 4 + 1 + +contract HonkVerifier is IVerifier { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SLAB ALLOCATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + /** + * We manually manage memory within this optimised implementation. + * Memory is loaded into a large slab with the following layout: + * + * HIGH MEMORY (persistent, non-overlapping regions from 0x1000 upward): + * + * VK Data (circuit size, num PIs, offset, 28 G1 commitment points) + * Proof: Pairing point limbs (8 field elements) + * Proof: Witness commitments (W_L..Z_PERM, 8 G1 points) + * Proof: Sumcheck univariates (LOG_N rounds x 8 coefficients) + * Proof: Sumcheck evaluations (41 entity evaluations) + * Proof: Gemini fold commitments (LOG_N-1 G1 points) + * Proof: Gemini A evaluations (LOG_N field elements) + * Proof: Shplonk Q + KZG quotient (2 G1 points) + * Challenges (eta..sum_u, alpha[0..29], gate + sum_u challenges) + * Subrelation evaluations (31 slots, used during sumcheck) + * Subrelation intermediates (7 slots: round target, pow, AUX) + * Powers of evaluation challenge (LOG_N slots) + * Batch scalars (69 slots, for MSM) + * Gemini R inverse (1 slot) + * Inverted Gemini denominators (LOG_N+1 = 16 slots) + * Batch evaluation accumulator inversions (LOG_N slots) + * Batched eval, constant term accumulator, pos/neg inv denom + * Inverted challenge^pow - u (LOG_N slots) + * Pos inverted denominators (LOG_N slots) + * Neg inverted denominators (LOG_N slots) + * Fold pos evaluations (LOG_N slots) + * LATER_SCRATCH_SPACE (batch inversion products marker) + * Temporary space (45 slots, ephemeral computation) + * + * LOW MEMORY / SCRATCH SPACE (barycentric evaluation during sumcheck): + * Barycentric Lagrange denominators (8 domain points) + * Barycentric denominator inverses (LOG_N x 8 slots) + * [Slots at 0x1000-0x10E0 overlap VK data; VK is re-loaded later] + * + * Scratch aliases (0x00-0x40): CHALL_POW/SUMCHECK_U/GEMINI_A during sumcheck; + * SS_POS_INV_DENOM/SS_NEG_INV_DENOM/SS_GEMINI_EVALS during shplemini. + * MSM stage reuses 0x00-0xA0 for ACCUMULATOR, G1_LOCATION, SCALAR. + */ + + // {{ SECTION_START MEMORY_LAYOUT }} + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* VK INDICIES */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant VK_CIRCUIT_SIZE_LOC = 0x1000; +uint256 internal constant VK_NUM_PUBLIC_INPUTS_LOC = 0x1020; +uint256 internal constant VK_PUB_INPUTS_OFFSET_LOC = 0x1040; +uint256 internal constant Q_M_X_LOC = 0x1060; +uint256 internal constant Q_M_Y_LOC = 0x1080; +uint256 internal constant Q_L_X_LOC = 0x10a0; +uint256 internal constant Q_L_Y_LOC = 0x10c0; +uint256 internal constant Q_R_X_LOC = 0x10e0; +uint256 internal constant Q_R_Y_LOC = 0x1100; +uint256 internal constant Q_O_X_LOC = 0x1120; +uint256 internal constant Q_O_Y_LOC = 0x1140; +uint256 internal constant Q_4_X_LOC = 0x1160; +uint256 internal constant Q_4_Y_LOC = 0x1180; +uint256 internal constant Q_C_X_LOC = 0x11a0; +uint256 internal constant Q_C_Y_LOC = 0x11c0; +uint256 internal constant Q_ARITH_X_LOC = 0x11e0; +uint256 internal constant Q_ARITH_Y_LOC = 0x1200; +uint256 internal constant SIGMA_1_X_LOC = 0x1220; +uint256 internal constant SIGMA_1_Y_LOC = 0x1240; +uint256 internal constant SIGMA_2_X_LOC = 0x1260; +uint256 internal constant SIGMA_2_Y_LOC = 0x1280; +uint256 internal constant SIGMA_3_X_LOC = 0x12a0; +uint256 internal constant SIGMA_3_Y_LOC = 0x12c0; +uint256 internal constant SIGMA_4_X_LOC = 0x12e0; +uint256 internal constant SIGMA_4_Y_LOC = 0x1300; +uint256 internal constant ID_1_X_LOC = 0x1320; +uint256 internal constant ID_1_Y_LOC = 0x1340; +uint256 internal constant ID_2_X_LOC = 0x1360; +uint256 internal constant ID_2_Y_LOC = 0x1380; +uint256 internal constant ID_3_X_LOC = 0x13a0; +uint256 internal constant ID_3_Y_LOC = 0x13c0; +uint256 internal constant ID_4_X_LOC = 0x13e0; +uint256 internal constant ID_4_Y_LOC = 0x1400; +uint256 internal constant LAGRANGE_FIRST_X_LOC = 0x1420; +uint256 internal constant LAGRANGE_FIRST_Y_LOC = 0x1440; +uint256 internal constant LAGRANGE_LAST_X_LOC = 0x1460; +uint256 internal constant LAGRANGE_LAST_Y_LOC = 0x1480; +uint256 internal constant Q_LOOKUP_X_LOC = 0x14a0; +uint256 internal constant Q_LOOKUP_Y_LOC = 0x14c0; +uint256 internal constant TABLE_1_X_LOC = 0x14e0; +uint256 internal constant TABLE_1_Y_LOC = 0x1500; +uint256 internal constant TABLE_2_X_LOC = 0x1520; +uint256 internal constant TABLE_2_Y_LOC = 0x1540; +uint256 internal constant TABLE_3_X_LOC = 0x1560; +uint256 internal constant TABLE_3_Y_LOC = 0x1580; +uint256 internal constant TABLE_4_X_LOC = 0x15a0; +uint256 internal constant TABLE_4_Y_LOC = 0x15c0; +uint256 internal constant Q_DELTA_RANGE_X_LOC = 0x15e0; +uint256 internal constant Q_DELTA_RANGE_Y_LOC = 0x1600; +uint256 internal constant Q_ELLIPTIC_X_LOC = 0x1620; +uint256 internal constant Q_ELLIPTIC_Y_LOC = 0x1640; +uint256 internal constant Q_MEMORY_X_LOC = 0x1660; +uint256 internal constant Q_MEMORY_Y_LOC = 0x1680; +uint256 internal constant Q_NNF_X_LOC = 0x16a0; +uint256 internal constant Q_NNF_Y_LOC = 0x16c0; +uint256 internal constant Q_POSEIDON_2_EXTERNAL_X_LOC = 0x16e0; +uint256 internal constant Q_POSEIDON_2_EXTERNAL_Y_LOC = 0x1700; +uint256 internal constant Q_POSEIDON_2_INTERNAL_X_LOC = 0x1720; +uint256 internal constant Q_POSEIDON_2_INTERNAL_Y_LOC = 0x1740; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* PROOF INDICIES */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant PAIRING_POINT_0_X_0_LOC = 0x1760; +uint256 internal constant PAIRING_POINT_0_X_1_LOC = 0x1780; +uint256 internal constant PAIRING_POINT_0_Y_0_LOC = 0x17a0; +uint256 internal constant PAIRING_POINT_0_Y_1_LOC = 0x17c0; +uint256 internal constant PAIRING_POINT_1_X_0_LOC = 0x17e0; +uint256 internal constant PAIRING_POINT_1_X_1_LOC = 0x1800; +uint256 internal constant PAIRING_POINT_1_Y_0_LOC = 0x1820; +uint256 internal constant PAIRING_POINT_1_Y_1_LOC = 0x1840; +uint256 internal constant W_L_X_LOC = 0x1860; +uint256 internal constant W_L_Y_LOC = 0x1880; +uint256 internal constant W_R_X_LOC = 0x18a0; +uint256 internal constant W_R_Y_LOC = 0x18c0; +uint256 internal constant W_O_X_LOC = 0x18e0; +uint256 internal constant W_O_Y_LOC = 0x1900; +uint256 internal constant LOOKUP_READ_COUNTS_X_LOC = 0x1920; +uint256 internal constant LOOKUP_READ_COUNTS_Y_LOC = 0x1940; +uint256 internal constant LOOKUP_READ_TAGS_X_LOC = 0x1960; +uint256 internal constant LOOKUP_READ_TAGS_Y_LOC = 0x1980; +uint256 internal constant W_4_X_LOC = 0x19a0; +uint256 internal constant W_4_Y_LOC = 0x19c0; +uint256 internal constant LOOKUP_INVERSES_X_LOC = 0x19e0; +uint256 internal constant LOOKUP_INVERSES_Y_LOC = 0x1a00; +uint256 internal constant Z_PERM_X_LOC = 0x1a20; +uint256 internal constant Z_PERM_Y_LOC = 0x1a40; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* PROOF INDICIES - SUMCHECK UNIVARIATES */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant SUMCHECK_UNIVARIATE_0_0_LOC = 0x1a60; +uint256 internal constant SUMCHECK_UNIVARIATE_0_1_LOC = 0x1a80; +uint256 internal constant SUMCHECK_UNIVARIATE_0_2_LOC = 0x1aa0; +uint256 internal constant SUMCHECK_UNIVARIATE_0_3_LOC = 0x1ac0; +uint256 internal constant SUMCHECK_UNIVARIATE_0_4_LOC = 0x1ae0; +uint256 internal constant SUMCHECK_UNIVARIATE_0_5_LOC = 0x1b00; +uint256 internal constant SUMCHECK_UNIVARIATE_0_6_LOC = 0x1b20; +uint256 internal constant SUMCHECK_UNIVARIATE_0_7_LOC = 0x1b40; +uint256 internal constant SUMCHECK_UNIVARIATE_1_0_LOC = 0x1b60; +uint256 internal constant SUMCHECK_UNIVARIATE_1_1_LOC = 0x1b80; +uint256 internal constant SUMCHECK_UNIVARIATE_1_2_LOC = 0x1ba0; +uint256 internal constant SUMCHECK_UNIVARIATE_1_3_LOC = 0x1bc0; +uint256 internal constant SUMCHECK_UNIVARIATE_1_4_LOC = 0x1be0; +uint256 internal constant SUMCHECK_UNIVARIATE_1_5_LOC = 0x1c00; +uint256 internal constant SUMCHECK_UNIVARIATE_1_6_LOC = 0x1c20; +uint256 internal constant SUMCHECK_UNIVARIATE_1_7_LOC = 0x1c40; +uint256 internal constant SUMCHECK_UNIVARIATE_2_0_LOC = 0x1c60; +uint256 internal constant SUMCHECK_UNIVARIATE_2_1_LOC = 0x1c80; +uint256 internal constant SUMCHECK_UNIVARIATE_2_2_LOC = 0x1ca0; +uint256 internal constant SUMCHECK_UNIVARIATE_2_3_LOC = 0x1cc0; +uint256 internal constant SUMCHECK_UNIVARIATE_2_4_LOC = 0x1ce0; +uint256 internal constant SUMCHECK_UNIVARIATE_2_5_LOC = 0x1d00; +uint256 internal constant SUMCHECK_UNIVARIATE_2_6_LOC = 0x1d20; +uint256 internal constant SUMCHECK_UNIVARIATE_2_7_LOC = 0x1d40; +uint256 internal constant SUMCHECK_UNIVARIATE_3_0_LOC = 0x1d60; +uint256 internal constant SUMCHECK_UNIVARIATE_3_1_LOC = 0x1d80; +uint256 internal constant SUMCHECK_UNIVARIATE_3_2_LOC = 0x1da0; +uint256 internal constant SUMCHECK_UNIVARIATE_3_3_LOC = 0x1dc0; +uint256 internal constant SUMCHECK_UNIVARIATE_3_4_LOC = 0x1de0; +uint256 internal constant SUMCHECK_UNIVARIATE_3_5_LOC = 0x1e00; +uint256 internal constant SUMCHECK_UNIVARIATE_3_6_LOC = 0x1e20; +uint256 internal constant SUMCHECK_UNIVARIATE_3_7_LOC = 0x1e40; +uint256 internal constant SUMCHECK_UNIVARIATE_4_0_LOC = 0x1e60; +uint256 internal constant SUMCHECK_UNIVARIATE_4_1_LOC = 0x1e80; +uint256 internal constant SUMCHECK_UNIVARIATE_4_2_LOC = 0x1ea0; +uint256 internal constant SUMCHECK_UNIVARIATE_4_3_LOC = 0x1ec0; +uint256 internal constant SUMCHECK_UNIVARIATE_4_4_LOC = 0x1ee0; +uint256 internal constant SUMCHECK_UNIVARIATE_4_5_LOC = 0x1f00; +uint256 internal constant SUMCHECK_UNIVARIATE_4_6_LOC = 0x1f20; +uint256 internal constant SUMCHECK_UNIVARIATE_4_7_LOC = 0x1f40; +uint256 internal constant SUMCHECK_UNIVARIATE_5_0_LOC = 0x1f60; +uint256 internal constant SUMCHECK_UNIVARIATE_5_1_LOC = 0x1f80; +uint256 internal constant SUMCHECK_UNIVARIATE_5_2_LOC = 0x1fa0; +uint256 internal constant SUMCHECK_UNIVARIATE_5_3_LOC = 0x1fc0; +uint256 internal constant SUMCHECK_UNIVARIATE_5_4_LOC = 0x1fe0; +uint256 internal constant SUMCHECK_UNIVARIATE_5_5_LOC = 0x2000; +uint256 internal constant SUMCHECK_UNIVARIATE_5_6_LOC = 0x2020; +uint256 internal constant SUMCHECK_UNIVARIATE_5_7_LOC = 0x2040; +uint256 internal constant SUMCHECK_UNIVARIATE_6_0_LOC = 0x2060; +uint256 internal constant SUMCHECK_UNIVARIATE_6_1_LOC = 0x2080; +uint256 internal constant SUMCHECK_UNIVARIATE_6_2_LOC = 0x20a0; +uint256 internal constant SUMCHECK_UNIVARIATE_6_3_LOC = 0x20c0; +uint256 internal constant SUMCHECK_UNIVARIATE_6_4_LOC = 0x20e0; +uint256 internal constant SUMCHECK_UNIVARIATE_6_5_LOC = 0x2100; +uint256 internal constant SUMCHECK_UNIVARIATE_6_6_LOC = 0x2120; +uint256 internal constant SUMCHECK_UNIVARIATE_6_7_LOC = 0x2140; +uint256 internal constant SUMCHECK_UNIVARIATE_7_0_LOC = 0x2160; +uint256 internal constant SUMCHECK_UNIVARIATE_7_1_LOC = 0x2180; +uint256 internal constant SUMCHECK_UNIVARIATE_7_2_LOC = 0x21a0; +uint256 internal constant SUMCHECK_UNIVARIATE_7_3_LOC = 0x21c0; +uint256 internal constant SUMCHECK_UNIVARIATE_7_4_LOC = 0x21e0; +uint256 internal constant SUMCHECK_UNIVARIATE_7_5_LOC = 0x2200; +uint256 internal constant SUMCHECK_UNIVARIATE_7_6_LOC = 0x2220; +uint256 internal constant SUMCHECK_UNIVARIATE_7_7_LOC = 0x2240; +uint256 internal constant SUMCHECK_UNIVARIATE_8_0_LOC = 0x2260; +uint256 internal constant SUMCHECK_UNIVARIATE_8_1_LOC = 0x2280; +uint256 internal constant SUMCHECK_UNIVARIATE_8_2_LOC = 0x22a0; +uint256 internal constant SUMCHECK_UNIVARIATE_8_3_LOC = 0x22c0; +uint256 internal constant SUMCHECK_UNIVARIATE_8_4_LOC = 0x22e0; +uint256 internal constant SUMCHECK_UNIVARIATE_8_5_LOC = 0x2300; +uint256 internal constant SUMCHECK_UNIVARIATE_8_6_LOC = 0x2320; +uint256 internal constant SUMCHECK_UNIVARIATE_8_7_LOC = 0x2340; +uint256 internal constant SUMCHECK_UNIVARIATE_9_0_LOC = 0x2360; +uint256 internal constant SUMCHECK_UNIVARIATE_9_1_LOC = 0x2380; +uint256 internal constant SUMCHECK_UNIVARIATE_9_2_LOC = 0x23a0; +uint256 internal constant SUMCHECK_UNIVARIATE_9_3_LOC = 0x23c0; +uint256 internal constant SUMCHECK_UNIVARIATE_9_4_LOC = 0x23e0; +uint256 internal constant SUMCHECK_UNIVARIATE_9_5_LOC = 0x2400; +uint256 internal constant SUMCHECK_UNIVARIATE_9_6_LOC = 0x2420; +uint256 internal constant SUMCHECK_UNIVARIATE_9_7_LOC = 0x2440; +uint256 internal constant SUMCHECK_UNIVARIATE_10_0_LOC = 0x2460; +uint256 internal constant SUMCHECK_UNIVARIATE_10_1_LOC = 0x2480; +uint256 internal constant SUMCHECK_UNIVARIATE_10_2_LOC = 0x24a0; +uint256 internal constant SUMCHECK_UNIVARIATE_10_3_LOC = 0x24c0; +uint256 internal constant SUMCHECK_UNIVARIATE_10_4_LOC = 0x24e0; +uint256 internal constant SUMCHECK_UNIVARIATE_10_5_LOC = 0x2500; +uint256 internal constant SUMCHECK_UNIVARIATE_10_6_LOC = 0x2520; +uint256 internal constant SUMCHECK_UNIVARIATE_10_7_LOC = 0x2540; +uint256 internal constant SUMCHECK_UNIVARIATE_11_0_LOC = 0x2560; +uint256 internal constant SUMCHECK_UNIVARIATE_11_1_LOC = 0x2580; +uint256 internal constant SUMCHECK_UNIVARIATE_11_2_LOC = 0x25a0; +uint256 internal constant SUMCHECK_UNIVARIATE_11_3_LOC = 0x25c0; +uint256 internal constant SUMCHECK_UNIVARIATE_11_4_LOC = 0x25e0; +uint256 internal constant SUMCHECK_UNIVARIATE_11_5_LOC = 0x2600; +uint256 internal constant SUMCHECK_UNIVARIATE_11_6_LOC = 0x2620; +uint256 internal constant SUMCHECK_UNIVARIATE_11_7_LOC = 0x2640; +uint256 internal constant SUMCHECK_UNIVARIATE_12_0_LOC = 0x2660; +uint256 internal constant SUMCHECK_UNIVARIATE_12_1_LOC = 0x2680; +uint256 internal constant SUMCHECK_UNIVARIATE_12_2_LOC = 0x26a0; +uint256 internal constant SUMCHECK_UNIVARIATE_12_3_LOC = 0x26c0; +uint256 internal constant SUMCHECK_UNIVARIATE_12_4_LOC = 0x26e0; +uint256 internal constant SUMCHECK_UNIVARIATE_12_5_LOC = 0x2700; +uint256 internal constant SUMCHECK_UNIVARIATE_12_6_LOC = 0x2720; +uint256 internal constant SUMCHECK_UNIVARIATE_12_7_LOC = 0x2740; +uint256 internal constant SUMCHECK_UNIVARIATE_13_0_LOC = 0x2760; +uint256 internal constant SUMCHECK_UNIVARIATE_13_1_LOC = 0x2780; +uint256 internal constant SUMCHECK_UNIVARIATE_13_2_LOC = 0x27a0; +uint256 internal constant SUMCHECK_UNIVARIATE_13_3_LOC = 0x27c0; +uint256 internal constant SUMCHECK_UNIVARIATE_13_4_LOC = 0x27e0; +uint256 internal constant SUMCHECK_UNIVARIATE_13_5_LOC = 0x2800; +uint256 internal constant SUMCHECK_UNIVARIATE_13_6_LOC = 0x2820; +uint256 internal constant SUMCHECK_UNIVARIATE_13_7_LOC = 0x2840; +uint256 internal constant SUMCHECK_UNIVARIATE_14_0_LOC = 0x2860; +uint256 internal constant SUMCHECK_UNIVARIATE_14_1_LOC = 0x2880; +uint256 internal constant SUMCHECK_UNIVARIATE_14_2_LOC = 0x28a0; +uint256 internal constant SUMCHECK_UNIVARIATE_14_3_LOC = 0x28c0; +uint256 internal constant SUMCHECK_UNIVARIATE_14_4_LOC = 0x28e0; +uint256 internal constant SUMCHECK_UNIVARIATE_14_5_LOC = 0x2900; +uint256 internal constant SUMCHECK_UNIVARIATE_14_6_LOC = 0x2920; +uint256 internal constant SUMCHECK_UNIVARIATE_14_7_LOC = 0x2940; +uint256 internal constant SUMCHECK_UNIVARIATE_15_0_LOC = 0x2960; +uint256 internal constant SUMCHECK_UNIVARIATE_15_1_LOC = 0x2980; +uint256 internal constant SUMCHECK_UNIVARIATE_15_2_LOC = 0x29a0; +uint256 internal constant SUMCHECK_UNIVARIATE_15_3_LOC = 0x29c0; +uint256 internal constant SUMCHECK_UNIVARIATE_15_4_LOC = 0x29e0; +uint256 internal constant SUMCHECK_UNIVARIATE_15_5_LOC = 0x2a00; +uint256 internal constant SUMCHECK_UNIVARIATE_15_6_LOC = 0x2a20; +uint256 internal constant SUMCHECK_UNIVARIATE_15_7_LOC = 0x2a40; +uint256 internal constant SUMCHECK_UNIVARIATE_16_0_LOC = 0x2a60; +uint256 internal constant SUMCHECK_UNIVARIATE_16_1_LOC = 0x2a80; +uint256 internal constant SUMCHECK_UNIVARIATE_16_2_LOC = 0x2aa0; +uint256 internal constant SUMCHECK_UNIVARIATE_16_3_LOC = 0x2ac0; +uint256 internal constant SUMCHECK_UNIVARIATE_16_4_LOC = 0x2ae0; +uint256 internal constant SUMCHECK_UNIVARIATE_16_5_LOC = 0x2b00; +uint256 internal constant SUMCHECK_UNIVARIATE_16_6_LOC = 0x2b20; +uint256 internal constant SUMCHECK_UNIVARIATE_16_7_LOC = 0x2b40; +uint256 internal constant SUMCHECK_UNIVARIATE_17_0_LOC = 0x2b60; +uint256 internal constant SUMCHECK_UNIVARIATE_17_1_LOC = 0x2b80; +uint256 internal constant SUMCHECK_UNIVARIATE_17_2_LOC = 0x2ba0; +uint256 internal constant SUMCHECK_UNIVARIATE_17_3_LOC = 0x2bc0; +uint256 internal constant SUMCHECK_UNIVARIATE_17_4_LOC = 0x2be0; +uint256 internal constant SUMCHECK_UNIVARIATE_17_5_LOC = 0x2c00; +uint256 internal constant SUMCHECK_UNIVARIATE_17_6_LOC = 0x2c20; +uint256 internal constant SUMCHECK_UNIVARIATE_17_7_LOC = 0x2c40; +uint256 internal constant SUMCHECK_UNIVARIATE_18_0_LOC = 0x2c60; +uint256 internal constant SUMCHECK_UNIVARIATE_18_1_LOC = 0x2c80; +uint256 internal constant SUMCHECK_UNIVARIATE_18_2_LOC = 0x2ca0; +uint256 internal constant SUMCHECK_UNIVARIATE_18_3_LOC = 0x2cc0; +uint256 internal constant SUMCHECK_UNIVARIATE_18_4_LOC = 0x2ce0; +uint256 internal constant SUMCHECK_UNIVARIATE_18_5_LOC = 0x2d00; +uint256 internal constant SUMCHECK_UNIVARIATE_18_6_LOC = 0x2d20; +uint256 internal constant SUMCHECK_UNIVARIATE_18_7_LOC = 0x2d40; +uint256 internal constant SUMCHECK_UNIVARIATE_19_0_LOC = 0x2d60; +uint256 internal constant SUMCHECK_UNIVARIATE_19_1_LOC = 0x2d80; +uint256 internal constant SUMCHECK_UNIVARIATE_19_2_LOC = 0x2da0; +uint256 internal constant SUMCHECK_UNIVARIATE_19_3_LOC = 0x2dc0; +uint256 internal constant SUMCHECK_UNIVARIATE_19_4_LOC = 0x2de0; +uint256 internal constant SUMCHECK_UNIVARIATE_19_5_LOC = 0x2e00; +uint256 internal constant SUMCHECK_UNIVARIATE_19_6_LOC = 0x2e20; +uint256 internal constant SUMCHECK_UNIVARIATE_19_7_LOC = 0x2e40; +uint256 internal constant SUMCHECK_UNIVARIATE_20_0_LOC = 0x2e60; +uint256 internal constant SUMCHECK_UNIVARIATE_20_1_LOC = 0x2e80; +uint256 internal constant SUMCHECK_UNIVARIATE_20_2_LOC = 0x2ea0; +uint256 internal constant SUMCHECK_UNIVARIATE_20_3_LOC = 0x2ec0; +uint256 internal constant SUMCHECK_UNIVARIATE_20_4_LOC = 0x2ee0; +uint256 internal constant SUMCHECK_UNIVARIATE_20_5_LOC = 0x2f00; +uint256 internal constant SUMCHECK_UNIVARIATE_20_6_LOC = 0x2f20; +uint256 internal constant SUMCHECK_UNIVARIATE_20_7_LOC = 0x2f40; +uint256 internal constant SUMCHECK_UNIVARIATE_21_0_LOC = 0x2f60; +uint256 internal constant SUMCHECK_UNIVARIATE_21_1_LOC = 0x2f80; +uint256 internal constant SUMCHECK_UNIVARIATE_21_2_LOC = 0x2fa0; +uint256 internal constant SUMCHECK_UNIVARIATE_21_3_LOC = 0x2fc0; +uint256 internal constant SUMCHECK_UNIVARIATE_21_4_LOC = 0x2fe0; +uint256 internal constant SUMCHECK_UNIVARIATE_21_5_LOC = 0x3000; +uint256 internal constant SUMCHECK_UNIVARIATE_21_6_LOC = 0x3020; +uint256 internal constant SUMCHECK_UNIVARIATE_21_7_LOC = 0x3040; +uint256 internal constant SUMCHECK_UNIVARIATE_22_0_LOC = 0x3060; +uint256 internal constant SUMCHECK_UNIVARIATE_22_1_LOC = 0x3080; +uint256 internal constant SUMCHECK_UNIVARIATE_22_2_LOC = 0x30a0; +uint256 internal constant SUMCHECK_UNIVARIATE_22_3_LOC = 0x30c0; +uint256 internal constant SUMCHECK_UNIVARIATE_22_4_LOC = 0x30e0; +uint256 internal constant SUMCHECK_UNIVARIATE_22_5_LOC = 0x3100; +uint256 internal constant SUMCHECK_UNIVARIATE_22_6_LOC = 0x3120; +uint256 internal constant SUMCHECK_UNIVARIATE_22_7_LOC = 0x3140; +uint256 internal constant SUMCHECK_UNIVARIATE_23_0_LOC = 0x3160; +uint256 internal constant SUMCHECK_UNIVARIATE_23_1_LOC = 0x3180; +uint256 internal constant SUMCHECK_UNIVARIATE_23_2_LOC = 0x31a0; +uint256 internal constant SUMCHECK_UNIVARIATE_23_3_LOC = 0x31c0; +uint256 internal constant SUMCHECK_UNIVARIATE_23_4_LOC = 0x31e0; +uint256 internal constant SUMCHECK_UNIVARIATE_23_5_LOC = 0x3200; +uint256 internal constant SUMCHECK_UNIVARIATE_23_6_LOC = 0x3220; +uint256 internal constant SUMCHECK_UNIVARIATE_23_7_LOC = 0x3240; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* PROOF INDICIES - SUMCHECK EVALUATIONS */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant SIGMA1_EVAL_LOC = 0x3260; +uint256 internal constant SIGMA2_EVAL_LOC = 0x3280; +uint256 internal constant SIGMA3_EVAL_LOC = 0x32a0; +uint256 internal constant SIGMA4_EVAL_LOC = 0x32c0; +uint256 internal constant ID1_EVAL_LOC = 0x32e0; +uint256 internal constant ID2_EVAL_LOC = 0x3300; +uint256 internal constant ID3_EVAL_LOC = 0x3320; +uint256 internal constant ID4_EVAL_LOC = 0x3340; +uint256 internal constant LAGRANGE_FIRST_EVAL_LOC = 0x3360; +uint256 internal constant LAGRANGE_LAST_EVAL_LOC = 0x3380; +uint256 internal constant QLOOKUP_EVAL_LOC = 0x33a0; +uint256 internal constant TABLE1_EVAL_LOC = 0x33c0; +uint256 internal constant TABLE2_EVAL_LOC = 0x33e0; +uint256 internal constant TABLE3_EVAL_LOC = 0x3400; +uint256 internal constant TABLE4_EVAL_LOC = 0x3420; +uint256 internal constant QM_EVAL_LOC = 0x3440; +uint256 internal constant QR_EVAL_LOC = 0x3460; +uint256 internal constant QO_EVAL_LOC = 0x3480; +uint256 internal constant QC_EVAL_LOC = 0x34a0; +uint256 internal constant QL_EVAL_LOC = 0x34c0; +uint256 internal constant Q4_EVAL_LOC = 0x34e0; +uint256 internal constant QARITH_EVAL_LOC = 0x3500; +uint256 internal constant QRANGE_EVAL_LOC = 0x3520; +uint256 internal constant QELLIPTIC_EVAL_LOC = 0x3540; +uint256 internal constant QMEMORY_EVAL_LOC = 0x3560; +uint256 internal constant QNNF_EVAL_LOC = 0x3580; +uint256 internal constant QPOSEIDON2_EXTERNAL_EVAL_LOC = 0x35a0; +uint256 internal constant QPOSEIDON2_INTERNAL_EVAL_LOC = 0x35c0; +uint256 internal constant W1_EVAL_LOC = 0x35e0; +uint256 internal constant W2_EVAL_LOC = 0x3600; +uint256 internal constant W3_EVAL_LOC = 0x3620; +uint256 internal constant W4_EVAL_LOC = 0x3640; +uint256 internal constant Z_PERM_EVAL_LOC = 0x3660; +uint256 internal constant LOOKUP_INVERSES_EVAL_LOC = 0x3680; +uint256 internal constant LOOKUP_READ_COUNTS_EVAL_LOC = 0x36a0; +uint256 internal constant LOOKUP_READ_TAGS_EVAL_LOC = 0x36c0; +uint256 internal constant W1_SHIFT_EVAL_LOC = 0x36e0; +uint256 internal constant W2_SHIFT_EVAL_LOC = 0x3700; +uint256 internal constant W3_SHIFT_EVAL_LOC = 0x3720; +uint256 internal constant W4_SHIFT_EVAL_LOC = 0x3740; +uint256 internal constant Z_PERM_SHIFT_EVAL_LOC = 0x3760; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* PROOF INDICIES - GEMINI FOLDING COMMS */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant GEMINI_FOLD_UNIVARIATE_0_X_LOC = 0x3780; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_0_Y_LOC = 0x37a0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_1_X_LOC = 0x37c0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_1_Y_LOC = 0x37e0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_2_X_LOC = 0x3800; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_2_Y_LOC = 0x3820; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_3_X_LOC = 0x3840; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_3_Y_LOC = 0x3860; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_4_X_LOC = 0x3880; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_4_Y_LOC = 0x38a0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_5_X_LOC = 0x38c0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_5_Y_LOC = 0x38e0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_6_X_LOC = 0x3900; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_6_Y_LOC = 0x3920; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_7_X_LOC = 0x3940; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_7_Y_LOC = 0x3960; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_8_X_LOC = 0x3980; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_8_Y_LOC = 0x39a0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_9_X_LOC = 0x39c0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_9_Y_LOC = 0x39e0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_10_X_LOC = 0x3a00; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_10_Y_LOC = 0x3a20; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_11_X_LOC = 0x3a40; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_11_Y_LOC = 0x3a60; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_12_X_LOC = 0x3a80; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_12_Y_LOC = 0x3aa0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_13_X_LOC = 0x3ac0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_13_Y_LOC = 0x3ae0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_14_X_LOC = 0x3b00; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_14_Y_LOC = 0x3b20; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_15_X_LOC = 0x3b40; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_15_Y_LOC = 0x3b60; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_16_X_LOC = 0x3b80; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_16_Y_LOC = 0x3ba0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_17_X_LOC = 0x3bc0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_17_Y_LOC = 0x3be0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_18_X_LOC = 0x3c00; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_18_Y_LOC = 0x3c20; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_19_X_LOC = 0x3c40; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_19_Y_LOC = 0x3c60; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_20_X_LOC = 0x3c80; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_20_Y_LOC = 0x3ca0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_21_X_LOC = 0x3cc0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_21_Y_LOC = 0x3ce0; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_22_X_LOC = 0x3d00; +uint256 internal constant GEMINI_FOLD_UNIVARIATE_22_Y_LOC = 0x3d20; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* PROOF INDICIES - GEMINI FOLDING EVALUATIONS */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant GEMINI_A_EVAL_0 = 0x3d40; +uint256 internal constant GEMINI_A_EVAL_1 = 0x3d60; +uint256 internal constant GEMINI_A_EVAL_2 = 0x3d80; +uint256 internal constant GEMINI_A_EVAL_3 = 0x3da0; +uint256 internal constant GEMINI_A_EVAL_4 = 0x3dc0; +uint256 internal constant GEMINI_A_EVAL_5 = 0x3de0; +uint256 internal constant GEMINI_A_EVAL_6 = 0x3e00; +uint256 internal constant GEMINI_A_EVAL_7 = 0x3e20; +uint256 internal constant GEMINI_A_EVAL_8 = 0x3e40; +uint256 internal constant GEMINI_A_EVAL_9 = 0x3e60; +uint256 internal constant GEMINI_A_EVAL_10 = 0x3e80; +uint256 internal constant GEMINI_A_EVAL_11 = 0x3ea0; +uint256 internal constant GEMINI_A_EVAL_12 = 0x3ec0; +uint256 internal constant GEMINI_A_EVAL_13 = 0x3ee0; +uint256 internal constant GEMINI_A_EVAL_14 = 0x3f00; +uint256 internal constant GEMINI_A_EVAL_15 = 0x3f20; +uint256 internal constant GEMINI_A_EVAL_16 = 0x3f40; +uint256 internal constant GEMINI_A_EVAL_17 = 0x3f60; +uint256 internal constant GEMINI_A_EVAL_18 = 0x3f80; +uint256 internal constant GEMINI_A_EVAL_19 = 0x3fa0; +uint256 internal constant GEMINI_A_EVAL_20 = 0x3fc0; +uint256 internal constant GEMINI_A_EVAL_21 = 0x3fe0; +uint256 internal constant GEMINI_A_EVAL_22 = 0x4000; +uint256 internal constant GEMINI_A_EVAL_23 = 0x4020; +uint256 internal constant SHPLONK_Q_X_LOC = 0x4040; +uint256 internal constant SHPLONK_Q_Y_LOC = 0x4060; +uint256 internal constant KZG_QUOTIENT_X_LOC = 0x4080; +uint256 internal constant KZG_QUOTIENT_Y_LOC = 0x40a0; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* PROOF INDICIES - COMPLETE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* CHALLENGES */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant ETA_CHALLENGE = 0x40c0; +uint256 internal constant ETA_TWO_CHALLENGE = 0x40e0; +uint256 internal constant ETA_THREE_CHALLENGE = 0x4100; +uint256 internal constant ROM_LOGUP_GAMMA_CHALLENGE = 0x4120; +uint256 internal constant BETA_CHALLENGE = 0x4140; +uint256 internal constant GAMMA_CHALLENGE = 0x4160; +uint256 internal constant RHO_CHALLENGE = 0x4180; +uint256 internal constant GEMINI_R_CHALLENGE = 0x41a0; +uint256 internal constant SHPLONK_NU_CHALLENGE = 0x41c0; +uint256 internal constant SHPLONK_Z_CHALLENGE = 0x41e0; +uint256 internal constant PUBLIC_INPUTS_DELTA_NUMERATOR_CHALLENGE = 0x4200; +uint256 internal constant PUBLIC_INPUTS_DELTA_DENOMINATOR_CHALLENGE = 0x4220; +uint256 internal constant ALPHA_CHALLENGE_0 = 0x4240; +uint256 internal constant ALPHA_CHALLENGE_1 = 0x4260; +uint256 internal constant ALPHA_CHALLENGE_2 = 0x4280; +uint256 internal constant ALPHA_CHALLENGE_3 = 0x42a0; +uint256 internal constant ALPHA_CHALLENGE_4 = 0x42c0; +uint256 internal constant ALPHA_CHALLENGE_5 = 0x42e0; +uint256 internal constant ALPHA_CHALLENGE_6 = 0x4300; +uint256 internal constant ALPHA_CHALLENGE_7 = 0x4320; +uint256 internal constant ALPHA_CHALLENGE_8 = 0x4340; +uint256 internal constant ALPHA_CHALLENGE_9 = 0x4360; +uint256 internal constant ALPHA_CHALLENGE_10 = 0x4380; +uint256 internal constant ALPHA_CHALLENGE_11 = 0x43a0; +uint256 internal constant ALPHA_CHALLENGE_12 = 0x43c0; +uint256 internal constant ALPHA_CHALLENGE_13 = 0x43e0; +uint256 internal constant ALPHA_CHALLENGE_14 = 0x4400; +uint256 internal constant ALPHA_CHALLENGE_15 = 0x4420; +uint256 internal constant ALPHA_CHALLENGE_16 = 0x4440; +uint256 internal constant ALPHA_CHALLENGE_17 = 0x4460; +uint256 internal constant ALPHA_CHALLENGE_18 = 0x4480; +uint256 internal constant ALPHA_CHALLENGE_19 = 0x44a0; +uint256 internal constant ALPHA_CHALLENGE_20 = 0x44c0; +uint256 internal constant ALPHA_CHALLENGE_21 = 0x44e0; +uint256 internal constant ALPHA_CHALLENGE_22 = 0x4500; +uint256 internal constant ALPHA_CHALLENGE_23 = 0x4520; +uint256 internal constant ALPHA_CHALLENGE_24 = 0x4540; +uint256 internal constant ALPHA_CHALLENGE_25 = 0x4560; +uint256 internal constant ALPHA_CHALLENGE_26 = 0x4580; +uint256 internal constant ALPHA_CHALLENGE_27 = 0x45a0; +uint256 internal constant ALPHA_CHALLENGE_28 = 0x45c0; +uint256 internal constant ALPHA_CHALLENGE_29 = 0x45e0; +uint256 internal constant GATE_CHALLENGE_0 = 0x4600; +uint256 internal constant GATE_CHALLENGE_1 = 0x4620; +uint256 internal constant GATE_CHALLENGE_2 = 0x4640; +uint256 internal constant GATE_CHALLENGE_3 = 0x4660; +uint256 internal constant GATE_CHALLENGE_4 = 0x4680; +uint256 internal constant GATE_CHALLENGE_5 = 0x46a0; +uint256 internal constant GATE_CHALLENGE_6 = 0x46c0; +uint256 internal constant GATE_CHALLENGE_7 = 0x46e0; +uint256 internal constant GATE_CHALLENGE_8 = 0x4700; +uint256 internal constant GATE_CHALLENGE_9 = 0x4720; +uint256 internal constant GATE_CHALLENGE_10 = 0x4740; +uint256 internal constant GATE_CHALLENGE_11 = 0x4760; +uint256 internal constant GATE_CHALLENGE_12 = 0x4780; +uint256 internal constant GATE_CHALLENGE_13 = 0x47a0; +uint256 internal constant GATE_CHALLENGE_14 = 0x47c0; +uint256 internal constant GATE_CHALLENGE_15 = 0x47e0; +uint256 internal constant GATE_CHALLENGE_16 = 0x4800; +uint256 internal constant GATE_CHALLENGE_17 = 0x4820; +uint256 internal constant GATE_CHALLENGE_18 = 0x4840; +uint256 internal constant GATE_CHALLENGE_19 = 0x4860; +uint256 internal constant GATE_CHALLENGE_20 = 0x4880; +uint256 internal constant GATE_CHALLENGE_21 = 0x48a0; +uint256 internal constant GATE_CHALLENGE_22 = 0x48c0; +uint256 internal constant GATE_CHALLENGE_23 = 0x48e0; +uint256 internal constant SUM_U_CHALLENGE_0 = 0x4900; +uint256 internal constant SUM_U_CHALLENGE_1 = 0x4920; +uint256 internal constant SUM_U_CHALLENGE_2 = 0x4940; +uint256 internal constant SUM_U_CHALLENGE_3 = 0x4960; +uint256 internal constant SUM_U_CHALLENGE_4 = 0x4980; +uint256 internal constant SUM_U_CHALLENGE_5 = 0x49a0; +uint256 internal constant SUM_U_CHALLENGE_6 = 0x49c0; +uint256 internal constant SUM_U_CHALLENGE_7 = 0x49e0; +uint256 internal constant SUM_U_CHALLENGE_8 = 0x4a00; +uint256 internal constant SUM_U_CHALLENGE_9 = 0x4a20; +uint256 internal constant SUM_U_CHALLENGE_10 = 0x4a40; +uint256 internal constant SUM_U_CHALLENGE_11 = 0x4a60; +uint256 internal constant SUM_U_CHALLENGE_12 = 0x4a80; +uint256 internal constant SUM_U_CHALLENGE_13 = 0x4aa0; +uint256 internal constant SUM_U_CHALLENGE_14 = 0x4ac0; +uint256 internal constant SUM_U_CHALLENGE_15 = 0x4ae0; +uint256 internal constant SUM_U_CHALLENGE_16 = 0x4b00; +uint256 internal constant SUM_U_CHALLENGE_17 = 0x4b20; +uint256 internal constant SUM_U_CHALLENGE_18 = 0x4b40; +uint256 internal constant SUM_U_CHALLENGE_19 = 0x4b60; +uint256 internal constant SUM_U_CHALLENGE_20 = 0x4b80; +uint256 internal constant SUM_U_CHALLENGE_21 = 0x4ba0; +uint256 internal constant SUM_U_CHALLENGE_22 = 0x4bc0; +uint256 internal constant SUM_U_CHALLENGE_23 = 0x4be0; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* CHALLENGES - COMPLETE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SUMCHECK - RUNTIME MEMORY */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SUMCHECK - RUNTIME MEMORY - BARYCENTRIC */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_0_LOC = 0x4c00; +uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_1_LOC = 0x4c20; +uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_2_LOC = 0x4c40; +uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_3_LOC = 0x4c60; +uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_4_LOC = 0x4c80; +uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_5_LOC = 0x4ca0; +uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_6_LOC = 0x4cc0; +uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_7_LOC = 0x4ce0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_0_0_LOC = 0x4d00; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_0_1_LOC = 0x4d20; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_0_2_LOC = 0x4d40; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_0_3_LOC = 0x4d60; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_0_4_LOC = 0x4d80; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_0_5_LOC = 0x4da0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_0_6_LOC = 0x4dc0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_0_7_LOC = 0x4de0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_1_0_LOC = 0x4e00; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_1_1_LOC = 0x4e20; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_1_2_LOC = 0x4e40; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_1_3_LOC = 0x4e60; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_1_4_LOC = 0x4e80; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_1_5_LOC = 0x4ea0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_1_6_LOC = 0x4ec0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_1_7_LOC = 0x4ee0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_2_0_LOC = 0x4f00; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_2_1_LOC = 0x4f20; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_2_2_LOC = 0x4f40; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_2_3_LOC = 0x4f60; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_2_4_LOC = 0x4f80; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_2_5_LOC = 0x4fa0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_2_6_LOC = 0x4fc0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_2_7_LOC = 0x4fe0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_3_0_LOC = 0x5000; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_3_1_LOC = 0x5020; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_3_2_LOC = 0x5040; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_3_3_LOC = 0x5060; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_3_4_LOC = 0x5080; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_3_5_LOC = 0x50a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_3_6_LOC = 0x50c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_3_7_LOC = 0x50e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_4_0_LOC = 0x5100; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_4_1_LOC = 0x5120; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_4_2_LOC = 0x5140; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_4_3_LOC = 0x5160; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_4_4_LOC = 0x5180; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_4_5_LOC = 0x51a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_4_6_LOC = 0x51c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_4_7_LOC = 0x51e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_5_0_LOC = 0x5200; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_5_1_LOC = 0x5220; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_5_2_LOC = 0x5240; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_5_3_LOC = 0x5260; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_5_4_LOC = 0x5280; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_5_5_LOC = 0x52a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_5_6_LOC = 0x52c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_5_7_LOC = 0x52e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_6_0_LOC = 0x5300; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_6_1_LOC = 0x5320; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_6_2_LOC = 0x5340; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_6_3_LOC = 0x5360; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_6_4_LOC = 0x5380; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_6_5_LOC = 0x53a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_6_6_LOC = 0x53c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_6_7_LOC = 0x53e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_7_0_LOC = 0x5400; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_7_1_LOC = 0x5420; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_7_2_LOC = 0x5440; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_7_3_LOC = 0x5460; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_7_4_LOC = 0x5480; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_7_5_LOC = 0x54a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_7_6_LOC = 0x54c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_7_7_LOC = 0x54e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_8_0_LOC = 0x5500; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_8_1_LOC = 0x5520; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_8_2_LOC = 0x5540; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_8_3_LOC = 0x5560; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_8_4_LOC = 0x5580; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_8_5_LOC = 0x55a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_8_6_LOC = 0x55c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_8_7_LOC = 0x55e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_9_0_LOC = 0x5600; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_9_1_LOC = 0x5620; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_9_2_LOC = 0x5640; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_9_3_LOC = 0x5660; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_9_4_LOC = 0x5680; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_9_5_LOC = 0x56a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_9_6_LOC = 0x56c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_9_7_LOC = 0x56e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_10_0_LOC = 0x5700; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_10_1_LOC = 0x5720; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_10_2_LOC = 0x5740; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_10_3_LOC = 0x5760; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_10_4_LOC = 0x5780; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_10_5_LOC = 0x57a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_10_6_LOC = 0x57c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_10_7_LOC = 0x57e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_11_0_LOC = 0x5800; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_11_1_LOC = 0x5820; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_11_2_LOC = 0x5840; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_11_3_LOC = 0x5860; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_11_4_LOC = 0x5880; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_11_5_LOC = 0x58a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_11_6_LOC = 0x58c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_11_7_LOC = 0x58e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_12_0_LOC = 0x5900; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_12_1_LOC = 0x5920; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_12_2_LOC = 0x5940; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_12_3_LOC = 0x5960; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_12_4_LOC = 0x5980; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_12_5_LOC = 0x59a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_12_6_LOC = 0x59c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_12_7_LOC = 0x59e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_13_0_LOC = 0x5a00; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_13_1_LOC = 0x5a20; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_13_2_LOC = 0x5a40; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_13_3_LOC = 0x5a60; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_13_4_LOC = 0x5a80; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_13_5_LOC = 0x5aa0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_13_6_LOC = 0x5ac0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_13_7_LOC = 0x5ae0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_14_0_LOC = 0x5b00; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_14_1_LOC = 0x5b20; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_14_2_LOC = 0x5b40; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_14_3_LOC = 0x5b60; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_14_4_LOC = 0x5b80; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_14_5_LOC = 0x5ba0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_14_6_LOC = 0x5bc0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_14_7_LOC = 0x5be0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_15_0_LOC = 0x5c00; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_15_1_LOC = 0x5c20; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_15_2_LOC = 0x5c40; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_15_3_LOC = 0x5c60; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_15_4_LOC = 0x5c80; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_15_5_LOC = 0x5ca0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_15_6_LOC = 0x5cc0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_15_7_LOC = 0x5ce0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_16_0_LOC = 0x5d00; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_16_1_LOC = 0x5d20; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_16_2_LOC = 0x5d40; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_16_3_LOC = 0x5d60; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_16_4_LOC = 0x5d80; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_16_5_LOC = 0x5da0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_16_6_LOC = 0x5dc0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_16_7_LOC = 0x5de0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_17_0_LOC = 0x5e00; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_17_1_LOC = 0x5e20; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_17_2_LOC = 0x5e40; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_17_3_LOC = 0x5e60; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_17_4_LOC = 0x5e80; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_17_5_LOC = 0x5ea0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_17_6_LOC = 0x5ec0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_17_7_LOC = 0x5ee0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_18_0_LOC = 0x5f00; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_18_1_LOC = 0x5f20; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_18_2_LOC = 0x5f40; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_18_3_LOC = 0x5f60; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_18_4_LOC = 0x5f80; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_18_5_LOC = 0x5fa0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_18_6_LOC = 0x5fc0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_18_7_LOC = 0x5fe0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_19_0_LOC = 0x6000; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_19_1_LOC = 0x6020; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_19_2_LOC = 0x6040; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_19_3_LOC = 0x6060; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_19_4_LOC = 0x6080; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_19_5_LOC = 0x60a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_19_6_LOC = 0x60c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_19_7_LOC = 0x60e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_20_0_LOC = 0x6100; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_20_1_LOC = 0x6120; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_20_2_LOC = 0x6140; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_20_3_LOC = 0x6160; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_20_4_LOC = 0x6180; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_20_5_LOC = 0x61a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_20_6_LOC = 0x61c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_20_7_LOC = 0x61e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_21_0_LOC = 0x6200; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_21_1_LOC = 0x6220; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_21_2_LOC = 0x6240; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_21_3_LOC = 0x6260; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_21_4_LOC = 0x6280; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_21_5_LOC = 0x62a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_21_6_LOC = 0x62c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_21_7_LOC = 0x62e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_22_0_LOC = 0x6300; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_22_1_LOC = 0x6320; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_22_2_LOC = 0x6340; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_22_3_LOC = 0x6360; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_22_4_LOC = 0x6380; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_22_5_LOC = 0x63a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_22_6_LOC = 0x63c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_22_7_LOC = 0x63e0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_23_0_LOC = 0x6400; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_23_1_LOC = 0x6420; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_23_2_LOC = 0x6440; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_23_3_LOC = 0x6460; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_23_4_LOC = 0x6480; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_23_5_LOC = 0x64a0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_23_6_LOC = 0x64c0; +uint256 internal constant BARYCENTRIC_DENOMINATOR_INVERSES_23_7_LOC = 0x64e0; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SUMCHECK - RUNTIME MEMORY - BARYCENTRIC COMPLETE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SUMCHECK - RUNTIME MEMORY - SUBRELATION EVALUATIONS */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant SUBRELATION_EVAL_0_LOC = 0x6500; +uint256 internal constant SUBRELATION_EVAL_1_LOC = 0x6520; +uint256 internal constant SUBRELATION_EVAL_2_LOC = 0x6540; +uint256 internal constant SUBRELATION_EVAL_3_LOC = 0x6560; +uint256 internal constant SUBRELATION_EVAL_4_LOC = 0x6580; +uint256 internal constant SUBRELATION_EVAL_5_LOC = 0x65a0; +uint256 internal constant SUBRELATION_EVAL_6_LOC = 0x65c0; +uint256 internal constant SUBRELATION_EVAL_7_LOC = 0x65e0; +uint256 internal constant SUBRELATION_EVAL_8_LOC = 0x6600; +uint256 internal constant SUBRELATION_EVAL_9_LOC = 0x6620; +uint256 internal constant SUBRELATION_EVAL_10_LOC = 0x6640; +uint256 internal constant SUBRELATION_EVAL_11_LOC = 0x6660; +uint256 internal constant SUBRELATION_EVAL_12_LOC = 0x6680; +uint256 internal constant SUBRELATION_EVAL_13_LOC = 0x66a0; +uint256 internal constant SUBRELATION_EVAL_14_LOC = 0x66c0; +uint256 internal constant SUBRELATION_EVAL_15_LOC = 0x66e0; +uint256 internal constant SUBRELATION_EVAL_16_LOC = 0x6700; +uint256 internal constant SUBRELATION_EVAL_17_LOC = 0x6720; +uint256 internal constant SUBRELATION_EVAL_18_LOC = 0x6740; +uint256 internal constant SUBRELATION_EVAL_19_LOC = 0x6760; +uint256 internal constant SUBRELATION_EVAL_20_LOC = 0x6780; +uint256 internal constant SUBRELATION_EVAL_21_LOC = 0x67a0; +uint256 internal constant SUBRELATION_EVAL_22_LOC = 0x67c0; +uint256 internal constant SUBRELATION_EVAL_23_LOC = 0x67e0; +uint256 internal constant SUBRELATION_EVAL_24_LOC = 0x6800; +uint256 internal constant SUBRELATION_EVAL_25_LOC = 0x6820; +uint256 internal constant SUBRELATION_EVAL_26_LOC = 0x6840; +uint256 internal constant SUBRELATION_EVAL_27_LOC = 0x6860; +uint256 internal constant SUBRELATION_EVAL_28_LOC = 0x6880; +uint256 internal constant SUBRELATION_EVAL_29_LOC = 0x68a0; +uint256 internal constant SUBRELATION_EVAL_30_LOC = 0x68c0; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SUMCHECK - RUNTIME MEMORY - SUBRELATION EVALUATIONS COMPLETE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SUMCHECK - RUNTIME MEMORY - SUBRELATION INTERMEDIATES */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant FINAL_ROUND_TARGET_LOC = 0x68e0; +uint256 internal constant POW_PARTIAL_EVALUATION_LOC = 0x6900; +uint256 internal constant AUX_NON_NATIVE_FIELD_IDENTITY = 0x6920; +uint256 internal constant AUX_LIMB_ACCUMULATOR_IDENTITY = 0x6940; +uint256 internal constant AUX_RAM_CONSISTENCY_CHECK_IDENTITY = 0x6960; +uint256 internal constant AUX_ROM_CONSISTENCY_CHECK_IDENTITY = 0x6980; +uint256 internal constant AUX_MEMORY_CHECK_IDENTITY = 0x69a0; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SUMCHECK - RUNTIME MEMORY - COMPLETE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SHPLEMINI - RUNTIME MEMORY */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SHPLEMINI - POWERS OF EVALUATION CHALLENGE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +/// {{ UNROLL_SECTION_START POWERS_OF_EVALUATION_CHALLENGE }} +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_0_LOC = 0x69c0; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_1_LOC = 0x69e0; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_2_LOC = 0x6a00; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_3_LOC = 0x6a20; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_4_LOC = 0x6a40; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_5_LOC = 0x6a60; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_6_LOC = 0x6a80; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_7_LOC = 0x6aa0; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_8_LOC = 0x6ac0; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_9_LOC = 0x6ae0; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_10_LOC = 0x6b00; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_11_LOC = 0x6b20; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_12_LOC = 0x6b40; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_13_LOC = 0x6b60; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_14_LOC = 0x6b80; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_15_LOC = 0x6ba0; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_16_LOC = 0x6bc0; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_17_LOC = 0x6be0; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_18_LOC = 0x6c00; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_19_LOC = 0x6c20; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_20_LOC = 0x6c40; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_21_LOC = 0x6c60; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_22_LOC = 0x6c80; +uint256 internal constant POWERS_OF_EVALUATION_CHALLENGE_23_LOC = 0x6ca0; +/// {{ UNROLL_SECTION_END POWERS_OF_EVALUATION_CHALLENGE }} + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SHPLEMINI - POWERS OF EVALUATION CHALLENGE COMPLETE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SHPLEMINI - RUNTIME MEMORY - BATCH SCALARS */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant BATCH_SCALAR_1_LOC = 0x6cc0; +uint256 internal constant BATCH_SCALAR_2_LOC = 0x6ce0; +uint256 internal constant BATCH_SCALAR_3_LOC = 0x6d00; +uint256 internal constant BATCH_SCALAR_4_LOC = 0x6d20; +uint256 internal constant BATCH_SCALAR_5_LOC = 0x6d40; +uint256 internal constant BATCH_SCALAR_6_LOC = 0x6d60; +uint256 internal constant BATCH_SCALAR_7_LOC = 0x6d80; +uint256 internal constant BATCH_SCALAR_8_LOC = 0x6da0; +uint256 internal constant BATCH_SCALAR_9_LOC = 0x6dc0; +uint256 internal constant BATCH_SCALAR_10_LOC = 0x6de0; +uint256 internal constant BATCH_SCALAR_11_LOC = 0x6e00; +uint256 internal constant BATCH_SCALAR_12_LOC = 0x6e20; +uint256 internal constant BATCH_SCALAR_13_LOC = 0x6e40; +uint256 internal constant BATCH_SCALAR_14_LOC = 0x6e60; +uint256 internal constant BATCH_SCALAR_15_LOC = 0x6e80; +uint256 internal constant BATCH_SCALAR_16_LOC = 0x6ea0; +uint256 internal constant BATCH_SCALAR_17_LOC = 0x6ec0; +uint256 internal constant BATCH_SCALAR_18_LOC = 0x6ee0; +uint256 internal constant BATCH_SCALAR_19_LOC = 0x6f00; +uint256 internal constant BATCH_SCALAR_20_LOC = 0x6f20; +uint256 internal constant BATCH_SCALAR_21_LOC = 0x6f40; +uint256 internal constant BATCH_SCALAR_22_LOC = 0x6f60; +uint256 internal constant BATCH_SCALAR_23_LOC = 0x6f80; +uint256 internal constant BATCH_SCALAR_24_LOC = 0x6fa0; +uint256 internal constant BATCH_SCALAR_25_LOC = 0x6fc0; +uint256 internal constant BATCH_SCALAR_26_LOC = 0x6fe0; +uint256 internal constant BATCH_SCALAR_27_LOC = 0x7000; +uint256 internal constant BATCH_SCALAR_28_LOC = 0x7020; +uint256 internal constant BATCH_SCALAR_29_LOC = 0x7040; +uint256 internal constant BATCH_SCALAR_30_LOC = 0x7060; +uint256 internal constant BATCH_SCALAR_31_LOC = 0x7080; +uint256 internal constant BATCH_SCALAR_32_LOC = 0x70a0; +uint256 internal constant BATCH_SCALAR_33_LOC = 0x70c0; +uint256 internal constant BATCH_SCALAR_34_LOC = 0x70e0; +uint256 internal constant BATCH_SCALAR_35_LOC = 0x7100; +uint256 internal constant BATCH_SCALAR_36_LOC = 0x7120; +uint256 internal constant BATCH_SCALAR_37_LOC = 0x7140; +uint256 internal constant BATCH_SCALAR_38_LOC = 0x7160; +uint256 internal constant BATCH_SCALAR_39_LOC = 0x7180; +uint256 internal constant BATCH_SCALAR_40_LOC = 0x71a0; +uint256 internal constant BATCH_SCALAR_41_LOC = 0x71c0; +uint256 internal constant BATCH_SCALAR_42_LOC = 0x71e0; +uint256 internal constant BATCH_SCALAR_43_LOC = 0x7200; +uint256 internal constant BATCH_SCALAR_44_LOC = 0x7220; +uint256 internal constant BATCH_SCALAR_45_LOC = 0x7240; +uint256 internal constant BATCH_SCALAR_46_LOC = 0x7260; +uint256 internal constant BATCH_SCALAR_47_LOC = 0x7280; +uint256 internal constant BATCH_SCALAR_48_LOC = 0x72a0; +uint256 internal constant BATCH_SCALAR_49_LOC = 0x72c0; +uint256 internal constant BATCH_SCALAR_50_LOC = 0x72e0; +uint256 internal constant BATCH_SCALAR_51_LOC = 0x7300; +uint256 internal constant BATCH_SCALAR_52_LOC = 0x7320; +uint256 internal constant BATCH_SCALAR_53_LOC = 0x7340; +uint256 internal constant BATCH_SCALAR_54_LOC = 0x7360; +uint256 internal constant BATCH_SCALAR_55_LOC = 0x7380; +uint256 internal constant BATCH_SCALAR_56_LOC = 0x73a0; +uint256 internal constant BATCH_SCALAR_57_LOC = 0x73c0; +uint256 internal constant BATCH_SCALAR_58_LOC = 0x73e0; +uint256 internal constant BATCH_SCALAR_59_LOC = 0x7400; +uint256 internal constant BATCH_SCALAR_60_LOC = 0x7420; +uint256 internal constant BATCH_SCALAR_61_LOC = 0x7440; +uint256 internal constant BATCH_SCALAR_62_LOC = 0x7460; +uint256 internal constant BATCH_SCALAR_63_LOC = 0x7480; +uint256 internal constant BATCH_SCALAR_64_LOC = 0x74a0; +uint256 internal constant BATCH_SCALAR_65_LOC = 0x74c0; +uint256 internal constant BATCH_SCALAR_66_LOC = 0x74e0; +uint256 internal constant BATCH_SCALAR_67_LOC = 0x7500; +uint256 internal constant BATCH_SCALAR_68_LOC = 0x7520; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SHPLEMINI - RUNTIME MEMORY - BATCH SCALARS COMPLETE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SHPLEMINI - RUNTIME MEMORY - INVERSIONS */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant GEMINI_R_INV_LOC = 0x7540; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_0_LOC = 0x7560; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_1_LOC = 0x7580; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_2_LOC = 0x75a0; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_3_LOC = 0x75c0; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_4_LOC = 0x75e0; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_5_LOC = 0x7600; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_6_LOC = 0x7620; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_7_LOC = 0x7640; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_8_LOC = 0x7660; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_9_LOC = 0x7680; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_10_LOC = 0x76a0; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_11_LOC = 0x76c0; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_12_LOC = 0x76e0; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_13_LOC = 0x7700; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_14_LOC = 0x7720; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_15_LOC = 0x7740; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_16_LOC = 0x7760; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_17_LOC = 0x7780; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_18_LOC = 0x77a0; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_19_LOC = 0x77c0; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_20_LOC = 0x77e0; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_21_LOC = 0x7800; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_22_LOC = 0x7820; +uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_23_LOC = 0x7840; + +uint256 internal constant CONSTANT_TERM_ACCUMULATOR_LOC = 0x7860; + +uint256 internal constant POS_INVERTED_DENOMINATOR = 0x7880; +uint256 internal constant NEG_INVERTED_DENOMINATOR = 0x78a0; + +// LOG_N challenge pow minus u +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_0_LOC = 0x78c0; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_1_LOC = 0x78e0; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_2_LOC = 0x7900; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_3_LOC = 0x7920; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_4_LOC = 0x7940; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_5_LOC = 0x7960; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_6_LOC = 0x7980; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_7_LOC = 0x79a0; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_8_LOC = 0x79c0; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_9_LOC = 0x79e0; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_10_LOC = 0x7a00; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_11_LOC = 0x7a20; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_12_LOC = 0x7a40; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_13_LOC = 0x7a60; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_14_LOC = 0x7a80; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_15_LOC = 0x7aa0; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_16_LOC = 0x7ac0; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_17_LOC = 0x7ae0; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_18_LOC = 0x7b00; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_19_LOC = 0x7b20; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_20_LOC = 0x7b40; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_21_LOC = 0x7b60; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_22_LOC = 0x7b80; +uint256 internal constant INVERTED_CHALLENGE_POW_MINUS_U_23_LOC = 0x7ba0; + +// LOG_N pos_inverted_off +uint256 internal constant POS_INVERTED_DENOM_0_LOC = 0x7bc0; +uint256 internal constant POS_INVERTED_DENOM_1_LOC = 0x7be0; +uint256 internal constant POS_INVERTED_DENOM_2_LOC = 0x7c00; +uint256 internal constant POS_INVERTED_DENOM_3_LOC = 0x7c20; +uint256 internal constant POS_INVERTED_DENOM_4_LOC = 0x7c40; +uint256 internal constant POS_INVERTED_DENOM_5_LOC = 0x7c60; +uint256 internal constant POS_INVERTED_DENOM_6_LOC = 0x7c80; +uint256 internal constant POS_INVERTED_DENOM_7_LOC = 0x7ca0; +uint256 internal constant POS_INVERTED_DENOM_8_LOC = 0x7cc0; +uint256 internal constant POS_INVERTED_DENOM_9_LOC = 0x7ce0; +uint256 internal constant POS_INVERTED_DENOM_10_LOC = 0x7d00; +uint256 internal constant POS_INVERTED_DENOM_11_LOC = 0x7d20; +uint256 internal constant POS_INVERTED_DENOM_12_LOC = 0x7d40; +uint256 internal constant POS_INVERTED_DENOM_13_LOC = 0x7d60; +uint256 internal constant POS_INVERTED_DENOM_14_LOC = 0x7d80; +uint256 internal constant POS_INVERTED_DENOM_15_LOC = 0x7da0; +uint256 internal constant POS_INVERTED_DENOM_16_LOC = 0x7dc0; +uint256 internal constant POS_INVERTED_DENOM_17_LOC = 0x7de0; +uint256 internal constant POS_INVERTED_DENOM_18_LOC = 0x7e00; +uint256 internal constant POS_INVERTED_DENOM_19_LOC = 0x7e20; +uint256 internal constant POS_INVERTED_DENOM_20_LOC = 0x7e40; +uint256 internal constant POS_INVERTED_DENOM_21_LOC = 0x7e60; +uint256 internal constant POS_INVERTED_DENOM_22_LOC = 0x7e80; +uint256 internal constant POS_INVERTED_DENOM_23_LOC = 0x7ea0; + +// LOG_N neg_inverted_off +uint256 internal constant NEG_INVERTED_DENOM_0_LOC = 0x7ec0; +uint256 internal constant NEG_INVERTED_DENOM_1_LOC = 0x7ee0; +uint256 internal constant NEG_INVERTED_DENOM_2_LOC = 0x7f00; +uint256 internal constant NEG_INVERTED_DENOM_3_LOC = 0x7f20; +uint256 internal constant NEG_INVERTED_DENOM_4_LOC = 0x7f40; +uint256 internal constant NEG_INVERTED_DENOM_5_LOC = 0x7f60; +uint256 internal constant NEG_INVERTED_DENOM_6_LOC = 0x7f80; +uint256 internal constant NEG_INVERTED_DENOM_7_LOC = 0x7fa0; +uint256 internal constant NEG_INVERTED_DENOM_8_LOC = 0x7fc0; +uint256 internal constant NEG_INVERTED_DENOM_9_LOC = 0x7fe0; +uint256 internal constant NEG_INVERTED_DENOM_10_LOC = 0x8000; +uint256 internal constant NEG_INVERTED_DENOM_11_LOC = 0x8020; +uint256 internal constant NEG_INVERTED_DENOM_12_LOC = 0x8040; +uint256 internal constant NEG_INVERTED_DENOM_13_LOC = 0x8060; +uint256 internal constant NEG_INVERTED_DENOM_14_LOC = 0x8080; +uint256 internal constant NEG_INVERTED_DENOM_15_LOC = 0x80a0; +uint256 internal constant NEG_INVERTED_DENOM_16_LOC = 0x80c0; +uint256 internal constant NEG_INVERTED_DENOM_17_LOC = 0x80e0; +uint256 internal constant NEG_INVERTED_DENOM_18_LOC = 0x8100; +uint256 internal constant NEG_INVERTED_DENOM_19_LOC = 0x8120; +uint256 internal constant NEG_INVERTED_DENOM_20_LOC = 0x8140; +uint256 internal constant NEG_INVERTED_DENOM_21_LOC = 0x8160; +uint256 internal constant NEG_INVERTED_DENOM_22_LOC = 0x8180; +uint256 internal constant NEG_INVERTED_DENOM_23_LOC = 0x81a0; + +uint256 internal constant FOLD_POS_EVALUATIONS_0_LOC = 0x81c0; +uint256 internal constant FOLD_POS_EVALUATIONS_1_LOC = 0x81e0; +uint256 internal constant FOLD_POS_EVALUATIONS_2_LOC = 0x8200; +uint256 internal constant FOLD_POS_EVALUATIONS_3_LOC = 0x8220; +uint256 internal constant FOLD_POS_EVALUATIONS_4_LOC = 0x8240; +uint256 internal constant FOLD_POS_EVALUATIONS_5_LOC = 0x8260; +uint256 internal constant FOLD_POS_EVALUATIONS_6_LOC = 0x8280; +uint256 internal constant FOLD_POS_EVALUATIONS_7_LOC = 0x82a0; +uint256 internal constant FOLD_POS_EVALUATIONS_8_LOC = 0x82c0; +uint256 internal constant FOLD_POS_EVALUATIONS_9_LOC = 0x82e0; +uint256 internal constant FOLD_POS_EVALUATIONS_10_LOC = 0x8300; +uint256 internal constant FOLD_POS_EVALUATIONS_11_LOC = 0x8320; +uint256 internal constant FOLD_POS_EVALUATIONS_12_LOC = 0x8340; +uint256 internal constant FOLD_POS_EVALUATIONS_13_LOC = 0x8360; +uint256 internal constant FOLD_POS_EVALUATIONS_14_LOC = 0x8380; +uint256 internal constant FOLD_POS_EVALUATIONS_15_LOC = 0x83a0; +uint256 internal constant FOLD_POS_EVALUATIONS_16_LOC = 0x83c0; +uint256 internal constant FOLD_POS_EVALUATIONS_17_LOC = 0x83e0; +uint256 internal constant FOLD_POS_EVALUATIONS_18_LOC = 0x8400; +uint256 internal constant FOLD_POS_EVALUATIONS_19_LOC = 0x8420; +uint256 internal constant FOLD_POS_EVALUATIONS_20_LOC = 0x8440; +uint256 internal constant FOLD_POS_EVALUATIONS_21_LOC = 0x8460; +uint256 internal constant FOLD_POS_EVALUATIONS_22_LOC = 0x8480; +uint256 internal constant FOLD_POS_EVALUATIONS_23_LOC = 0x84a0; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SHPLEMINI RUNTIME MEMORY - INVERSIONS - COMPLETE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SHPLEMINI RUNTIME MEMORY - COMPLETE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +uint256 internal constant BARYCENTRIC_TEMP_0_LOC = 0x84c0; +uint256 internal constant BARYCENTRIC_TEMP_1_LOC = 0x84e0; +uint256 internal constant BARYCENTRIC_TEMP_2_LOC = 0x8500; +uint256 internal constant BARYCENTRIC_TEMP_3_LOC = 0x8520; +uint256 internal constant BARYCENTRIC_TEMP_4_LOC = 0x8540; +uint256 internal constant BARYCENTRIC_TEMP_5_LOC = 0x8560; +uint256 internal constant BARYCENTRIC_TEMP_6_LOC = 0x8580; +uint256 internal constant BARYCENTRIC_TEMP_7_LOC = 0x85a0; +uint256 internal constant BARYCENTRIC_TEMP_8_LOC = 0x85c0; +uint256 internal constant BARYCENTRIC_TEMP_9_LOC = 0x85e0; +uint256 internal constant BARYCENTRIC_TEMP_10_LOC = 0x8600; +uint256 internal constant BARYCENTRIC_TEMP_11_LOC = 0x8620; +uint256 internal constant BARYCENTRIC_TEMP_12_LOC = 0x8640; +uint256 internal constant BARYCENTRIC_TEMP_13_LOC = 0x8660; +uint256 internal constant BARYCENTRIC_TEMP_14_LOC = 0x8680; +uint256 internal constant BARYCENTRIC_TEMP_15_LOC = 0x86a0; +uint256 internal constant BARYCENTRIC_TEMP_16_LOC = 0x86c0; +uint256 internal constant BARYCENTRIC_TEMP_17_LOC = 0x86e0; +uint256 internal constant BARYCENTRIC_TEMP_18_LOC = 0x8700; +uint256 internal constant BARYCENTRIC_TEMP_19_LOC = 0x8720; +uint256 internal constant BARYCENTRIC_TEMP_20_LOC = 0x8740; +uint256 internal constant BARYCENTRIC_TEMP_21_LOC = 0x8760; +uint256 internal constant BARYCENTRIC_TEMP_22_LOC = 0x8780; +uint256 internal constant BARYCENTRIC_TEMP_23_LOC = 0x87a0; +uint256 internal constant BARYCENTRIC_TEMP_24_LOC = 0x87c0; +uint256 internal constant BARYCENTRIC_TEMP_25_LOC = 0x87e0; +uint256 internal constant BARYCENTRIC_TEMP_26_LOC = 0x8800; +uint256 internal constant BARYCENTRIC_TEMP_27_LOC = 0x8820; +uint256 internal constant BARYCENTRIC_TEMP_28_LOC = 0x8840; +uint256 internal constant BARYCENTRIC_TEMP_29_LOC = 0x8860; +uint256 internal constant BARYCENTRIC_TEMP_30_LOC = 0x8880; +uint256 internal constant BARYCENTRIC_TEMP_31_LOC = 0x88a0; +uint256 internal constant BARYCENTRIC_TEMP_32_LOC = 0x88c0; +uint256 internal constant BARYCENTRIC_TEMP_33_LOC = 0x88e0; +uint256 internal constant BARYCENTRIC_TEMP_34_LOC = 0x8900; +uint256 internal constant BARYCENTRIC_TEMP_35_LOC = 0x8920; +uint256 internal constant BARYCENTRIC_TEMP_36_LOC = 0x8940; +uint256 internal constant BARYCENTRIC_TEMP_37_LOC = 0x8960; +uint256 internal constant BARYCENTRIC_TEMP_38_LOC = 0x8980; +uint256 internal constant BARYCENTRIC_TEMP_39_LOC = 0x89a0; +uint256 internal constant BARYCENTRIC_TEMP_40_LOC = 0x89c0; +uint256 internal constant BARYCENTRIC_TEMP_41_LOC = 0x89e0; +uint256 internal constant BARYCENTRIC_TEMP_42_LOC = 0x8a00; +uint256 internal constant BARYCENTRIC_TEMP_43_LOC = 0x8a20; +uint256 internal constant BARYCENTRIC_TEMP_44_LOC = 0x8a40; +uint256 internal constant BARYCENTRIC_TEMP_45_LOC = 0x8a60; +uint256 internal constant BARYCENTRIC_TEMP_46_LOC = 0x8a80; +uint256 internal constant BARYCENTRIC_TEMP_47_LOC = 0x8aa0; +uint256 internal constant BARYCENTRIC_TEMP_48_LOC = 0x8ac0; +uint256 internal constant BARYCENTRIC_TEMP_49_LOC = 0x8ae0; +uint256 internal constant BARYCENTRIC_TEMP_50_LOC = 0x8b00; +uint256 internal constant BARYCENTRIC_TEMP_51_LOC = 0x8b20; +uint256 internal constant BARYCENTRIC_TEMP_52_LOC = 0x8b40; +uint256 internal constant BARYCENTRIC_TEMP_53_LOC = 0x8b60; +uint256 internal constant BARYCENTRIC_TEMP_54_LOC = 0x8b80; +uint256 internal constant BARYCENTRIC_TEMP_55_LOC = 0x8ba0; +uint256 internal constant BARYCENTRIC_TEMP_56_LOC = 0x8bc0; +uint256 internal constant BARYCENTRIC_TEMP_57_LOC = 0x8be0; +uint256 internal constant BARYCENTRIC_TEMP_58_LOC = 0x8c00; +uint256 internal constant BARYCENTRIC_TEMP_59_LOC = 0x8c20; +uint256 internal constant BARYCENTRIC_TEMP_60_LOC = 0x8c40; +uint256 internal constant BARYCENTRIC_TEMP_61_LOC = 0x8c60; +uint256 internal constant BARYCENTRIC_TEMP_62_LOC = 0x8c80; +uint256 internal constant BARYCENTRIC_TEMP_63_LOC = 0x8ca0; +uint256 internal constant BARYCENTRIC_TEMP_64_LOC = 0x8cc0; +uint256 internal constant BARYCENTRIC_TEMP_65_LOC = 0x8ce0; +uint256 internal constant BARYCENTRIC_TEMP_66_LOC = 0x8d00; +uint256 internal constant BARYCENTRIC_TEMP_67_LOC = 0x8d20; +uint256 internal constant BARYCENTRIC_TEMP_68_LOC = 0x8d40; +uint256 internal constant BARYCENTRIC_TEMP_69_LOC = 0x8d60; +uint256 internal constant BARYCENTRIC_TEMP_70_LOC = 0x8d80; +uint256 internal constant BARYCENTRIC_TEMP_71_LOC = 0x8da0; +uint256 internal constant BARYCENTRIC_TEMP_72_LOC = 0x8dc0; +uint256 internal constant BARYCENTRIC_TEMP_73_LOC = 0x8de0; +uint256 internal constant BARYCENTRIC_TEMP_74_LOC = 0x8e00; +uint256 internal constant BARYCENTRIC_TEMP_75_LOC = 0x8e20; +uint256 internal constant BARYCENTRIC_TEMP_76_LOC = 0x8e40; +uint256 internal constant BARYCENTRIC_TEMP_77_LOC = 0x8e60; +uint256 internal constant BARYCENTRIC_TEMP_78_LOC = 0x8e80; +uint256 internal constant BARYCENTRIC_TEMP_79_LOC = 0x8ea0; +uint256 internal constant BARYCENTRIC_TEMP_80_LOC = 0x8ec0; +uint256 internal constant BARYCENTRIC_TEMP_81_LOC = 0x8ee0; +uint256 internal constant BARYCENTRIC_TEMP_82_LOC = 0x8f00; +uint256 internal constant BARYCENTRIC_TEMP_83_LOC = 0x8f20; +uint256 internal constant BARYCENTRIC_TEMP_84_LOC = 0x8f40; +uint256 internal constant BARYCENTRIC_TEMP_85_LOC = 0x8f60; +uint256 internal constant BARYCENTRIC_TEMP_86_LOC = 0x8f80; +uint256 internal constant BARYCENTRIC_TEMP_87_LOC = 0x8fa0; +uint256 internal constant BARYCENTRIC_TEMP_88_LOC = 0x8fc0; +uint256 internal constant BARYCENTRIC_TEMP_89_LOC = 0x8fe0; +uint256 internal constant BARYCENTRIC_TEMP_90_LOC = 0x9000; +uint256 internal constant BARYCENTRIC_TEMP_91_LOC = 0x9020; +uint256 internal constant BARYCENTRIC_TEMP_92_LOC = 0x9040; +uint256 internal constant BARYCENTRIC_TEMP_93_LOC = 0x9060; +uint256 internal constant BARYCENTRIC_TEMP_94_LOC = 0x9080; +uint256 internal constant BARYCENTRIC_TEMP_95_LOC = 0x90a0; +uint256 internal constant BARYCENTRIC_TEMP_96_LOC = 0x90c0; +uint256 internal constant BARYCENTRIC_TEMP_97_LOC = 0x90e0; +uint256 internal constant BARYCENTRIC_TEMP_98_LOC = 0x9100; +uint256 internal constant BARYCENTRIC_TEMP_99_LOC = 0x9120; +uint256 internal constant BARYCENTRIC_TEMP_100_LOC = 0x9140; +uint256 internal constant BARYCENTRIC_TEMP_101_LOC = 0x9160; +uint256 internal constant BARYCENTRIC_TEMP_102_LOC = 0x9180; +uint256 internal constant BARYCENTRIC_TEMP_103_LOC = 0x91a0; +uint256 internal constant BARYCENTRIC_TEMP_104_LOC = 0x91c0; +uint256 internal constant BARYCENTRIC_TEMP_105_LOC = 0x91e0; +uint256 internal constant BARYCENTRIC_TEMP_106_LOC = 0x9200; +uint256 internal constant BARYCENTRIC_TEMP_107_LOC = 0x9220; +uint256 internal constant BARYCENTRIC_TEMP_108_LOC = 0x9240; +uint256 internal constant BARYCENTRIC_TEMP_109_LOC = 0x9260; +uint256 internal constant BARYCENTRIC_TEMP_110_LOC = 0x9280; +uint256 internal constant BARYCENTRIC_TEMP_111_LOC = 0x92a0; +uint256 internal constant BARYCENTRIC_TEMP_112_LOC = 0x92c0; +uint256 internal constant BARYCENTRIC_TEMP_113_LOC = 0x92e0; +uint256 internal constant BARYCENTRIC_TEMP_114_LOC = 0x9300; +uint256 internal constant BARYCENTRIC_TEMP_115_LOC = 0x9320; +uint256 internal constant BARYCENTRIC_TEMP_116_LOC = 0x9340; +uint256 internal constant BARYCENTRIC_TEMP_117_LOC = 0x9360; +uint256 internal constant BARYCENTRIC_TEMP_118_LOC = 0x9380; +uint256 internal constant BARYCENTRIC_TEMP_119_LOC = 0x93a0; +uint256 internal constant BARYCENTRIC_TEMP_120_LOC = 0x93c0; +uint256 internal constant BARYCENTRIC_TEMP_121_LOC = 0x93e0; +uint256 internal constant BARYCENTRIC_TEMP_122_LOC = 0x9400; +uint256 internal constant BARYCENTRIC_TEMP_123_LOC = 0x9420; +uint256 internal constant BARYCENTRIC_TEMP_124_LOC = 0x9440; +uint256 internal constant BARYCENTRIC_TEMP_125_LOC = 0x9460; +uint256 internal constant BARYCENTRIC_TEMP_126_LOC = 0x9480; +uint256 internal constant BARYCENTRIC_TEMP_127_LOC = 0x94a0; +uint256 internal constant BARYCENTRIC_TEMP_128_LOC = 0x94c0; +uint256 internal constant BARYCENTRIC_TEMP_129_LOC = 0x94e0; +uint256 internal constant BARYCENTRIC_TEMP_130_LOC = 0x9500; +uint256 internal constant BARYCENTRIC_TEMP_131_LOC = 0x9520; +uint256 internal constant BARYCENTRIC_TEMP_132_LOC = 0x9540; +uint256 internal constant BARYCENTRIC_TEMP_133_LOC = 0x9560; +uint256 internal constant BARYCENTRIC_TEMP_134_LOC = 0x9580; +uint256 internal constant BARYCENTRIC_TEMP_135_LOC = 0x95a0; +uint256 internal constant BARYCENTRIC_TEMP_136_LOC = 0x95c0; +uint256 internal constant BARYCENTRIC_TEMP_137_LOC = 0x95e0; +uint256 internal constant BARYCENTRIC_TEMP_138_LOC = 0x9600; +uint256 internal constant BARYCENTRIC_TEMP_139_LOC = 0x9620; +uint256 internal constant BARYCENTRIC_TEMP_140_LOC = 0x9640; +uint256 internal constant BARYCENTRIC_TEMP_141_LOC = 0x9660; +uint256 internal constant BARYCENTRIC_TEMP_142_LOC = 0x9680; +uint256 internal constant BARYCENTRIC_TEMP_143_LOC = 0x96a0; +uint256 internal constant BARYCENTRIC_TEMP_144_LOC = 0x96c0; +uint256 internal constant BARYCENTRIC_TEMP_145_LOC = 0x96e0; +uint256 internal constant BARYCENTRIC_TEMP_146_LOC = 0x9700; +uint256 internal constant BARYCENTRIC_TEMP_147_LOC = 0x9720; +uint256 internal constant BARYCENTRIC_TEMP_148_LOC = 0x9740; +uint256 internal constant BARYCENTRIC_TEMP_149_LOC = 0x9760; +uint256 internal constant BARYCENTRIC_TEMP_150_LOC = 0x9780; +uint256 internal constant BARYCENTRIC_TEMP_151_LOC = 0x97a0; +uint256 internal constant BARYCENTRIC_TEMP_152_LOC = 0x97c0; +uint256 internal constant BARYCENTRIC_TEMP_153_LOC = 0x97e0; +uint256 internal constant BARYCENTRIC_TEMP_154_LOC = 0x9800; +uint256 internal constant BARYCENTRIC_TEMP_155_LOC = 0x9820; +uint256 internal constant BARYCENTRIC_TEMP_156_LOC = 0x9840; +uint256 internal constant BARYCENTRIC_TEMP_157_LOC = 0x9860; +uint256 internal constant BARYCENTRIC_TEMP_158_LOC = 0x9880; +uint256 internal constant BARYCENTRIC_TEMP_159_LOC = 0x98a0; +uint256 internal constant BARYCENTRIC_TEMP_160_LOC = 0x98c0; +uint256 internal constant BARYCENTRIC_TEMP_161_LOC = 0x98e0; +uint256 internal constant BARYCENTRIC_TEMP_162_LOC = 0x9900; +uint256 internal constant BARYCENTRIC_TEMP_163_LOC = 0x9920; +uint256 internal constant BARYCENTRIC_TEMP_164_LOC = 0x9940; +uint256 internal constant BARYCENTRIC_TEMP_165_LOC = 0x9960; +uint256 internal constant BARYCENTRIC_TEMP_166_LOC = 0x9980; +uint256 internal constant BARYCENTRIC_TEMP_167_LOC = 0x99a0; +uint256 internal constant BARYCENTRIC_TEMP_168_LOC = 0x99c0; +uint256 internal constant BARYCENTRIC_TEMP_169_LOC = 0x99e0; +uint256 internal constant BARYCENTRIC_TEMP_170_LOC = 0x9a00; +uint256 internal constant BARYCENTRIC_TEMP_171_LOC = 0x9a20; +uint256 internal constant BARYCENTRIC_TEMP_172_LOC = 0x9a40; +uint256 internal constant BARYCENTRIC_TEMP_173_LOC = 0x9a60; +uint256 internal constant BARYCENTRIC_TEMP_174_LOC = 0x9a80; +uint256 internal constant BARYCENTRIC_TEMP_175_LOC = 0x9aa0; +uint256 internal constant BARYCENTRIC_TEMP_176_LOC = 0x9ac0; +uint256 internal constant BARYCENTRIC_TEMP_177_LOC = 0x9ae0; +uint256 internal constant BARYCENTRIC_TEMP_178_LOC = 0x9b00; +uint256 internal constant BARYCENTRIC_TEMP_179_LOC = 0x9b20; +uint256 internal constant BARYCENTRIC_TEMP_180_LOC = 0x9b40; +uint256 internal constant BARYCENTRIC_TEMP_181_LOC = 0x9b60; +uint256 internal constant BARYCENTRIC_TEMP_182_LOC = 0x9b80; +uint256 internal constant BARYCENTRIC_TEMP_183_LOC = 0x9ba0; +uint256 internal constant BARYCENTRIC_TEMP_184_LOC = 0x9bc0; +uint256 internal constant BARYCENTRIC_TEMP_185_LOC = 0x9be0; +uint256 internal constant BARYCENTRIC_TEMP_186_LOC = 0x9c00; +uint256 internal constant BARYCENTRIC_TEMP_187_LOC = 0x9c20; +uint256 internal constant BARYCENTRIC_TEMP_188_LOC = 0x9c40; +uint256 internal constant BARYCENTRIC_TEMP_189_LOC = 0x9c60; +uint256 internal constant BARYCENTRIC_TEMP_190_LOC = 0x9c80; +uint256 internal constant BARYCENTRIC_TEMP_191_LOC = 0x9ca0; +uint256 internal constant PUBLIC_INPUTS_DENOM_TEMP_LOC = 0x9cc0; +uint256 internal constant GEMINI_R_INV_TEMP_LOC = 0x9ce0; +uint256 internal constant BATCH_PRODUCT_TEMP_LOC = 0x9d00; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* Temporary space */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +uint256 internal constant TEMP_0_LOC = 0x9d20; +uint256 internal constant TEMP_1_LOC = 0x9d40; +uint256 internal constant TEMP_2_LOC = 0x9d60; +uint256 internal constant TEMP_3_LOC = 0x9d80; +uint256 internal constant TEMP_4_LOC = 0x9da0; +uint256 internal constant TEMP_5_LOC = 0x9dc0; +uint256 internal constant TEMP_6_LOC = 0x9de0; +uint256 internal constant TEMP_7_LOC = 0x9e00; +uint256 internal constant TEMP_8_LOC = 0x9e20; +uint256 internal constant TEMP_9_LOC = 0x9e40; +uint256 internal constant TEMP_10_LOC = 0x9e60; +uint256 internal constant TEMP_11_LOC = 0x9e80; +uint256 internal constant TEMP_12_LOC = 0x9ea0; +uint256 internal constant TEMP_13_LOC = 0x9ec0; +uint256 internal constant TEMP_14_LOC = 0x9ee0; +uint256 internal constant TEMP_15_LOC = 0x9f00; +uint256 internal constant TEMP_16_LOC = 0x9f20; +uint256 internal constant TEMP_17_LOC = 0x9f40; +uint256 internal constant TEMP_18_LOC = 0x9f60; +uint256 internal constant TEMP_19_LOC = 0x9f80; +uint256 internal constant TEMP_20_LOC = 0x9fa0; +uint256 internal constant TEMP_21_LOC = 0x9fc0; +uint256 internal constant TEMP_22_LOC = 0x9fe0; +uint256 internal constant TEMP_23_LOC = 0xa000; +uint256 internal constant TEMP_24_LOC = 0xa020; +uint256 internal constant TEMP_25_LOC = 0xa040; +uint256 internal constant TEMP_26_LOC = 0xa060; +uint256 internal constant TEMP_27_LOC = 0xa080; +uint256 internal constant TEMP_28_LOC = 0xa0a0; +uint256 internal constant TEMP_29_LOC = 0xa0c0; +uint256 internal constant TEMP_30_LOC = 0xa0e0; +uint256 internal constant TEMP_31_LOC = 0xa100; +uint256 internal constant TEMP_32_LOC = 0xa120; +uint256 internal constant TEMP_33_LOC = 0xa140; +uint256 internal constant TEMP_34_LOC = 0xa160; +uint256 internal constant TEMP_35_LOC = 0xa180; +uint256 internal constant TEMP_36_LOC = 0xa1a0; +uint256 internal constant TEMP_37_LOC = 0xa1c0; +uint256 internal constant TEMP_38_LOC = 0xa1e0; +uint256 internal constant TEMP_39_LOC = 0xa200; +uint256 internal constant TEMP_40_LOC = 0xa220; +uint256 internal constant TEMP_41_LOC = 0xa240; +uint256 internal constant TEMP_42_LOC = 0xa260; +uint256 internal constant TEMP_43_LOC = 0xa280; +uint256 internal constant TEMP_44_LOC = 0xa2a0; +uint256 internal constant TEMP_45_LOC = 0xa2c0; +uint256 internal constant TEMP_46_LOC = 0xa2e0; +uint256 internal constant TEMP_47_LOC = 0xa300; +uint256 internal constant TEMP_48_LOC = 0xa320; +uint256 internal constant TEMP_49_LOC = 0xa340; +uint256 internal constant TEMP_50_LOC = 0xa360; +uint256 internal constant TEMP_51_LOC = 0xa380; +uint256 internal constant TEMP_52_LOC = 0xa3a0; +uint256 internal constant TEMP_53_LOC = 0xa3c0; +uint256 internal constant TEMP_54_LOC = 0xa3e0; +uint256 internal constant TEMP_55_LOC = 0xa400; +uint256 internal constant TEMP_56_LOC = 0xa420; +uint256 internal constant TEMP_57_LOC = 0xa440; +uint256 internal constant TEMP_58_LOC = 0xa460; +uint256 internal constant TEMP_59_LOC = 0xa480; +uint256 internal constant TEMP_60_LOC = 0xa4a0; +uint256 internal constant TEMP_61_LOC = 0xa4c0; +uint256 internal constant TEMP_62_LOC = 0xa4e0; +uint256 internal constant TEMP_63_LOC = 0xa500; +uint256 internal constant TEMP_64_LOC = 0xa520; +uint256 internal constant TEMP_65_LOC = 0xa540; +uint256 internal constant TEMP_66_LOC = 0xa560; +uint256 internal constant TEMP_67_LOC = 0xa580; +uint256 internal constant TEMP_68_LOC = 0xa5a0; +uint256 internal constant TEMP_69_LOC = 0xa5c0; +uint256 internal constant TEMP_70_LOC = 0xa5e0; +uint256 internal constant TEMP_71_LOC = 0xa600; +uint256 internal constant LATER_SCRATCH_SPACE = 0xa620; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* Temporary space - COMPLETE */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +// Aliases for scratch space +// Scratch space aliases at 0x00-0x40 +// Phase 1 (sumcheck rounds): CHALL_POW_LOC, SUMCHECK_U_LOC, GEMINI_A_LOC +// Phase 2 (shplemini batch scalars): SS_POS_INV_DENOM_LOC, SS_NEG_INV_DENOM_LOC, SS_GEMINI_EVALS_LOC +// These phases do not overlap in execution time. +uint256 internal constant CHALL_POW_LOC = 0; +uint256 internal constant SUMCHECK_U_LOC = 0x20; +uint256 internal constant GEMINI_A_LOC = 0x40; + +uint256 internal constant SS_POS_INV_DENOM_LOC = 0; +uint256 internal constant SS_NEG_INV_DENOM_LOC = 0x20; +uint256 internal constant SS_GEMINI_EVALS_LOC = 0x40; + + + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* SUMCHECK - MEMORY ALIASES */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ +// {{ SECTION_END MEMORY_LAYOUT }} + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SUMCHECK - MEMORY ALIASES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + uint256 internal constant EC_X_1 = W2_EVAL_LOC; + uint256 internal constant EC_Y_1 = W3_EVAL_LOC; + uint256 internal constant EC_X_2 = W1_SHIFT_EVAL_LOC; + uint256 internal constant EC_Y_2 = W4_SHIFT_EVAL_LOC; + uint256 internal constant EC_Y_3 = W3_SHIFT_EVAL_LOC; + uint256 internal constant EC_X_3 = W2_SHIFT_EVAL_LOC; + + // Aliases for selectors (Elliptic curve gadget) + uint256 internal constant EC_Q_SIGN = QL_EVAL_LOC; + uint256 internal constant EC_Q_IS_DOUBLE = QM_EVAL_LOC; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + uint256 internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = 17; // -(-17) + + // Auxiliary relation constants + // In the Non Native Field Arithmetic Relation, large field elements are broken up into 4 LIMBs of 68 `LIMB_SIZE` bits each. + uint256 internal constant LIMB_SIZE = 0x100000000000000000; // 1<<68 + + // In the Delta Range Check Relation, there is a range checking relation that can validate 14-bit range checks with only 1 + // extra relation in the execution trace. + // For large range checks, we decompose them into a collection of 14-bit range checks. + uint256 internal constant SUBLIMB_SHIFT = 0x4000; // 1<<14 + + // Poseidon2 internal constants + // https://github.com/HorizenLabs/poseidon2/blob/main/poseidon2_rust_params.sage - derivation code + uint256 internal constant POS_INTERNAL_MATRIX_D_0 = + 0x10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7; + uint256 internal constant POS_INTERNAL_MATRIX_D_1 = + 0x0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740b; + uint256 internal constant POS_INTERNAL_MATRIX_D_2 = + 0x00544b8338791518b2c7645a50392798b21f75bb60e3596170067d00141cac15; + uint256 internal constant POS_INTERNAL_MATRIX_D_3 = + 0x222c01175718386f2e2e82eb122789e352e105a3b8fa852613bc534433ee428b; + + // Constants inspecting proof components + uint256 internal constant NUMBER_OF_UNSHIFTED_ENTITIES = 36; + // Shifted columns are columns that are duplicates of existing columns but right-shifted by 1 + uint256 internal constant NUMBER_OF_SHIFTED_ENTITIES = 5; + uint256 internal constant TOTAL_NUMBER_OF_ENTITIES = 41; + + // Constants for performing batch multiplication + uint256 internal constant ACCUMULATOR = 0x00; + uint256 internal constant ACCUMULATOR_2 = 0x40; + uint256 internal constant G1_LOCATION = 0x60; + uint256 internal constant G1_Y_LOCATION = 0x80; + uint256 internal constant SCALAR_LOCATION = 0xa0; + + // Group order + uint256 internal constant Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; // EC group order + + // Field order constants + // -1/2 mod p + uint256 internal constant NEG_HALF_MODULO_P = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000; + uint256 internal constant P = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + uint256 internal constant P_SUB_1 = 21888242871839275222246405745257275088548364400416034343698204186575808495616; + uint256 internal constant P_SUB_2 = 21888242871839275222246405745257275088548364400416034343698204186575808495615; + uint256 internal constant P_SUB_3 = 21888242871839275222246405745257275088548364400416034343698204186575808495614; + + // Barycentric evaluation constants + uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_0 = + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51; + uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_1 = + 0x00000000000000000000000000000000000000000000000000000000000002d0; + uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_2 = + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffff11; + uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_3 = + 0x0000000000000000000000000000000000000000000000000000000000000090; + uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_4 = + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffff71; + uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_5 = + 0x00000000000000000000000000000000000000000000000000000000000000f0; + uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_6 = + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31; + uint256 internal constant BARYCENTRIC_LAGRANGE_DENOMINATOR_7 = + 0x00000000000000000000000000000000000000000000000000000000000013b0; + + // Constants for computing public input delta + uint256 internal constant PERMUTATION_ARGUMENT_VALUE_SEPARATOR = 1 << 28; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // The errors match Errors.sol + + bytes4 internal constant VALUE_GE_LIMB_MAX_SELECTOR = 0xeb73e0bd; + bytes4 internal constant VALUE_GE_GROUP_ORDER_SELECTOR = 0x607be13e; + bytes4 internal constant VALUE_GE_FIELD_ORDER_SELECTOR = 0x20a33589; + bytes4 internal constant SUMCHECK_FAILED_SELECTOR = 0x9fc3a218; + bytes4 internal constant SHPLEMINI_FAILED_SELECTOR = 0xa5d82e8a; + + bytes4 internal constant PROOF_LENGTH_WRONG_WITH_LOG_N_SELECTOR = 0x59895a53; + bytes4 internal constant PUBLIC_INPUTS_LENGTH_WRONG_SELECTOR = 0xfa066593; + + bytes4 internal constant MODEXP_FAILED_SELECTOR = 0xf442f163; + + constructor() {} + + function verify( + bytes calldata, + /*proof*/ + bytes32[] calldata /*public_inputs*/ + ) + public + view + override + returns (bool) + { + // Load the proof from calldata in one large chunk + assembly { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* LOAD VERIFCATION KEY */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Write the verification key into memory + // + // Although defined at the top of the file, it is used towards the end of the algorithm when batching in the commitment scheme. + function loadVk() { + mstore(Q_L_X_LOC, 0x2a20634fb671a5edfb3cdc996b05a5e7a4e470ae3e41ccc967e91626a9f25a3f) + mstore(Q_L_Y_LOC, 0x0675a8a69c714584713af63efe118ccfa472f416071ba9b5982e6008aa8b0ab9) + mstore(Q_R_X_LOC, 0x06ae980612e087026abfd4522bd7d01e6224c1f979d5a71b4dc1280381590bf9) + mstore(Q_R_Y_LOC, 0x0adf8aa1a792462a4535ebd7a3e8a8c9cd16b8b5984544ee3fe48f36076b90ca) + mstore(Q_O_X_LOC, 0x195326da0abf4499318af33bde785811b1025186a90a4939ee53b66b3ea04778) + mstore(Q_O_Y_LOC, 0x15018a43f05435e3b1cdddf0f6e5cfee2d72846f18980ee99bb2ac179c2fcf48) + mstore(Q_4_X_LOC, 0x0b335c3a0e885c2e895d9067e3cf90a50c3524db8a6509516b9a7c7c27878914) + mstore(Q_4_Y_LOC, 0x21d71464b06460b258d31bcde415ad06d4467bbdbca84ed1040170e85e8b60f2) + mstore(Q_M_X_LOC, 0x1235ad3b3a0d7a7c358df22616446d1a97c5a6e69724c18905d5981c8d3f6701) + mstore(Q_M_Y_LOC, 0x2cc8ef0f111956537f62279f3342465d5c9e4c088a9bb0a607e8c00ff18da3a0) + mstore(Q_C_X_LOC, 0x20a4018eaecd5e0ba6eef5333083286d698c61822678a9489f9d8c8f39730d3b) + mstore(Q_C_Y_LOC, 0x207d47c75e7361551743f1ad004172071828d76ba8fa2a594db5ec964147f3fd) + mstore(Q_LOOKUP_X_LOC, 0x2b477d547c3ddb457c91a7b4af29546964ae01a320007d46d5a04cf30f61574a) + mstore(Q_LOOKUP_Y_LOC, 0x1f6e473e88cec3a77d034eb9d8f611b697a7bd90ab288dc848cf176d0dcdb9d9) + mstore(Q_ARITH_X_LOC, 0x1912b6c4485278fa8916feaf30204f8e284a65c5c9c054e6e30b1a73c2e35660) + mstore(Q_ARITH_Y_LOC, 0x25059189f22c003a21486840b7b84002157cc31f9912b8b200fa38e86c6f6a71) + mstore(Q_DELTA_RANGE_X_LOC, 0x238d6feb6f925d229c371cc69305cbd1d60f4252463cd92664041a9954e99a54) + mstore(Q_DELTA_RANGE_Y_LOC, 0x2dd2baef816544951966b72806e98ca5f0f6937b405308d2ca527ad94a72b87a) + mstore(Q_ELLIPTIC_X_LOC, 0x1cdda711c21ed600ffc7b40e26bf351500e9639959f2011c8469e94b76bb40c5) + mstore(Q_ELLIPTIC_Y_LOC, 0x247b749d402b7d2db4345d2324aba472d47e34be7657214ad9c8263ab09ef397) + mstore(Q_MEMORY_X_LOC, 0x19385e067fd4125167218ea48eb5b1771a9c6eed481c65498b9154316c91e4b4) + mstore(Q_MEMORY_Y_LOC, 0x0d1b2289176f885f20eeac8f55688e9f76475cacf48fc210c561e9cbfc3acc98) + mstore(Q_NNF_X_LOC, 0x010758b9c62d9f8d5bf8480711548ab4840d68c6190d9657741f67f440083ab5) + mstore(Q_NNF_Y_LOC, 0x14e0e26a0b91410ce2b794679cb1d1f5a3db4adc71b2abe5a6b280d46d7ec77e) + mstore(Q_POSEIDON_2_EXTERNAL_X_LOC, 0x0a12771886836c7f662e8950f623bffc17dbf50b2141ac85a288d3cdfa33c2c8) + mstore(Q_POSEIDON_2_EXTERNAL_Y_LOC, 0x1308e7ee41860d649227b000edb38addd755e7ac3cd1cd82426d7374f6c75836) + mstore(Q_POSEIDON_2_INTERNAL_X_LOC, 0x2d6182ea0a8a198063a0e3aa95ce72b23a8072e47bc312cadfd1cbccabaec5c3) + mstore(Q_POSEIDON_2_INTERNAL_Y_LOC, 0x087d0e5cf96b13c6bbf1e4027db97b54cbd3dd69d18a67ef9149e9558822c0bd) + mstore(SIGMA_1_X_LOC, 0x0eb3cef48da51a6a28618929e73ec1892b01b3e071e38ce4e72ecb8313b748c0) + mstore(SIGMA_1_Y_LOC, 0x08df69fa54612a307b17287d42449bf3f2b290c679b549b3ad26d07426001285) + mstore(SIGMA_2_X_LOC, 0x219b8ad03c08669d3e92d421f0800bc194a6d401ba64e4feae528ebc433a6cce) + mstore(SIGMA_2_Y_LOC, 0x1eb7d410d20db0fe0660e9864c75484d091945ad80b7c7f9bd0dbe9e07b920a4) + mstore(SIGMA_3_X_LOC, 0x10d27b952900f7d6d59c0f4c995fd93c9af709d68bf03cc14c1a5e41e31b3a61) + mstore(SIGMA_3_Y_LOC, 0x0ceaa2e84b1a50abb00aae785b08031bff72af369406c84513d248f67256d66d) + mstore(SIGMA_4_X_LOC, 0x254283816eed94b837e8b971e2e42f23250049df2737d64993b188c37edeb4ea) + mstore(SIGMA_4_Y_LOC, 0x17544718d2f0b36bbec7aae7c9925f91ca50529605fd9a1fcd87c0fd3baaecd1) + mstore(TABLE_1_X_LOC, 0x184c7c4d484bf7e9d86c70daa6e050a733fbeb942c3dfac869f47e5cb8b7dbb9) + mstore(TABLE_1_Y_LOC, 0x001a0eda86c3a1b7e8ba53c15c74590ffe1437c0fa0fd319250a9af06c913e1e) + mstore(TABLE_2_X_LOC, 0x13c1163099f5336d03053ed16ef20e094034d8264734fe5c5f2a7b3683909d85) + mstore(TABLE_2_Y_LOC, 0x1c161aee5588dcac7670f9b109082efd0168a6428895ad41643c63754e410738) + mstore(TABLE_3_X_LOC, 0x299a70b163f1247944d21d7b36767d90daa642af69eca0075ac266554a2997a9) + mstore(TABLE_3_Y_LOC, 0x0f4ef9a289f0c1d1624f4c4d5829e1f2ce9d1081c25857c603a29a7294669c05) + mstore(TABLE_4_X_LOC, 0x11d6adc448da42fd14898b159f4c0ab5540eb7c5265520a70048617aa23b9836) + mstore(TABLE_4_Y_LOC, 0x174ed47c5e56aaefb1e4a5e458f5963c68efefa4beed51062007bed5d31de344) + mstore(ID_1_X_LOC, 0x1a90a04aa472cde8811fa0e3849345cf45722da9abea0503e9d0658c1d15be24) + mstore(ID_1_Y_LOC, 0x22955dfca4fcd7ad90a83cf8bfa4eae9e84fcd16f6a3175618386a310026b504) + mstore(ID_2_X_LOC, 0x0f135a279ae6eb7d3e324cf05561d3ec988050920f935f6a2d403274269399d6) + mstore(ID_2_Y_LOC, 0x12d7ee8440970136cded169d1caeed260dc5165c2c1eb95e8503312cee5eb820) + mstore(ID_3_X_LOC, 0x28d89f3de17dd3a71b26f28ffdc81da532b2ad0f1e68dd05247b603e426b50cb) + mstore(ID_3_Y_LOC, 0x04c7e454f75253d05f6c028bf328da75c647d27b56e93c0007aee3bb7feb81a7) + mstore(ID_4_X_LOC, 0x04b375ce773a20b233408c820f9dfc320f48b85c95cd34ef4bd96b0bf4cfd1f4) + mstore(ID_4_Y_LOC, 0x193b49fb47b125cdc406dcb2abf8c7d5a521a9e2dcb2731135cedcec9bf68854) + mstore(LAGRANGE_FIRST_X_LOC, 0x2a56ce41f6b0be13b9c26747621b821eee81b23a887f299049b14c11e98460d6) + mstore(LAGRANGE_FIRST_Y_LOC, 0x1aa98f2de3ddda547d8f6de4e725ded5827d6338c78656c0d12ca1aea6ef2c7c) + mstore(LAGRANGE_LAST_X_LOC, 0x0d9af5d85bd502123bfd789aecc42038772d26923c2572cd103370c5f3ed4423) + mstore(LAGRANGE_LAST_Y_LOC, 0x10d0f9264ee08f9254a43a4447ae4e4a019c72cdb8abf41336dfedcab951a90d) + } + + // Prime field order - placing on the stack + let p := P + + { + let proof_ptr := add(calldataload(0x04), 0x24) + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* VALIDATE INPUT LENGTHS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Validate proof byte length matches expected size for this circuit's LOG_N. + // Expected = (8*2 + LOG_N*BATCHED_RELATION_PARTIAL_LENGTH + NUMBER_OF_ENTITIES + // + (LOG_N-1)*2 + LOG_N + 2*2 + PAIRING_POINTS_SIZE) * 32 + { + let expected_proof_size := mul( + add( + add( + add(16, mul(LOG_N, BATCHED_RELATION_PARTIAL_LENGTH)), + add(NUMBER_OF_ENTITIES, mul(sub(LOG_N, 1), 2)) + ), + add(add(LOG_N, 4), PAIRING_POINTS_SIZE) + ), + 32 + ) + let proof_length := calldataload(add(calldataload(0x04), 0x04)) + if iszero(eq(proof_length, expected_proof_size)) { + mstore(0x00, PROOF_LENGTH_WRONG_WITH_LOG_N_SELECTOR) + mstore(0x04, LOG_N) + mstore(0x24, proof_length) + mstore(0x44, expected_proof_size) + revert(0x00, 0x64) + } + } + // Validate public inputs array length matches expected count. + { + let pi_count := calldataload(add(calldataload(0x24), 0x04)) + if iszero(eq(pi_count, REAL_NUMBER_PUBLIC_INPUTS)) { + mstore(0x00, PUBLIC_INPUTS_LENGTH_WRONG_SELECTOR) + revert(0x00, 0x04) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* GENERATE CHALLENGES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + /* + * Proof points (affine coordinates) in the proof are in the following format, where offset is + * the offset in the entire proof until the first bit of the x coordinate + * offset + 0x00: x + * offset + 0x20: y + */ + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* GENERATE ETA CHALLENGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + /* Eta challenge participants + * - circuit size + * - number of public inputs + * - public inputs offset + * - w1 + * - w2 + * - w3 + * + * Where circuit size, number of public inputs and public inputs offset are all 32 byte values + * and w1,w2,w3 are all proof points values + */ + + mstore(0x00, VK_HASH) + + let public_inputs_start := add(calldataload(0x24), 0x24) + let public_inputs_size := mul(REAL_NUMBER_PUBLIC_INPUTS, 0x20) + + // Copy the public inputs into the eta buffer + calldatacopy(0x20, public_inputs_start, public_inputs_size) + + // Copy Pairing points into eta buffer + let public_inputs_end := add(0x20, public_inputs_size) + + calldatacopy(public_inputs_end, proof_ptr, 0x100) + + // 0x20 * 8 = 0x100 (8 pairing point limbs) + // End of public inputs + pairing points + calldatacopy(add(0x120, public_inputs_size), add(proof_ptr, 0x100), 0x100) + + // 0x1e0 = 1 * 32 bytes + 3 * 64 bytes for (w1,w2,w3) + 0x100 for pairing points + let eta_input_length := add(0x1e0, public_inputs_size) + + // Get eta and rom_logup_gamma, and compute eta powers (eta, eta², eta³) + let prev_challenge := mod(keccak256(0x00, eta_input_length), p) + let eta := prev_challenge + mstore(0x00, prev_challenge) + prev_challenge := mod(keccak256(0x00, 0x20), p) + mstore(0x00, prev_challenge) + + let eta_two := mulmod(eta, eta, p) + let eta_three := mulmod(eta_two, eta, p) + + mstore(ETA_CHALLENGE, eta) + mstore(ETA_TWO_CHALLENGE, eta_two) + mstore(ETA_THREE_CHALLENGE, eta_three) + mstore(ROM_LOGUP_GAMMA_CHALLENGE, prev_challenge) + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* LOAD PROOF INTO MEMORY */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // As all of our proof points are written in contiguous parts of memory, we call use a single + // calldatacopy to place all of our proof into the correct memory regions + // We copy the entire proof into memory as we must hash each proof section for challenge + // evaluation + // The last item in the proof, and the first item in the proof (pairing point 0) + let proof_size := sub(ETA_CHALLENGE, PAIRING_POINT_0_X_0_LOC) + + calldatacopy(PAIRING_POINT_0_X_0_LOC, proof_ptr, proof_size) + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* VALIDATE PROOF INPUTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Validate all proof elements are within their expected ranges. + // Pairing limbs: lo < 2^136, hi < 2^120. G1 coordinates < Q. Fr elements < P. + { + let valid := true + let lo_limb_max := shl(136, 1) + let hi_limb_max := shl(120, 1) + let q_mod := Q + + // 1. Pairing limbs: lo < 2^136, hi < 2^120 (4 pairs, stride 0x40) + let ptr := PAIRING_POINT_0_X_0_LOC + for {} lt(ptr, W_L_X_LOC) { ptr := add(ptr, 0x40) } { + valid := and(valid, lt(mload(ptr), lo_limb_max)) + valid := and(valid, lt(mload(add(ptr, 0x20)), hi_limb_max)) + } + if iszero(valid) { + mstore(0x00, VALUE_GE_LIMB_MAX_SELECTOR) + revert(0x00, 0x04) + } + + // 2. G1 coordinates: each < Q + // - Witness commitments: W_L through Z_PERM (16 slots) + for { ptr := W_L_X_LOC } lt(ptr, SUMCHECK_UNIVARIATE_0_0_LOC) { ptr := add(ptr, 0x20) } { + valid := and(valid, lt(mload(ptr), q_mod)) + } + // - Gemini fold commitments (28 slots) + for { ptr := GEMINI_FOLD_UNIVARIATE_0_X_LOC } lt(ptr, GEMINI_A_EVAL_0) { ptr := add(ptr, 0x20) } { + valid := and(valid, lt(mload(ptr), q_mod)) + } + // - Shplonk Q + KZG quotient (4 slots) + for { ptr := SHPLONK_Q_X_LOC } lt(ptr, ETA_CHALLENGE) { ptr := add(ptr, 0x20) } { + valid := and(valid, lt(mload(ptr), q_mod)) + } + if iszero(valid) { + mstore(0x00, VALUE_GE_GROUP_ORDER_SELECTOR) + revert(0x00, 0x04) + } + + // 2b. G1 points: identity (0,0) is accepted. + // Polynomial commitments to identically-zero polynomials are + // legitimately the identity, and the ecAdd/ecMul precompiles + // treat (0,0) as the additive identity per EIP-196. Soundness + // against (0,0) substitution for a non-zero commitment is upheld + // by sumcheck/Shplemini downstream. + + // 3. Fr elements: each < P + // - Sumcheck univariates + evaluations (161 slots) + for { ptr := SUMCHECK_UNIVARIATE_0_0_LOC } lt(ptr, GEMINI_FOLD_UNIVARIATE_0_X_LOC) { + ptr := add(ptr, 0x20) + } { + valid := and(valid, lt(mload(ptr), p)) + } + // - Gemini evaluations (15 slots) + for { ptr := GEMINI_A_EVAL_0 } lt(ptr, SHPLONK_Q_X_LOC) { ptr := add(ptr, 0x20) } { + valid := and(valid, lt(mload(ptr), p)) + } + if iszero(valid) { + mstore(0x00, VALUE_GE_FIELD_ORDER_SELECTOR) + revert(0x00, 0x04) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* GENERATE BETA and GAMMA CHALLENGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Generate Beta and Gamma Challenges + // - prevChallenge + // - LOOKUP_READ_COUNTS + // - LOOKUP_READ_TAGS + // - W4 + mcopy(0x20, LOOKUP_READ_COUNTS_X_LOC, 0xc0) + + prev_challenge := mod(keccak256(0x00, 0xe0), p) + let beta := prev_challenge + mstore(0x00, prev_challenge) + prev_challenge := mod(keccak256(0x00, 0x20), p) + mstore(0x00, prev_challenge) + let gamma := prev_challenge + + mstore(BETA_CHALLENGE, beta) + mstore(GAMMA_CHALLENGE, gamma) + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ALPHA CHALLENGES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Generate Alpha challenges - non-linearise the gate contributions + // + // There are 31 total subrelations in this honk relation, we do not need to non linearise the first sub relation. + // There are 30 total gate contributions, a gate contribution is analogous to + // a custom gate, it is an expression which must evaluate to zero for each + // row in the constraint matrix + // + // If we do not non-linearise sub relations, then sub relations which rely + // on the same wire will interact with each other's sums. + + mcopy(0x20, LOOKUP_INVERSES_X_LOC, 0x80) + + prev_challenge := mod(keccak256(0x00, 0xa0), p) + mstore(0x00, prev_challenge) + let alpha := prev_challenge + mstore(ALPHA_CHALLENGE_0, alpha) + + // Compute powers of alpha: alpha^2, alpha^3, ..., alpha^30 + let alpha_off_set := ALPHA_CHALLENGE_1 + for {} lt(alpha_off_set, add(ALPHA_CHALLENGE_29, 0x20)) {} { + let prev_alpha := mload(sub(alpha_off_set, 0x20)) + mstore(alpha_off_set, mulmod(prev_alpha, alpha, p)) + alpha_off_set := add(alpha_off_set, 0x20) + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* GATE CHALLENGES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Store the first gate challenge + prev_challenge := mod(keccak256(0x00, 0x20), p) + mstore(0x00, prev_challenge) + let gate_challenge := prev_challenge + mstore(GATE_CHALLENGE_0, gate_challenge) + + let gate_off := GATE_CHALLENGE_1 + for {} lt(gate_off, SUM_U_CHALLENGE_0) {} { + let prev := mload(sub(gate_off, 0x20)) + + mstore(gate_off, mulmod(prev, prev, p)) + gate_off := add(gate_off, 0x20) + } + + // Sumcheck Univariate challenges + // The algebraic relations of the Honk protocol are max degree-7. + // To prove satifiability, we multiply the relation by a random (POW) polynomial. We do this as we want all of our relations + // to be zero on every row - not for the sum of the relations to be zero. (Which is all sumcheck can do without this modification) + // + // As a result, in every round of sumcheck, the prover sends an degree-8 univariate polynomial. + // The sumcheck univariate challenge produces a challenge for each round of sumcheck, hashing the prev_challenge with + // a hash of the degree 8 univariate polynomial provided by the prover. + // + // 8 points are sent as it is enough to uniquely identify the polynomial + let read_off := SUMCHECK_UNIVARIATE_0_0_LOC + let write_off := SUM_U_CHALLENGE_0 + for {} lt(read_off, SIGMA1_EVAL_LOC) {} { + // Increase by 20 * batched relation length (8) + // 0x20 * 0x8 = 0x100 + mcopy(0x20, read_off, 0x100) + + // Hash 0x100 + 0x20 (prev hash) = 0x120 + prev_challenge := mod(keccak256(0x00, 0x120), p) + mstore(0x00, prev_challenge) + + let sumcheck_u_challenge := prev_challenge + mstore(write_off, sumcheck_u_challenge) + + // Progress read / write pointers + read_off := add(read_off, 0x100) + write_off := add(write_off, 0x20) + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RHO CHALLENGES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // The RHO challenge is the hash of the evaluations of all of the wire values + // As per usual, it includes the previous challenge + // Evaluations of the following wires and their shifts (for relevant wires): + // - QM + // - QC + // - Q1 (QL) + // - Q2 (QR) + // - Q3 (QO) + // - Q4 + // - QLOOKUP + // - QARITH + // - QRANGE + // - QELLIPTIC + // - QMEMORY + // - QNNF (NNF = Non Native Field) + // - QPOSEIDON2_EXTERNAL + // - QPOSEIDON2_INTERNAL + // - SIGMA1 + // - SIGMA2 + // - SIGMA3 + // - SIGMA4 + // - ID1 + // - ID2 + // - ID3 + // - ID4 + // - TABLE1 + // - TABLE2 + // - TABLE3 + // - TABLE4 + // - W1 (WL) + // - W2 (WR) + // - W3 (WO) + // - W4 + // - Z_PERM + // - LOOKUP_INVERSES + // - LOOKUP_READ_COUNTS + // - LOOKUP_READ_TAGS + // - W1_SHIFT + // - W2_SHIFT + // - W3_SHIFT + // - W4_SHIFT + // - Z_PERM_SHIFT + // + // Hash of all of the above evaluations + // Number of bytes to copy = 0x20 * NUMBER_OF_ENTITIES (41) = 0x520 + mcopy(0x20, SIGMA1_EVAL_LOC, 0x520) + prev_challenge := mod(keccak256(0x00, 0x540), p) + mstore(0x00, prev_challenge) + + let rho := prev_challenge + + mstore(RHO_CHALLENGE, rho) + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* GEMINI R CHALLENGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // The Gemini R challenge contains a of all of commitments to all of the univariates + // evaluated in the Gemini Protocol + // So for multivariate polynomials in l variables, we will hash l - 1 commitments. + // For this implementation, we have logN number of of rounds and thus logN - 1 committments + // The format of these commitments are proof points, which are explained above + // 0x40 * (logN - 1) + + mcopy(0x20, GEMINI_FOLD_UNIVARIATE_0_X_LOC, 0x5c0) + + prev_challenge := mod(keccak256(0x00, 0x5e0), p) + mstore(0x00, prev_challenge) + + let geminiR := prev_challenge + + mstore(GEMINI_R_CHALLENGE, geminiR) + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SHPLONK NU CHALLENGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // The shplonk nu challenge hashes the evaluations of the above gemini univariates + // 0x20 * logN = 0x20 * 15 = 0x1e0 + + mcopy(0x20, GEMINI_A_EVAL_0, 0x300) + prev_challenge := mod(keccak256(0x00, 0x320), p) + mstore(0x00, prev_challenge) + + let shplonkNu := prev_challenge + mstore(SHPLONK_NU_CHALLENGE, shplonkNu) + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SHPLONK Z CHALLENGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Generate Shplonk Z + // Hash of the single shplonk Q commitment + mcopy(0x20, SHPLONK_Q_X_LOC, 0x40) + prev_challenge := mod(keccak256(0x00, 0x60), p) + + let shplonkZ := prev_challenge + mstore(SHPLONK_Z_CHALLENGE, shplonkZ) + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CHALLENGES COMPLETE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PUBLIC INPUT DELTA */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + /** + * Generate public inputs delta + * + * The public inputs delta leverages plonk's copy constraints in order to + * evaluate public inputs. + * + * For each row of the execution trace, the prover will calculate the following value + * There are 4 witness wires, 4 id wires and 4 sigma wires in this instantiation of the proof system + * So there will be 4 groups of wires (w_i, id_i and sigma_i) + * + * (w_0 + β(id_0) + γ) * ∏(w_1 + β(id_1) + γ) * ∏(w_2 + β(id_2) + γ) * ∏(w_3 + β(id_3) + γ) + * ∏------------------------------------------------------------------------------------------ * public_inputs_delta + * (w_0 + β(σ_0) + γ) * ∏(w_1 + β(σ_1) + γ) * ∏(w_2 + β(σ_2) + γ) * ∏(w_3 + β(σ_3) + γ) + * + * The above product is accumulated for all rows in the trace. + * + * The above equation enforces that for each cell in the trace, if the id and sigma pair are equal, then the + * witness value in that cell is equal. + * + * We extra terms to add to this product that correspond to public input values. + * + * The values of id_i and σ_i polynomials are related to a generalized PLONK permutation argument, in the original paper, there + * were no id_i polynomials. + * + * These are required under the multilinear setting as we cannot use cosets of the roots of unity to represent unique sets, rather + * we just use polynomials that include unique values. In implementation, id_0 can be {0 .. n} and id_1 can be {n .. 2n} and so forth. + * + */ + { + let beta := mload(BETA_CHALLENGE) + let gamma := mload(GAMMA_CHALLENGE) + let pub_off := PUBLIC_INPUTS_OFFSET + + let numerator_value := 1 + let denominator_value := 1 + + let p_clone := p // move p to the front of the stack + + // Assume offset is less than p + // numerator_acc = gamma + (beta * (PERMUTATION_ARGUMENT_VALUE_SEPARATOR + offset)) + let numerator_acc := + addmod(gamma, mulmod(beta, add(PERMUTATION_ARGUMENT_VALUE_SEPARATOR, pub_off), p_clone), p_clone) + // denominator_acc = gamma - (beta * (offset + 1)) + let beta_x_off := mulmod(beta, add(pub_off, 1), p_clone) + let denominator_acc := addmod(gamma, sub(p_clone, beta_x_off), p_clone) + + let valid_inputs := true + // Load the starting point of the public inputs (jump over the selector and the length of public inputs [0x24]) + let public_inputs_ptr := add(calldataload(0x24), 0x24) + + // endpoint_ptr = public_inputs_ptr + num_inputs * 0x20. // every public input is 0x20 bytes + let endpoint_ptr := add(public_inputs_ptr, mul(REAL_NUMBER_PUBLIC_INPUTS, 0x20)) + + for {} lt(public_inputs_ptr, endpoint_ptr) { public_inputs_ptr := add(public_inputs_ptr, 0x20) } { + // Get public inputs from calldata + let input := calldataload(public_inputs_ptr) + + valid_inputs := and(valid_inputs, lt(input, p_clone)) + + numerator_value := mulmod(numerator_value, addmod(numerator_acc, input, p_clone), p_clone) + denominator_value := mulmod(denominator_value, addmod(denominator_acc, input, p_clone), p_clone) + + numerator_acc := addmod(numerator_acc, beta, p_clone) + denominator_acc := addmod(denominator_acc, sub(p_clone, beta), p_clone) + } + + // Revert if not all public inputs are field elements (i.e. < p) + if iszero(valid_inputs) { + mstore(0x00, VALUE_GE_FIELD_ORDER_SELECTOR) + revert(0x00, 0x04) + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PUBLIC INPUT DELTA - Pairing points accum */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Pairing points contribution to public inputs delta + let pairing_points_ptr := PAIRING_POINT_0_X_0_LOC + for {} lt(pairing_points_ptr, W_L_X_LOC) { pairing_points_ptr := add(pairing_points_ptr, 0x20) } { + let input := mload(pairing_points_ptr) + + numerator_value := mulmod(numerator_value, addmod(numerator_acc, input, p_clone), p_clone) + denominator_value := mulmod(denominator_value, addmod(denominator_acc, input, p_clone), p_clone) + + numerator_acc := addmod(numerator_acc, beta, p_clone) + denominator_acc := addmod(denominator_acc, sub(p_clone, beta), p_clone) + } + + mstore(PUBLIC_INPUTS_DELTA_NUMERATOR_CHALLENGE, numerator_value) + mstore(PUBLIC_INPUTS_DELTA_DENOMINATOR_CHALLENGE, denominator_value) + + // PI delta denominator inversion is deferred to the barycentric + // batch inversion below. + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PUBLIC INPUT DELTA - complete */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SUMCHECK */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // + // Sumcheck is used to prove that every relation 0 on each row of the witness. + // + // Given each of the columns of our trace is a multilinear polynomial 𝑃1,…,𝑃𝑁∈𝔽[𝑋0,…,𝑋𝑑−1]. We run sumcheck over the polynomial + // + // 𝐹̃ (𝑋0,…,𝑋𝑑−1)=𝑝𝑜𝑤𝛽(𝑋0,…,𝑋𝑑−1)⋅𝐹(𝑃1(𝑋0,…,𝑋𝑑−1),…,𝑃𝑁(𝑋0,…,𝑋𝑑−1)) + // + // The Pow polynomial is a random polynomial that allows us to ceritify that the relations sum to 0 on each row of the witness, + // rather than the entire sum just targeting 0. + // + // Each polynomial P in our implementation are the polys in the proof and the verification key. (W_1, W_2, W_3, W_4, Z_PERM, etc....) + // + // We start with a LOG_N variate multilinear polynomial, each round fixes a variable to a challenge value. + // Each round the prover sends a round univariate poly, since the degree of our honk relations is 7 + the pow polynomial the prover + // sends a degree-8 univariate on each round. + // This is sent efficiently by sending 8 values, enough to represent a unique polynomial. + // Barycentric evaluation is used to evaluate the polynomial at any point on the domain, given these 8 unique points. + // + // In the sumcheck protocol, the target sum for each round is the sum of the round univariate evaluated on 0 and 1. + // 𝜎𝑖=?𝑆̃ 𝑖(0)+𝑆̃ 𝑖(1) + // This is efficiently checked as S(0) and S(1) are sent by the prover as values of the round univariate. + // + // We compute the next challenge by evaluating the round univariate at a random challenge value. + // 𝜎𝑖+1←𝑆̃ 𝑖(𝑢𝑖) + // This evaluation is performed via barycentric evaluation. + // + // Once we have reduced the multilinear polynomials into single dimensional polys, we check the entire sumcheck relation matches the target sum. + // + // Below this is composed of 8 relations: + // 1. Arithmetic relation - constrains arithmetic + // 2. Permutaiton Relation - efficiently encodes copy constraints + // 3. Log Derivative Lookup Relation - used for lookup operations + // 4. Delta Range Relation - used for efficient range checks + // 5. Memory Relation - used for efficient memory operations + // 6. NNF Relation - used for efficient Non Native Field operations + // 7. Poseidon2 External Relation - used for efficient in-circuit hashing + // 8. Poseidon2 Internal Relation - used for efficient in-circuit hashing + // + // These are batched together and evaluated at the same time using the alpha challenges. + // + { + // We write the barycentric domain values into memory + // These are written once per program execution, and reused across all + // sumcheck rounds + mstore(BARYCENTRIC_LAGRANGE_DENOMINATOR_0_LOC, BARYCENTRIC_LAGRANGE_DENOMINATOR_0) + mstore(BARYCENTRIC_LAGRANGE_DENOMINATOR_1_LOC, BARYCENTRIC_LAGRANGE_DENOMINATOR_1) + mstore(BARYCENTRIC_LAGRANGE_DENOMINATOR_2_LOC, BARYCENTRIC_LAGRANGE_DENOMINATOR_2) + mstore(BARYCENTRIC_LAGRANGE_DENOMINATOR_3_LOC, BARYCENTRIC_LAGRANGE_DENOMINATOR_3) + mstore(BARYCENTRIC_LAGRANGE_DENOMINATOR_4_LOC, BARYCENTRIC_LAGRANGE_DENOMINATOR_4) + mstore(BARYCENTRIC_LAGRANGE_DENOMINATOR_5_LOC, BARYCENTRIC_LAGRANGE_DENOMINATOR_5) + mstore(BARYCENTRIC_LAGRANGE_DENOMINATOR_6_LOC, BARYCENTRIC_LAGRANGE_DENOMINATOR_6) + mstore(BARYCENTRIC_LAGRANGE_DENOMINATOR_7_LOC, BARYCENTRIC_LAGRANGE_DENOMINATOR_7) + + // Compute the target sums for each round of sumcheck + { + // This requires the barycentric inverses to be computed for each round + // Write all of the non inverted barycentric denominators into memory + let accumulator := 1 + let temp := FOLD_POS_EVALUATIONS_23_LOC // we use fold pos evaluations as we add 0x20 immediately to get to `BARYCENTRIC_TEMP_0_LOC` + let bary_centric_inverses_off := BARYCENTRIC_DENOMINATOR_INVERSES_0_0_LOC + { + let round_challenge_off := SUM_U_CHALLENGE_0 + for { let round := 0 } lt(round, LOG_N) { round := add(round, 1) } { + let round_challenge := mload(round_challenge_off) + let bary_lagrange_denominator_off := BARYCENTRIC_LAGRANGE_DENOMINATOR_0_LOC + + // Unrolled as this loop as it only has 8 iterations - somehow this saves >10k gas + { + let bary_lagrange_denominator := mload(bary_lagrange_denominator_off) + let pre_inv := + mulmod( + bary_lagrange_denominator, + addmod(round_challenge, p, p), // sub(p, 0) = p + p + ) + mstore(bary_centric_inverses_off, pre_inv) + temp := add(temp, 0x20) + mstore(temp, accumulator) + accumulator := mulmod(accumulator, pre_inv, p) + + // increase offsets + bary_lagrange_denominator_off := add(bary_lagrange_denominator_off, 0x20) + bary_centric_inverses_off := add(bary_centric_inverses_off, 0x20) + + // barycentric_index = 1 + bary_lagrange_denominator := mload(bary_lagrange_denominator_off) + pre_inv := mulmod(bary_lagrange_denominator, addmod(round_challenge, sub(p, 1), p), p) + mstore(bary_centric_inverses_off, pre_inv) + temp := add(temp, 0x20) + mstore(temp, accumulator) + accumulator := mulmod(accumulator, pre_inv, p) + + // increase offsets + bary_lagrange_denominator_off := add(bary_lagrange_denominator_off, 0x20) + bary_centric_inverses_off := add(bary_centric_inverses_off, 0x20) + + // barycentric_index = 2 + bary_lagrange_denominator := mload(bary_lagrange_denominator_off) + pre_inv := mulmod(bary_lagrange_denominator, addmod(round_challenge, sub(p, 2), p), p) + mstore(bary_centric_inverses_off, pre_inv) + temp := add(temp, 0x20) + mstore(temp, accumulator) + accumulator := mulmod(accumulator, pre_inv, p) + + // increase offsets + bary_lagrange_denominator_off := add(bary_lagrange_denominator_off, 0x20) + bary_centric_inverses_off := add(bary_centric_inverses_off, 0x20) + + // barycentric_index = 3 + bary_lagrange_denominator := mload(bary_lagrange_denominator_off) + pre_inv := mulmod(bary_lagrange_denominator, addmod(round_challenge, sub(p, 3), p), p) + mstore(bary_centric_inverses_off, pre_inv) + temp := add(temp, 0x20) + mstore(temp, accumulator) + accumulator := mulmod(accumulator, pre_inv, p) + + // increase offsets + bary_lagrange_denominator_off := add(bary_lagrange_denominator_off, 0x20) + bary_centric_inverses_off := add(bary_centric_inverses_off, 0x20) + + // barycentric_index = 4 + bary_lagrange_denominator := mload(bary_lagrange_denominator_off) + pre_inv := mulmod(bary_lagrange_denominator, addmod(round_challenge, sub(p, 4), p), p) + mstore(bary_centric_inverses_off, pre_inv) + temp := add(temp, 0x20) + mstore(temp, accumulator) + accumulator := mulmod(accumulator, pre_inv, p) + + // increase offsets + bary_lagrange_denominator_off := add(bary_lagrange_denominator_off, 0x20) + bary_centric_inverses_off := add(bary_centric_inverses_off, 0x20) + + // barycentric_index = 5 + bary_lagrange_denominator := mload(bary_lagrange_denominator_off) + pre_inv := mulmod(bary_lagrange_denominator, addmod(round_challenge, sub(p, 5), p), p) + mstore(bary_centric_inverses_off, pre_inv) + temp := add(temp, 0x20) + mstore(temp, accumulator) + accumulator := mulmod(accumulator, pre_inv, p) + + // increase offsets + bary_lagrange_denominator_off := add(bary_lagrange_denominator_off, 0x20) + bary_centric_inverses_off := add(bary_centric_inverses_off, 0x20) + + // barycentric_index = 6 + bary_lagrange_denominator := mload(bary_lagrange_denominator_off) + pre_inv := mulmod(bary_lagrange_denominator, addmod(round_challenge, sub(p, 6), p), p) + mstore(bary_centric_inverses_off, pre_inv) + temp := add(temp, 0x20) + mstore(temp, accumulator) + accumulator := mulmod(accumulator, pre_inv, p) + + // increase offsets + bary_lagrange_denominator_off := add(bary_lagrange_denominator_off, 0x20) + bary_centric_inverses_off := add(bary_centric_inverses_off, 0x20) + + // barycentric_index = 7 + bary_lagrange_denominator := mload(bary_lagrange_denominator_off) + pre_inv := mulmod(bary_lagrange_denominator, addmod(round_challenge, sub(p, 7), p), p) + mstore(bary_centric_inverses_off, pre_inv) + temp := add(temp, 0x20) + mstore(temp, accumulator) + accumulator := mulmod(accumulator, pre_inv, p) + + // increase offsets + bary_lagrange_denominator_off := add(bary_lagrange_denominator_off, 0x20) + bary_centric_inverses_off := add(bary_centric_inverses_off, 0x20) + } + round_challenge_off := add(round_challenge_off, 0x20) + } + } + + // Append PI delta denominator to the batch inversion + { + let pi_denom := mload(PUBLIC_INPUTS_DELTA_DENOMINATOR_CHALLENGE) + mstore(PUBLIC_INPUTS_DENOM_TEMP_LOC, accumulator) + accumulator := mulmod(accumulator, pi_denom, p) + } + + // --- Phase 2: Shplemini forward pass --- + // Compute shplemini denominators and accumulate into the running product. + // Pre-inversion values stored in shplemini runtime memory + { + // Compute powers of evaluation challenge: gemini_r^{2^i} + let cache := mload(GEMINI_R_CHALLENGE) + mstore(POWERS_OF_EVALUATION_CHALLENGE_0_LOC, cache) + /// {{ UNROLL_SECTION_START POWERS_OF_EVALUATION_COMPUTATION }} + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_1_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_2_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_3_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_4_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_5_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_6_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_7_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_8_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_9_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_10_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_11_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_12_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_13_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_14_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_15_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_16_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_17_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_18_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_19_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_20_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_21_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_22_LOC, cache) + cache := mulmod(cache, cache, p) + mstore(POWERS_OF_EVALUATION_CHALLENGE_23_LOC, cache) +/// {{ UNROLL_SECTION_END POWERS_OF_EVALUATION_COMPUTATION }} + + // Element 0: gemini_r (seed) + { + let val := mload(GEMINI_R_CHALLENGE) + mstore(GEMINI_R_INV_TEMP_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + + // Elements 1..LOG_N: INVERTED_CHALLENGE_POW_MINUS_U + /// {{ UNROLL_SECTION_START ACCUMULATE_INVERSES }} + // INVERTED_CHALLENGE_POW_MINUS_U_0 + { + let u := mload(SUM_U_CHALLENGE_0) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_0_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_0_LOC, val) + mstore(TEMP_0_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_1 + { + let u := mload(SUM_U_CHALLENGE_1) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_1_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_1_LOC, val) + mstore(TEMP_1_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_2 + { + let u := mload(SUM_U_CHALLENGE_2) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_2_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_2_LOC, val) + mstore(TEMP_2_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_3 + { + let u := mload(SUM_U_CHALLENGE_3) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_3_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_3_LOC, val) + mstore(TEMP_3_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_4 + { + let u := mload(SUM_U_CHALLENGE_4) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_4_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_4_LOC, val) + mstore(TEMP_4_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_5 + { + let u := mload(SUM_U_CHALLENGE_5) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_5_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_5_LOC, val) + mstore(TEMP_5_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_6 + { + let u := mload(SUM_U_CHALLENGE_6) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_6_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_6_LOC, val) + mstore(TEMP_6_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_7 + { + let u := mload(SUM_U_CHALLENGE_7) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_7_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_7_LOC, val) + mstore(TEMP_7_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_8 + { + let u := mload(SUM_U_CHALLENGE_8) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_8_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_8_LOC, val) + mstore(TEMP_8_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_9 + { + let u := mload(SUM_U_CHALLENGE_9) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_9_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_9_LOC, val) + mstore(TEMP_9_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_10 + { + let u := mload(SUM_U_CHALLENGE_10) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_10_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_10_LOC, val) + mstore(TEMP_10_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_11 + { + let u := mload(SUM_U_CHALLENGE_11) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_11_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_11_LOC, val) + mstore(TEMP_11_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_12 + { + let u := mload(SUM_U_CHALLENGE_12) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_12_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_12_LOC, val) + mstore(TEMP_12_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_13 + { + let u := mload(SUM_U_CHALLENGE_13) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_13_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_13_LOC, val) + mstore(TEMP_13_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_14 + { + let u := mload(SUM_U_CHALLENGE_14) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_14_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_14_LOC, val) + mstore(TEMP_14_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_15 + { + let u := mload(SUM_U_CHALLENGE_15) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_15_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_15_LOC, val) + mstore(TEMP_15_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_16 + { + let u := mload(SUM_U_CHALLENGE_16) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_16_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_16_LOC, val) + mstore(TEMP_16_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_17 + { + let u := mload(SUM_U_CHALLENGE_17) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_17_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_17_LOC, val) + mstore(TEMP_17_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_18 + { + let u := mload(SUM_U_CHALLENGE_18) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_18_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_18_LOC, val) + mstore(TEMP_18_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_19 + { + let u := mload(SUM_U_CHALLENGE_19) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_19_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_19_LOC, val) + mstore(TEMP_19_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_20 + { + let u := mload(SUM_U_CHALLENGE_20) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_20_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_20_LOC, val) + mstore(TEMP_20_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_21 + { + let u := mload(SUM_U_CHALLENGE_21) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_21_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_21_LOC, val) + mstore(TEMP_21_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_22 + { + let u := mload(SUM_U_CHALLENGE_22) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_22_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_22_LOC, val) + mstore(TEMP_22_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // INVERTED_CHALLENGE_POW_MINUS_U_23 + { + let u := mload(SUM_U_CHALLENGE_23) + let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_23_LOC) + let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_23_LOC, val) + mstore(TEMP_23_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + + // Accumulate pos inverted denom + // Elements LOG_N+1..2*LOG_N: POS_INVERTED_DENOM + let eval_challenge := mload(SHPLONK_Z_CHALLENGE) + // POS_INVERTED_DENOM_0 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_0_LOC)) , p) + mstore(POS_INVERTED_DENOM_0_LOC, val) + mstore(TEMP_24_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_1 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_1_LOC)) , p) + mstore(POS_INVERTED_DENOM_1_LOC, val) + mstore(TEMP_25_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_2 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_2_LOC)) , p) + mstore(POS_INVERTED_DENOM_2_LOC, val) + mstore(TEMP_26_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_3 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_3_LOC)) , p) + mstore(POS_INVERTED_DENOM_3_LOC, val) + mstore(TEMP_27_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_4 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_4_LOC)) , p) + mstore(POS_INVERTED_DENOM_4_LOC, val) + mstore(TEMP_28_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_5 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_5_LOC)) , p) + mstore(POS_INVERTED_DENOM_5_LOC, val) + mstore(TEMP_29_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_6 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_6_LOC)) , p) + mstore(POS_INVERTED_DENOM_6_LOC, val) + mstore(TEMP_30_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_7 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_7_LOC)) , p) + mstore(POS_INVERTED_DENOM_7_LOC, val) + mstore(TEMP_31_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_8 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_8_LOC)) , p) + mstore(POS_INVERTED_DENOM_8_LOC, val) + mstore(TEMP_32_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_9 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_9_LOC)) , p) + mstore(POS_INVERTED_DENOM_9_LOC, val) + mstore(TEMP_33_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_10 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_10_LOC)) , p) + mstore(POS_INVERTED_DENOM_10_LOC, val) + mstore(TEMP_34_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_11 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_11_LOC)) , p) + mstore(POS_INVERTED_DENOM_11_LOC, val) + mstore(TEMP_35_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_12 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_12_LOC)) , p) + mstore(POS_INVERTED_DENOM_12_LOC, val) + mstore(TEMP_36_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_13 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_13_LOC)) , p) + mstore(POS_INVERTED_DENOM_13_LOC, val) + mstore(TEMP_37_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_14 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_14_LOC)) , p) + mstore(POS_INVERTED_DENOM_14_LOC, val) + mstore(TEMP_38_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_15 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_15_LOC)) , p) + mstore(POS_INVERTED_DENOM_15_LOC, val) + mstore(TEMP_39_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_16 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_16_LOC)) , p) + mstore(POS_INVERTED_DENOM_16_LOC, val) + mstore(TEMP_40_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_17 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_17_LOC)) , p) + mstore(POS_INVERTED_DENOM_17_LOC, val) + mstore(TEMP_41_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_18 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_18_LOC)) , p) + mstore(POS_INVERTED_DENOM_18_LOC, val) + mstore(TEMP_42_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_19 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_19_LOC)) , p) + mstore(POS_INVERTED_DENOM_19_LOC, val) + mstore(TEMP_43_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_20 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_20_LOC)) , p) + mstore(POS_INVERTED_DENOM_20_LOC, val) + mstore(TEMP_44_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_21 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_21_LOC)) , p) + mstore(POS_INVERTED_DENOM_21_LOC, val) + mstore(TEMP_45_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_22 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_22_LOC)) , p) + mstore(POS_INVERTED_DENOM_22_LOC, val) + mstore(TEMP_46_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + // POS_INVERTED_DENOM_23 + { + let val := addmod(eval_challenge, sub(p, mload(POWERS_OF_EVALUATION_CHALLENGE_23_LOC)) , p) + mstore(POS_INVERTED_DENOM_23_LOC, val) + mstore(TEMP_47_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + + // Accumulate neg inverted denom + // Elements 2*LOG_N+1..3*LOG_N: NEG_INVERTED_DENOM + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_0_LOC), p) + mstore(NEG_INVERTED_DENOM_0_LOC, val) + mstore(TEMP_48_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_1_LOC), p) + mstore(NEG_INVERTED_DENOM_1_LOC, val) + mstore(TEMP_49_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_2_LOC), p) + mstore(NEG_INVERTED_DENOM_2_LOC, val) + mstore(TEMP_50_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_3_LOC), p) + mstore(NEG_INVERTED_DENOM_3_LOC, val) + mstore(TEMP_51_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_4_LOC), p) + mstore(NEG_INVERTED_DENOM_4_LOC, val) + mstore(TEMP_52_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_5_LOC), p) + mstore(NEG_INVERTED_DENOM_5_LOC, val) + mstore(TEMP_53_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_6_LOC), p) + mstore(NEG_INVERTED_DENOM_6_LOC, val) + mstore(TEMP_54_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_7_LOC), p) + mstore(NEG_INVERTED_DENOM_7_LOC, val) + mstore(TEMP_55_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_8_LOC), p) + mstore(NEG_INVERTED_DENOM_8_LOC, val) + mstore(TEMP_56_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_9_LOC), p) + mstore(NEG_INVERTED_DENOM_9_LOC, val) + mstore(TEMP_57_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_10_LOC), p) + mstore(NEG_INVERTED_DENOM_10_LOC, val) + mstore(TEMP_58_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_11_LOC), p) + mstore(NEG_INVERTED_DENOM_11_LOC, val) + mstore(TEMP_59_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_12_LOC), p) + mstore(NEG_INVERTED_DENOM_12_LOC, val) + mstore(TEMP_60_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_13_LOC), p) + mstore(NEG_INVERTED_DENOM_13_LOC, val) + mstore(TEMP_61_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_14_LOC), p) + mstore(NEG_INVERTED_DENOM_14_LOC, val) + mstore(TEMP_62_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_15_LOC), p) + mstore(NEG_INVERTED_DENOM_15_LOC, val) + mstore(TEMP_63_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_16_LOC), p) + mstore(NEG_INVERTED_DENOM_16_LOC, val) + mstore(TEMP_64_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_17_LOC), p) + mstore(NEG_INVERTED_DENOM_17_LOC, val) + mstore(TEMP_65_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_18_LOC), p) + mstore(NEG_INVERTED_DENOM_18_LOC, val) + mstore(TEMP_66_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_19_LOC), p) + mstore(NEG_INVERTED_DENOM_19_LOC, val) + mstore(TEMP_67_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_20_LOC), p) + mstore(NEG_INVERTED_DENOM_20_LOC, val) + mstore(TEMP_68_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_21_LOC), p) + mstore(NEG_INVERTED_DENOM_21_LOC, val) + mstore(TEMP_69_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_22_LOC), p) + mstore(NEG_INVERTED_DENOM_22_LOC, val) + mstore(TEMP_70_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } + { + let val := addmod(eval_challenge, mload(POWERS_OF_EVALUATION_CHALLENGE_23_LOC), p) + mstore(NEG_INVERTED_DENOM_23_LOC, val) + mstore(TEMP_71_LOC, accumulator) + accumulator := mulmod(accumulator, val, p) + } +/// {{ UNROLL_SECTION_END ACCUMULATE_INVERSES }} + + // Invert all elements (barycentric + PI delta + shplemini) as a single batch + { + { + mstore(0, 0x20) + mstore(0x20, 0x20) + mstore(0x40, 0x20) + mstore(0x60, accumulator) + mstore(0x80, P_SUB_2) + mstore(0xa0, p) + if iszero(staticcall(gas(), 0x05, 0x00, 0xc0, 0x00, 0x20)) { + mstore(0x00, MODEXP_FAILED_SELECTOR) + revert(0x00, 0x04) + } + + accumulator := mload(0x00) + if iszero(accumulator) { + mstore(0x00, MODEXP_FAILED_SELECTOR) + revert(0x00, 0x04) + } + } + + // --- Shplemini backward pass --- + // Extract shplemini inverses in strict reverse order. + /// {{ UNROLL_SECTION_START COLLECT_INVERSES }} + // i = 24 + // NEG_INVERTED_DENOM (LOG_N elements, reverse) -- last group appended + { + let tmp := mulmod(accumulator, mload(TEMP_71_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_23_LOC), p) + mstore(NEG_INVERTED_DENOM_23_LOC, tmp) + } + // i = 23 + { + let tmp := mulmod(accumulator, mload(TEMP_70_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_22_LOC), p) + mstore(NEG_INVERTED_DENOM_22_LOC, tmp) + } + // i = 22 + { + let tmp := mulmod(accumulator, mload(TEMP_69_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_21_LOC), p) + mstore(NEG_INVERTED_DENOM_21_LOC, tmp) + } + // i = 21 + { + let tmp := mulmod(accumulator, mload(TEMP_68_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_20_LOC), p) + mstore(NEG_INVERTED_DENOM_20_LOC, tmp) + } + // i = 20 + { + let tmp := mulmod(accumulator, mload(TEMP_67_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_19_LOC), p) + mstore(NEG_INVERTED_DENOM_19_LOC, tmp) + } + // i = 19 + { + let tmp := mulmod(accumulator, mload(TEMP_66_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_18_LOC), p) + mstore(NEG_INVERTED_DENOM_18_LOC, tmp) + } + // i = 18 + { + let tmp := mulmod(accumulator, mload(TEMP_65_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_17_LOC), p) + mstore(NEG_INVERTED_DENOM_17_LOC, tmp) + } + // i = 17 + { + let tmp := mulmod(accumulator, mload(TEMP_64_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_16_LOC), p) + mstore(NEG_INVERTED_DENOM_16_LOC, tmp) + } + // i = 16 + { + let tmp := mulmod(accumulator, mload(TEMP_63_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_15_LOC), p) + mstore(NEG_INVERTED_DENOM_15_LOC, tmp) + } + // i = 15 + { + let tmp := mulmod(accumulator, mload(TEMP_62_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_14_LOC), p) + mstore(NEG_INVERTED_DENOM_14_LOC, tmp) + } + // i = 14 + { + let tmp := mulmod(accumulator, mload(TEMP_61_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_13_LOC), p) + mstore(NEG_INVERTED_DENOM_13_LOC, tmp) + } + // i = 13 + { + let tmp := mulmod(accumulator, mload(TEMP_60_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_12_LOC), p) + mstore(NEG_INVERTED_DENOM_12_LOC, tmp) + } + // i = 12 + { + let tmp := mulmod(accumulator, mload(TEMP_59_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_11_LOC), p) + mstore(NEG_INVERTED_DENOM_11_LOC, tmp) + } + // i = 11 + { + let tmp := mulmod(accumulator, mload(TEMP_58_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_10_LOC), p) + mstore(NEG_INVERTED_DENOM_10_LOC, tmp) + } + // i = 10 + { + let tmp := mulmod(accumulator, mload(TEMP_57_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_9_LOC), p) + mstore(NEG_INVERTED_DENOM_9_LOC, tmp) + } + // i = 9 + { + let tmp := mulmod(accumulator, mload(TEMP_56_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_8_LOC), p) + mstore(NEG_INVERTED_DENOM_8_LOC, tmp) + } + // i = 8 + { + let tmp := mulmod(accumulator, mload(TEMP_55_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_7_LOC), p) + mstore(NEG_INVERTED_DENOM_7_LOC, tmp) + } + // i = 7 + { + let tmp := mulmod(accumulator, mload(TEMP_54_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_6_LOC), p) + mstore(NEG_INVERTED_DENOM_6_LOC, tmp) + } + // i = 6 + { + let tmp := mulmod(accumulator, mload(TEMP_53_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_5_LOC), p) + mstore(NEG_INVERTED_DENOM_5_LOC, tmp) + } + // i = 5 + { + let tmp := mulmod(accumulator, mload(TEMP_52_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_4_LOC), p) + mstore(NEG_INVERTED_DENOM_4_LOC, tmp) + } + // i = 4 + { + let tmp := mulmod(accumulator, mload(TEMP_51_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_3_LOC), p) + mstore(NEG_INVERTED_DENOM_3_LOC, tmp) + } + // i = 3 + { + let tmp := mulmod(accumulator, mload(TEMP_50_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_2_LOC), p) + mstore(NEG_INVERTED_DENOM_2_LOC, tmp) + } + // i = 2 + { + let tmp := mulmod(accumulator, mload(TEMP_49_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_1_LOC), p) + mstore(NEG_INVERTED_DENOM_1_LOC, tmp) + } + // i = 1 + { + let tmp := mulmod(accumulator, mload(TEMP_48_LOC), p) + accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_0_LOC), p) + mstore(NEG_INVERTED_DENOM_0_LOC, tmp) + } + + // Unrolled for LOG_N = 24 + // i = 24 + { + let tmp := mulmod(accumulator, mload(TEMP_47_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_23_LOC), p) + mstore(POS_INVERTED_DENOM_23_LOC, tmp) + } + // i = 23 + { + let tmp := mulmod(accumulator, mload(TEMP_46_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_22_LOC), p) + mstore(POS_INVERTED_DENOM_22_LOC, tmp) + } + // i = 22 + { + let tmp := mulmod(accumulator, mload(TEMP_45_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_21_LOC), p) + mstore(POS_INVERTED_DENOM_21_LOC, tmp) + } + // i = 21 + { + let tmp := mulmod(accumulator, mload(TEMP_44_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_20_LOC), p) + mstore(POS_INVERTED_DENOM_20_LOC, tmp) + } + // i = 20 + { + let tmp := mulmod(accumulator, mload(TEMP_43_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_19_LOC), p) + mstore(POS_INVERTED_DENOM_19_LOC, tmp) + } + // i = 19 + { + let tmp := mulmod(accumulator, mload(TEMP_42_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_18_LOC), p) + mstore(POS_INVERTED_DENOM_18_LOC, tmp) + } + // i = 18 + { + let tmp := mulmod(accumulator, mload(TEMP_41_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_17_LOC), p) + mstore(POS_INVERTED_DENOM_17_LOC, tmp) + } + // i = 17 + { + let tmp := mulmod(accumulator, mload(TEMP_40_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_16_LOC), p) + mstore(POS_INVERTED_DENOM_16_LOC, tmp) + } + // i = 16 + { + let tmp := mulmod(accumulator, mload(TEMP_39_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_15_LOC), p) + mstore(POS_INVERTED_DENOM_15_LOC, tmp) + } + // i = 15 + { + let tmp := mulmod(accumulator, mload(TEMP_38_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_14_LOC), p) + mstore(POS_INVERTED_DENOM_14_LOC, tmp) + } + // i = 14 + { + let tmp := mulmod(accumulator, mload(TEMP_37_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_13_LOC), p) + mstore(POS_INVERTED_DENOM_13_LOC, tmp) + } + // i = 13 + { + let tmp := mulmod(accumulator, mload(TEMP_36_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_12_LOC), p) + mstore(POS_INVERTED_DENOM_12_LOC, tmp) + } + // i = 12 + { + let tmp := mulmod(accumulator, mload(TEMP_35_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_11_LOC), p) + mstore(POS_INVERTED_DENOM_11_LOC, tmp) + } + // i = 11 + { + let tmp := mulmod(accumulator, mload(TEMP_34_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_10_LOC), p) + mstore(POS_INVERTED_DENOM_10_LOC, tmp) + } + // i = 10 + { + let tmp := mulmod(accumulator, mload(TEMP_33_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_9_LOC), p) + mstore(POS_INVERTED_DENOM_9_LOC, tmp) + } + // i = 9 + { + let tmp := mulmod(accumulator, mload(TEMP_32_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_8_LOC), p) + mstore(POS_INVERTED_DENOM_8_LOC, tmp) + } + // i = 8 + { + let tmp := mulmod(accumulator, mload(TEMP_31_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_7_LOC), p) + mstore(POS_INVERTED_DENOM_7_LOC, tmp) + } + // i = 7 + { + let tmp := mulmod(accumulator, mload(TEMP_30_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_6_LOC), p) + mstore(POS_INVERTED_DENOM_6_LOC, tmp) + } + // i = 6 + { + let tmp := mulmod(accumulator, mload(TEMP_29_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_5_LOC), p) + mstore(POS_INVERTED_DENOM_5_LOC, tmp) + } + // i = 5 + { + let tmp := mulmod(accumulator, mload(TEMP_28_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_4_LOC), p) + mstore(POS_INVERTED_DENOM_4_LOC, tmp) + } + // i = 4 + { + let tmp := mulmod(accumulator, mload(TEMP_27_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_3_LOC), p) + mstore(POS_INVERTED_DENOM_3_LOC, tmp) + } + // i = 3 + { + let tmp := mulmod(accumulator, mload(TEMP_26_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_2_LOC), p) + mstore(POS_INVERTED_DENOM_2_LOC, tmp) + } + // i = 2 + { + let tmp := mulmod(accumulator, mload(TEMP_25_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_1_LOC), p) + mstore(POS_INVERTED_DENOM_1_LOC, tmp) + } + // i = 1 + { + let tmp := mulmod(accumulator, mload(TEMP_24_LOC), p) + accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_0_LOC), p) + mstore(POS_INVERTED_DENOM_0_LOC, tmp) + } + + // i = 24 + { + let tmp := mulmod(accumulator, mload(TEMP_23_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_23_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_23_LOC, tmp) + } + // i = 23 + { + let tmp := mulmod(accumulator, mload(TEMP_22_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_22_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_22_LOC, tmp) + } + // i = 22 + { + let tmp := mulmod(accumulator, mload(TEMP_21_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_21_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_21_LOC, tmp) + } + // i = 21 + { + let tmp := mulmod(accumulator, mload(TEMP_20_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_20_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_20_LOC, tmp) + } + // i = 20 + { + let tmp := mulmod(accumulator, mload(TEMP_19_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_19_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_19_LOC, tmp) + } + // i = 19 + { + let tmp := mulmod(accumulator, mload(TEMP_18_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_18_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_18_LOC, tmp) + } + // i = 18 + { + let tmp := mulmod(accumulator, mload(TEMP_17_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_17_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_17_LOC, tmp) + } + // i = 17 + { + let tmp := mulmod(accumulator, mload(TEMP_16_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_16_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_16_LOC, tmp) + } + // i = 16 + { + let tmp := mulmod(accumulator, mload(TEMP_15_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_15_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_15_LOC, tmp) + } + // i = 15 + { + let tmp := mulmod(accumulator, mload(TEMP_14_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_14_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_14_LOC, tmp) + } + // i = 14 + { + let tmp := mulmod(accumulator, mload(TEMP_13_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_13_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_13_LOC, tmp) + } + // i = 13 + { + let tmp := mulmod(accumulator, mload(TEMP_12_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_12_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_12_LOC, tmp) + } + // i = 12 + { + let tmp := mulmod(accumulator, mload(TEMP_11_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_11_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_11_LOC, tmp) + } + // i = 11 + { + let tmp := mulmod(accumulator, mload(TEMP_10_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_10_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_10_LOC, tmp) + } + // i = 10 + { + let tmp := mulmod(accumulator, mload(TEMP_9_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_9_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_9_LOC, tmp) + } + // i = 9 + { + let tmp := mulmod(accumulator, mload(TEMP_8_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_8_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_8_LOC, tmp) + } + // i = 8 + { + let tmp := mulmod(accumulator, mload(TEMP_7_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_7_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_7_LOC, tmp) + } + // i = 7 + { + let tmp := mulmod(accumulator, mload(TEMP_6_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_6_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_6_LOC, tmp) + } + // i = 6 + { + let tmp := mulmod(accumulator, mload(TEMP_5_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_5_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_5_LOC, tmp) + } + // i = 5 + { + let tmp := mulmod(accumulator, mload(TEMP_4_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_4_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_4_LOC, tmp) + } + // i = 4 + { + let tmp := mulmod(accumulator, mload(TEMP_3_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_3_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_3_LOC, tmp) + } + // i = 3 + { + let tmp := mulmod(accumulator, mload(TEMP_2_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_2_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_2_LOC, tmp) + } + // i = 2 + { + let tmp := mulmod(accumulator, mload(TEMP_1_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_1_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_1_LOC, tmp) + } + // i = 1 + { + let tmp := mulmod(accumulator, mload(TEMP_0_LOC), p) + accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_0_LOC), p) + mstore(INVERTED_CHALLENGE_POW_MINUS_U_0_LOC, tmp) + } +/// {{ UNROLL_SECTION_END COLLECT_INVERSES }} + + // gemini_r inverse (staging[0]) + { + let tmp := mulmod(accumulator, mload(GEMINI_R_INV_TEMP_LOC), p) + accumulator := mulmod(accumulator, mload(GEMINI_R_CHALLENGE), p) + mstore(GEMINI_R_INV_LOC, tmp) // 1/gemini_r at staging[0] + } + } + + // Extract PI delta denominator inverse from the batch + { + let pi_delta_inv := mulmod(accumulator, mload(PUBLIC_INPUTS_DENOM_TEMP_LOC), p) + accumulator := mulmod(accumulator, mload(PUBLIC_INPUTS_DELTA_DENOMINATOR_CHALLENGE), p) + + // Finalize: public_inputs_delta = numerator * (1/denominator) + mstore( + PUBLIC_INPUTS_DELTA_NUMERATOR_CHALLENGE, + mulmod(mload(PUBLIC_INPUTS_DELTA_NUMERATOR_CHALLENGE), pi_delta_inv, p) + ) + } + + // Normalise as last loop will have incremented the offset + bary_centric_inverses_off := sub(bary_centric_inverses_off, 0x20) + for {} gt(bary_centric_inverses_off, BARYCENTRIC_LAGRANGE_DENOMINATOR_7_LOC) { + bary_centric_inverses_off := sub(bary_centric_inverses_off, 0x20) + } { + let tmp := mulmod(accumulator, mload(temp), p) + accumulator := mulmod(accumulator, mload(bary_centric_inverses_off), p) + mstore(bary_centric_inverses_off, tmp) + + temp := sub(temp, 0x20) + } + } + } + + let valid := true + let round_target := 0 + let pow_partial_evaluation := 1 + let gate_challenge_off := GATE_CHALLENGE_0 + let round_univariates_off := SUMCHECK_UNIVARIATE_0_0_LOC + + let challenge_off := SUM_U_CHALLENGE_0 + let bary_inverses_off := BARYCENTRIC_DENOMINATOR_INVERSES_0_0_LOC + + for { let round := 0 } lt(round, LOG_N) { round := add(round, 1) } { + let round_challenge := mload(challenge_off) + + // Total sum = u[0] + u[1] + let total_sum := addmod(mload(round_univariates_off), mload(add(round_univariates_off, 0x20)), p) + valid := and(valid, eq(total_sum, round_target)) + + // Compute next target sum + let numerator_value := round_challenge + numerator_value := mulmod(numerator_value, addmod(round_challenge, sub(p, 1), p), p) + numerator_value := mulmod(numerator_value, addmod(round_challenge, sub(p, 2), p), p) + numerator_value := mulmod(numerator_value, addmod(round_challenge, sub(p, 3), p), p) + numerator_value := mulmod(numerator_value, addmod(round_challenge, sub(p, 4), p), p) + numerator_value := mulmod(numerator_value, addmod(round_challenge, sub(p, 5), p), p) + numerator_value := mulmod(numerator_value, addmod(round_challenge, sub(p, 6), p), p) + numerator_value := mulmod(numerator_value, addmod(round_challenge, sub(p, 7), p), p) + + // // Compute the next round target + round_target := 0 + for { let i := 0 } lt(i, BATCHED_RELATION_PARTIAL_LENGTH) { i := add(i, 1) } { + let term := mload(round_univariates_off) + let inverse := mload(bary_inverses_off) + + term := mulmod(term, inverse, p) + round_target := addmod(round_target, term, p) + round_univariates_off := add(round_univariates_off, 0x20) + bary_inverses_off := add(bary_inverses_off, 0x20) + } + + round_target := mulmod(round_target, numerator_value, p) + + // Partially evaluate POW + let gate_challenge := mload(gate_challenge_off) + let gate_challenge_minus_one := addmod(gate_challenge, sub(p, 1), p) + + let univariate_evaluation := addmod(1, mulmod(round_challenge, gate_challenge_minus_one, p), p) + + pow_partial_evaluation := mulmod(pow_partial_evaluation, univariate_evaluation, p) + + gate_challenge_off := add(gate_challenge_off, 0x20) + challenge_off := add(challenge_off, 0x20) + } + + if iszero(valid) { + mstore(0x00, SUMCHECK_FAILED_SELECTOR) + revert(0x00, 0x04) + } + + // The final sumcheck round; accumulating evaluations + // Uses pow partial evaluation as the gate scaling factor + + mstore(POW_PARTIAL_EVALUATION_LOC, pow_partial_evaluation) + mstore(FINAL_ROUND_TARGET_LOC, round_target) + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ARITHMETIC RELATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + { + /** + * The basic arithmetic gate identity in standard plonk is as follows. + * (w_1 . w_2 . q_m) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c = 0 + * However, for Ultraplonk, we extend this to support "passing" wires between rows (shown without alpha scaling below): + * q_arith * ( ( (-1/2) * (q_arith - 3) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c ) + + * (q_arith - 1)*( α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + w_4_omega) ) = 0 + * + * This formula results in several cases depending on q_arith: + * 1. q_arith == 0: Arithmetic gate is completely disabled + * + * 2. q_arith == 1: Everything in the minigate on the right is disabled. The equation is just a standard plonk equation + * with extra wires: q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c = 0 + * + * 3. q_arith == 2: The (w_1 + w_4 - ...) term is disabled. THe equation is: + * (1/2) * q_m * w_1 * w_2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + w_4_omega = 0 + * It allows defining w_4 at next index (w_4_omega) in terms of current wire values + * + * 4. q_arith == 3: The product of w_1 and w_2 is disabled, but a mini addition gate is enabled. α allows us to split + * the equation into two: + * + * q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + 2 * w_4_omega = 0 + * and + * w_1 + w_4 - w_1_omega + q_m = 0 (we are reusing q_m here) + * + * 5. q_arith > 3: The product of w_1 and w_2 is scaled by (q_arith - 3), while the w_4_omega term is scaled by (q_arith - 1). + * The equation can be split into two: + * + * (q_arith - 3)* q_m * w_1 * w_ 2 + q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + (q_arith - 1) * w_4_omega = 0 + * and + * w_1 + w_4 - w_1_omega + q_m = 0 + * + * The problem that q_m is used both in both equations can be dealt with by appropriately changing selector values at + * the next gate. Then we can treat (q_arith - 1) as a simulated q_6 selector and scale q_m to handle (q_arith - 3) at + * product. + */ + let w1q1 := mulmod(mload(W1_EVAL_LOC), mload(QL_EVAL_LOC), p) + let w2q2 := mulmod(mload(W2_EVAL_LOC), mload(QR_EVAL_LOC), p) + let w3q3 := mulmod(mload(W3_EVAL_LOC), mload(QO_EVAL_LOC), p) + let w4q3 := mulmod(mload(W4_EVAL_LOC), mload(Q4_EVAL_LOC), p) + + let q_arith := mload(QARITH_EVAL_LOC) + // w1w2qm := (w_1 . w_2 . q_m . (QARITH_EVAL_LOC - 3)) / 2 + let w1w2qm := + mulmod( + mulmod( + mulmod(mulmod(mload(W1_EVAL_LOC), mload(W2_EVAL_LOC), p), mload(QM_EVAL_LOC), p), + addmod(q_arith, sub(p, 3), p), + p + ), + NEG_HALF_MODULO_P, + p + ) + + // (w_1 . w_2 . q_m . (q_arith - 3)) / -2) + (w_1 . q_1) + (w_2 . q_2) + (w_3 . q_3) + (w_4 . q_4) + q_c + let identity := + addmod( + mload(QC_EVAL_LOC), + addmod(w4q3, addmod(w3q3, addmod(w2q2, addmod(w1q1, w1w2qm, p), p), p), p), + p + ) + + // if q_arith == 3 we evaluate an additional mini addition gate (on top of the regular one), where: + // w_1 + w_4 - w_1_omega + q_m = 0 + // we use this gate to save an addition gate when adding or subtracting non-native field elements + // α * (q_arith - 2) * (w_1 + w_4 - w_1_omega + q_m) + let extra_small_addition_gate_identity := + mulmod( + addmod(q_arith, sub(p, 2), p), + addmod( + mload(QM_EVAL_LOC), + addmod( + sub(p, mload(W1_SHIFT_EVAL_LOC)), + addmod(mload(W1_EVAL_LOC), mload(W4_EVAL_LOC), p), + p + ), + p + ), + p + ) + + // Split up the two relations + let contribution_0 := + addmod(identity, mulmod(addmod(q_arith, sub(p, 1), p), mload(W4_SHIFT_EVAL_LOC), p), p) + contribution_0 := mulmod(mulmod(contribution_0, q_arith, p), mload(POW_PARTIAL_EVALUATION_LOC), p) + mstore(SUBRELATION_EVAL_6_LOC, contribution_0) + + let contribution_1 := mulmod(extra_small_addition_gate_identity, addmod(q_arith, sub(p, 1), p), p) + contribution_1 := mulmod(contribution_1, q_arith, p) + contribution_1 := mulmod(contribution_1, mload(POW_PARTIAL_EVALUATION_LOC), p) + mstore(SUBRELATION_EVAL_7_LOC, contribution_1) + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PERMUTATION RELATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + { + let beta := mload(BETA_CHALLENGE) + let gamma := mload(GAMMA_CHALLENGE) + + /** + * t1 = (W1 + gamma + beta * ID1) * (W2 + gamma + beta * ID2) + * t2 = (W3 + gamma + beta * ID3) * (W4 + gamma + beta * ID4) + * gp_numerator = t1 * t2 + * t1 = (W1 + gamma + beta * sigma_1_eval) * (W2 + gamma + beta * sigma_2_eval) + * t2 = (W3 + gamma + beta * sigma_3_eval) * (W4 + gamma + beta * sigma_4_eval) + * gp_denominator = t1 * t2 + */ + let t1 := + mulmod( + add(add(mload(W1_EVAL_LOC), gamma), mulmod(beta, mload(ID1_EVAL_LOC), p)), + add(add(mload(W2_EVAL_LOC), gamma), mulmod(beta, mload(ID2_EVAL_LOC), p)), + p + ) + let t2 := + mulmod( + add(add(mload(W3_EVAL_LOC), gamma), mulmod(beta, mload(ID3_EVAL_LOC), p)), + add(add(mload(W4_EVAL_LOC), gamma), mulmod(beta, mload(ID4_EVAL_LOC), p)), + p + ) + let numerator := mulmod(t1, t2, p) + t1 := mulmod( + add(add(mload(W1_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA1_EVAL_LOC), p)), + add(add(mload(W2_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA2_EVAL_LOC), p)), + p + ) + t2 := mulmod( + add(add(mload(W3_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA3_EVAL_LOC), p)), + add(add(mload(W4_EVAL_LOC), gamma), mulmod(beta, mload(SIGMA4_EVAL_LOC), p)), + p + ) + let denominator := mulmod(t1, t2, p) + + { + let acc := + mulmod(addmod(mload(Z_PERM_EVAL_LOC), mload(LAGRANGE_FIRST_EVAL_LOC), p), numerator, p) + + acc := addmod( + acc, + sub( + p, + mulmod( + addmod( + mload(Z_PERM_SHIFT_EVAL_LOC), + mulmod( + mload(LAGRANGE_LAST_EVAL_LOC), + mload(PUBLIC_INPUTS_DELTA_NUMERATOR_CHALLENGE), + p + ), + p + ), + denominator, + p + ) + ), + p + ) + + acc := mulmod(acc, mload(POW_PARTIAL_EVALUATION_LOC), p) + mstore(SUBRELATION_EVAL_0_LOC, acc) + + acc := mulmod( + mulmod(mload(LAGRANGE_LAST_EVAL_LOC), mload(Z_PERM_SHIFT_EVAL_LOC), p), + mload(POW_PARTIAL_EVALUATION_LOC), + p + ) + mstore(SUBRELATION_EVAL_1_LOC, acc) + } + + // Contribution 4: z_perm initialization (lagrange_first * z_perm = 0) + { + let acc := mulmod( + mulmod(mload(LAGRANGE_FIRST_EVAL_LOC), mload(Z_PERM_EVAL_LOC), p), + mload(POW_PARTIAL_EVALUATION_LOC), + p + ) + mstore(SUBRELATION_EVAL_2_LOC, acc) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* LOGUP WIDGET EVALUATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Note: Using beta powers for column batching and gamma for offset ensures soundness + // beta and gamma must be independent challenges (they come from splitting the same hash) + { + let gamma := mload(GAMMA_CHALLENGE) + let beta := mload(BETA_CHALLENGE) + // Compute beta powers inline (β², β³) for lookup column batching + let beta_sqr := mulmod(beta, beta, p) + let beta_cube := mulmod(beta_sqr, beta, p) + + // table_term = table_1 + γ + table_2 * β + table_3 * β² + table_4 * β³ + let t0 := + addmod(addmod(mload(TABLE1_EVAL_LOC), gamma, p), mulmod(mload(TABLE2_EVAL_LOC), beta, p), p) + let t1 := + addmod( + mulmod(mload(TABLE3_EVAL_LOC), beta_sqr, p), + mulmod(mload(TABLE4_EVAL_LOC), beta_cube, p), + p + ) + let table_term := addmod(t0, t1, p) + + // lookup_term = derived_entry_1 + γ + derived_entry_2 * β + derived_entry_3 * β² + q_index * β³ + t0 := addmod( + addmod(mload(W1_EVAL_LOC), gamma, p), + mulmod(mload(QR_EVAL_LOC), mload(W1_SHIFT_EVAL_LOC), p), + p + ) + t1 := addmod(mload(W2_EVAL_LOC), mulmod(mload(QM_EVAL_LOC), mload(W2_SHIFT_EVAL_LOC), p), p) + let t2 := addmod(mload(W3_EVAL_LOC), mulmod(mload(QC_EVAL_LOC), mload(W3_SHIFT_EVAL_LOC), p), p) + + let lookup_term := addmod(t0, mulmod(t1, beta, p), p) + lookup_term := addmod(lookup_term, mulmod(t2, beta_sqr, p), p) + lookup_term := addmod(lookup_term, mulmod(mload(QO_EVAL_LOC), beta_cube, p), p) + + let lookup_inverse := mulmod(mload(LOOKUP_INVERSES_EVAL_LOC), table_term, p) + let table_inverse := mulmod(mload(LOOKUP_INVERSES_EVAL_LOC), lookup_term, p) + + let inverse_exists_xor := addmod(mload(LOOKUP_READ_TAGS_EVAL_LOC), mload(QLOOKUP_EVAL_LOC), p) + inverse_exists_xor := addmod( + inverse_exists_xor, + sub(p, mulmod(mload(LOOKUP_READ_TAGS_EVAL_LOC), mload(QLOOKUP_EVAL_LOC), p)), + p + ) + + let accumulator_none := mulmod(mulmod(lookup_term, table_term, p), mload(LOOKUP_INVERSES_EVAL_LOC), p) + accumulator_none := addmod(accumulator_none, sub(p, inverse_exists_xor), p) + accumulator_none := mulmod(accumulator_none, mload(POW_PARTIAL_EVALUATION_LOC), p) + + let accumulator_one := mulmod(mload(QLOOKUP_EVAL_LOC), lookup_inverse, p) + accumulator_one := addmod( + accumulator_one, + sub(p, mulmod(mload(LOOKUP_READ_COUNTS_EVAL_LOC), table_inverse, p)), + p + ) + + let read_tag := mload(LOOKUP_READ_TAGS_EVAL_LOC) + let read_tag_boolean_relation := mulmod(read_tag, addmod(read_tag, sub(p, 1), p), p) + read_tag_boolean_relation := mulmod(read_tag_boolean_relation, mload(POW_PARTIAL_EVALUATION_LOC), p) + + mstore(SUBRELATION_EVAL_3_LOC, accumulator_none) + mstore(SUBRELATION_EVAL_4_LOC, accumulator_one) + mstore(SUBRELATION_EVAL_5_LOC, read_tag_boolean_relation) + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* DELTA RANGE RELATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + { + let minus_one := P_SUB_1 + let minus_two := P_SUB_2 + let minus_three := P_SUB_3 + + let delta_1 := addmod(mload(W2_EVAL_LOC), sub(p, mload(W1_EVAL_LOC)), p) + let delta_2 := addmod(mload(W3_EVAL_LOC), sub(p, mload(W2_EVAL_LOC)), p) + let delta_3 := addmod(mload(W4_EVAL_LOC), sub(p, mload(W3_EVAL_LOC)), p) + let delta_4 := addmod(mload(W1_SHIFT_EVAL_LOC), sub(p, mload(W4_EVAL_LOC)), p) + + { + let acc := delta_1 + acc := mulmod(acc, addmod(delta_1, minus_one, p), p) + acc := mulmod(acc, addmod(delta_1, minus_two, p), p) + acc := mulmod(acc, addmod(delta_1, minus_three, p), p) + acc := mulmod(acc, mload(QRANGE_EVAL_LOC), p) + acc := mulmod(acc, mload(POW_PARTIAL_EVALUATION_LOC), p) + mstore(SUBRELATION_EVAL_8_LOC, acc) + } + + { + let acc := delta_2 + acc := mulmod(acc, addmod(delta_2, minus_one, p), p) + acc := mulmod(acc, addmod(delta_2, minus_two, p), p) + acc := mulmod(acc, addmod(delta_2, minus_three, p), p) + acc := mulmod(acc, mload(QRANGE_EVAL_LOC), p) + acc := mulmod(acc, mload(POW_PARTIAL_EVALUATION_LOC), p) + mstore(SUBRELATION_EVAL_9_LOC, acc) + } + + { + let acc := delta_3 + acc := mulmod(acc, addmod(delta_3, minus_one, p), p) + acc := mulmod(acc, addmod(delta_3, minus_two, p), p) + acc := mulmod(acc, addmod(delta_3, minus_three, p), p) + acc := mulmod(acc, mload(QRANGE_EVAL_LOC), p) + acc := mulmod(acc, mload(POW_PARTIAL_EVALUATION_LOC), p) + mstore(SUBRELATION_EVAL_10_LOC, acc) + } + + { + let acc := delta_4 + acc := mulmod(acc, addmod(delta_4, minus_one, p), p) + acc := mulmod(acc, addmod(delta_4, minus_two, p), p) + acc := mulmod(acc, addmod(delta_4, minus_three, p), p) + acc := mulmod(acc, mload(QRANGE_EVAL_LOC), p) + acc := mulmod(acc, mload(POW_PARTIAL_EVALUATION_LOC), p) + mstore(SUBRELATION_EVAL_11_LOC, acc) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ELLIPTIC CURVE RELATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + { + // Contribution 10 point addition, x-coordinate check + // q_elliptic * (x3 + x2 + x1)(x2 - x1)(x2 - x1) - y2^2 - y1^2 + 2(y2y1)*q_sign = 0 + let x_diff := addmod(mload(EC_X_2), sub(p, mload(EC_X_1)), p) + let y1_sqr := mulmod(mload(EC_Y_1), mload(EC_Y_1), p) + { + let y2_sqr := mulmod(mload(EC_Y_2), mload(EC_Y_2), p) + let y1y2 := mulmod(mulmod(mload(EC_Y_1), mload(EC_Y_2), p), mload(EC_Q_SIGN), p) + let x_add_identity := addmod(mload(EC_X_3), addmod(mload(EC_X_2), mload(EC_X_1), p), p) + x_add_identity := mulmod(mulmod(x_add_identity, x_diff, p), x_diff, p) + x_add_identity := addmod(x_add_identity, sub(p, y2_sqr), p) + x_add_identity := addmod(x_add_identity, sub(p, y1_sqr), p) + x_add_identity := addmod(x_add_identity, y1y2, p) + x_add_identity := addmod(x_add_identity, y1y2, p) + + let eval := mulmod(x_add_identity, mload(POW_PARTIAL_EVALUATION_LOC), p) + eval := mulmod(eval, mload(QELLIPTIC_EVAL_LOC), p) + eval := mulmod(eval, addmod(1, sub(p, mload(EC_Q_IS_DOUBLE)), p), p) + mstore(SUBRELATION_EVAL_12_LOC, eval) + } + + { + let y1_plus_y3 := addmod(mload(EC_Y_1), mload(EC_Y_3), p) + let y_diff := mulmod(mload(EC_Y_2), mload(EC_Q_SIGN), p) + y_diff := addmod(y_diff, sub(p, mload(EC_Y_1)), p) + let y_add_identity := mulmod(y1_plus_y3, x_diff, p) + y_add_identity := addmod( + y_add_identity, + mulmod(addmod(mload(EC_X_3), sub(p, mload(EC_X_1)), p), y_diff, p), + p + ) + + let eval := mulmod(y_add_identity, mload(POW_PARTIAL_EVALUATION_LOC), p) + eval := mulmod(eval, mload(QELLIPTIC_EVAL_LOC), p) + eval := mulmod(eval, addmod(1, sub(p, mload(EC_Q_IS_DOUBLE)), p), p) + mstore(SUBRELATION_EVAL_13_LOC, eval) + } + + { + let x_pow_4 := mulmod(addmod(y1_sqr, GRUMPKIN_CURVE_B_PARAMETER_NEGATED, p), mload(EC_X_1), p) + let y1_sqr_mul_4 := addmod(y1_sqr, y1_sqr, p) + y1_sqr_mul_4 := addmod(y1_sqr_mul_4, y1_sqr_mul_4, p) + + let x1_pow_4_mul_9 := mulmod(x_pow_4, 9, p) + + let ep_x_double_identity := addmod(mload(EC_X_3), addmod(mload(EC_X_1), mload(EC_X_1), p), p) + ep_x_double_identity := mulmod(ep_x_double_identity, y1_sqr_mul_4, p) + ep_x_double_identity := addmod(ep_x_double_identity, sub(p, x1_pow_4_mul_9), p) + + let acc := mulmod(ep_x_double_identity, mload(POW_PARTIAL_EVALUATION_LOC), p) + acc := mulmod(mulmod(acc, mload(QELLIPTIC_EVAL_LOC), p), mload(EC_Q_IS_DOUBLE), p) + acc := addmod(acc, mload(SUBRELATION_EVAL_12_LOC), p) + + // Add to existing contribution - and double check that numbers here + mstore(SUBRELATION_EVAL_12_LOC, acc) + } + + { + let x1_sqr_mul_3 := + mulmod(addmod(addmod(mload(EC_X_1), mload(EC_X_1), p), mload(EC_X_1), p), mload(EC_X_1), p) + let y_double_identity := + mulmod(x1_sqr_mul_3, addmod(mload(EC_X_1), sub(p, mload(EC_X_3)), p), p) + y_double_identity := addmod( + y_double_identity, + sub( + p, + mulmod( + addmod(mload(EC_Y_1), mload(EC_Y_1), p), + addmod(mload(EC_Y_1), mload(EC_Y_3), p), + p + ) + ), + p + ) + + let acc := mulmod(y_double_identity, mload(POW_PARTIAL_EVALUATION_LOC), p) + acc := mulmod(mulmod(acc, mload(QELLIPTIC_EVAL_LOC), p), mload(EC_Q_IS_DOUBLE), p) + acc := addmod(acc, mload(SUBRELATION_EVAL_13_LOC), p) + + // Add to existing contribution - and double check that numbers here + mstore(SUBRELATION_EVAL_13_LOC, acc) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MEMORY RELATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + { + { + /** + * MEMORY + * + * A RAM memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * t: `timestamp` of memory cell being accessed (used for RAM, set to 0 for ROM) + * * v: `value` of memory cell being accessed + * * a: `access` type of record. read: 0 = read, 1 = write + * * r: `record` of memory cell. record = access + index * eta + timestamp * eta_two + value * eta_three + * + * A ROM memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * v: `value1` of memory cell being accessed (ROM tables can store up to 2 values per index) + * * v2:`value2` of memory cell being accessed (ROM tables can store up to 2 values per index) + * * r: `record` of memory cell. record = index * eta + value2 * eta_two + value1 * eta_three + * + * When performing a read/write access, the values of i, t, v, v2, a, r are stored in the following wires + + * selectors, depending on whether the gate is a RAM read/write or a ROM read + * + * | gate type | i | v2/t | v | a | r | + * | --------- | -- | ----- | -- | -- | -- | + * | ROM | w1 | w2 | w3 | -- | w4 | + * | RAM | w1 | w2 | w3 | qc | w4 | + * + * (for accesses where `index` is a circuit constant, it is assumed the circuit will apply a copy constraint on + * `w2` to fix its value) + * + * + */ + + /** + * Memory Record Check + * Partial degree: 1 + * Total degree: 4 + * + * A ROM/ROM access gate can be evaluated with the identity: + * + * qc + w1 \eta + w2 \eta_two + w3 \eta_three - w4 = 0 + * + * For ROM gates, qc = 0 + */ + /** + * memory_record_check = w_3 * eta_three; + * memory_record_check += w_2 * eta_two; + * memory_record_check += w_1 * eta; + * memory_record_check += q_c; + * + * partial_record_check = memory_record_check; + * + * memory_record_check -= w_4; + */ + let memory_record_check := mulmod(mload(W3_EVAL_LOC), mload(ETA_THREE_CHALLENGE), p) + memory_record_check := addmod( + memory_record_check, + mulmod(mload(W2_EVAL_LOC), mload(ETA_TWO_CHALLENGE), p), + p + ) + memory_record_check := addmod( + memory_record_check, + mulmod(mload(W1_EVAL_LOC), mload(ETA_CHALLENGE), p), + p + ) + memory_record_check := addmod(memory_record_check, mload(QC_EVAL_LOC), p) + + let partial_record_check := memory_record_check + memory_record_check := addmod(memory_record_check, sub(p, mload(W4_EVAL_LOC)), p) + + mstore(AUX_MEMORY_CHECK_IDENTITY, memory_record_check) + + /** + * ROM Consistency Check + * Partial degree: 1 + * Total degree: 4 + * + * For every ROM read, a set equivalence check is applied between the record witnesses, and a second set of + * records that are sorted. + * + * We apply the following checks for the sorted records: + * + * 1. w1, w2, w3 correctly map to 'index', 'v1, 'v2' for a given record value at w4 + * 2. index values for adjacent records are monotonically increasing + * 3. if, at gate i, index_i == index_{i + 1}, then value1_i == value1_{i + 1} and value2_i == value2_{i + 1} + * + */ + // index_delta = w_1_omega - w_1 + let index_delta := addmod(mload(W1_SHIFT_EVAL_LOC), sub(p, mload(W1_EVAL_LOC)), p) + + // record_delta = w_4_omega - w_4 + let record_delta := addmod(mload(W4_SHIFT_EVAL_LOC), sub(p, mload(W4_EVAL_LOC)), p) + + // index_is_monotonically_increasing = index_delta * (index_delta - 1) + let index_is_monotonically_increasing := mulmod(index_delta, addmod(index_delta, P_SUB_1, p), p) + + // adjacent_values_match_if_adjacent_indices_match = record_delta * (1 - index_delta) + let adjacent_values_match_if_adjacent_indices_match := + mulmod(record_delta, addmod(1, sub(p, index_delta), p), p) + + mstore( + SUBRELATION_EVAL_15_LOC, + mulmod( + adjacent_values_match_if_adjacent_indices_match, + mulmod( + mload(QL_EVAL_LOC), + mulmod( + mload(QR_EVAL_LOC), + mulmod(mload(QMEMORY_EVAL_LOC), mload(POW_PARTIAL_EVALUATION_LOC), p), + p + ), + p + ), + p + ) + ) + + // ROM_CONSISTENCY_CHECK_2 + mstore( + SUBRELATION_EVAL_16_LOC, + mulmod( + index_is_monotonically_increasing, + mulmod( + mload(QL_EVAL_LOC), + mulmod( + mload(QR_EVAL_LOC), + mulmod(mload(QMEMORY_EVAL_LOC), mload(POW_PARTIAL_EVALUATION_LOC), p), + p + ), + p + ), + p + ) + ) + + mstore( + AUX_ROM_CONSISTENCY_CHECK_IDENTITY, + mulmod(memory_record_check, mulmod(mload(QL_EVAL_LOC), mload(QR_EVAL_LOC), p), p) + ) + + { + /** + * RAM Consistency Check + * + * The 'access' type of the record is extracted with the expression `w_4 - ap.partial_record_check` + * (i.e. for an honest Prover `w1 * eta + w2 * eta^2 + w3 * eta^3 - w4 = access`. + * This is validated by requiring `access` to be boolean + * + * For two adjacent entries in the sorted list if _both_ + * A) index values match + * B) adjacent access value is 0 (i.e. next gate is a READ) + * then + * C) both values must match. + * The gate boolean check is + * (A && B) => C === !(A && B) || C === !A || !B || C + * + * N.B. it is the responsibility of the circuit writer to ensure that every RAM cell is initialized + * with a WRITE operation. + */ + /** + * next_gate_access_type = w_3_shift * eta_three; + * next_gate_access_type += (w_2_shift * eta_two); + * next_gate_access_type += (w_1_shift * eta); + * next_gate_access_type += w_4_shift; + * next_gate_access_type *= eta; + * next_gate_access_type = w_4_omega - next_gate_access_type; + */ + let next_gate_access_type := mulmod(mload(W3_SHIFT_EVAL_LOC), mload(ETA_THREE_CHALLENGE), p) + next_gate_access_type := addmod( + next_gate_access_type, + mulmod(mload(W2_SHIFT_EVAL_LOC), mload(ETA_TWO_CHALLENGE), p), + p + ) + next_gate_access_type := addmod( + next_gate_access_type, + mulmod(mload(W1_SHIFT_EVAL_LOC), mload(ETA_CHALLENGE), p), + p + ) + next_gate_access_type := addmod(mload(W4_SHIFT_EVAL_LOC), sub(p, next_gate_access_type), p) + + // value_delta = w_3_omega - w_3 + let value_delta := addmod(mload(W3_SHIFT_EVAL_LOC), sub(p, mload(W3_EVAL_LOC)), p) + // adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = (1 - index_delta) * value_delta * (1 - next_gate_access_type); + + let adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation := + mulmod( + addmod(1, sub(p, index_delta), p), + mulmod(value_delta, addmod(1, sub(p, next_gate_access_type), p), p), + p + ) + + // We can't apply the RAM consistency check identity on the final entry in the sorted list (the wires in the + // next gate would make the identity fail). We need to validate that its 'access type' bool is correct. Can't + // do with an arithmetic gate because of the `eta` factors. We need to check that the *next* gate's access + // type is correct, to cover this edge case + // deg 2 or 4 + /** + * access_type = w_4 - partial_record_check + * access_check = access_type^2 - access_type + * next_gate_access_type_is_boolean = next_gate_access_type^2 - next_gate_access_type + */ + let access_type := addmod(mload(W4_EVAL_LOC), sub(p, partial_record_check), p) + let access_check := mulmod(access_type, addmod(access_type, P_SUB_1, p), p) + let next_gate_access_type_is_boolean := + mulmod(next_gate_access_type, addmod(next_gate_access_type, P_SUB_1, p), p) + + // scaled_activation_selector = q_arith * q_aux * alpha + let scaled_activation_selector := + mulmod( + mload(QO_EVAL_LOC), + mulmod(mload(QMEMORY_EVAL_LOC), mload(POW_PARTIAL_EVALUATION_LOC), p), + p + ) + + mstore( + SUBRELATION_EVAL_17_LOC, + mulmod( + adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation, + scaled_activation_selector, + p + ) + ) + + mstore( + SUBRELATION_EVAL_18_LOC, + mulmod(index_is_monotonically_increasing, scaled_activation_selector, p) + ) + + mstore( + SUBRELATION_EVAL_19_LOC, + mulmod(next_gate_access_type_is_boolean, scaled_activation_selector, p) + ) + + mstore(AUX_RAM_CONSISTENCY_CHECK_IDENTITY, mulmod(access_check, mload(QO_EVAL_LOC), p)) + } + + { + // timestamp_delta = w_2_omega - w_2 + let timestamp_delta := addmod(mload(W2_SHIFT_EVAL_LOC), sub(p, mload(W2_EVAL_LOC)), p) + + // RAM_timestamp_check_identity = (1 - index_delta) * timestamp_delta - w_3 + let RAM_TIMESTAMP_CHECK_IDENTITY := + addmod( + mulmod(timestamp_delta, addmod(1, sub(p, index_delta), p), p), + sub(p, mload(W3_EVAL_LOC)), + p + ) + + /** + * memory_identity = ROM_consistency_check_identity; + * memory_identity += RAM_timestamp_check_identity * q_4; + * memory_identity += memory_record_check * q_m; + * memory_identity *= q_1; + * memory_identity += (RAM_consistency_check_identity * q_arith); + * + * auxiliary_identity = memory_identity + non_native_field_identity + limb_accumulator_identity; + * auxiliary_identity *= q_aux; + * auxiliary_identity *= alpha_base; + */ + let memory_identity := mload(AUX_ROM_CONSISTENCY_CHECK_IDENTITY) + memory_identity := addmod( + memory_identity, + mulmod( + RAM_TIMESTAMP_CHECK_IDENTITY, + mulmod(mload(Q4_EVAL_LOC), mload(QL_EVAL_LOC), p), + p + ), + p + ) + + memory_identity := addmod( + memory_identity, + mulmod( + mload(AUX_MEMORY_CHECK_IDENTITY), + mulmod(mload(QM_EVAL_LOC), mload(QL_EVAL_LOC), p), + p + ), + p + ) + memory_identity := addmod(memory_identity, mload(AUX_RAM_CONSISTENCY_CHECK_IDENTITY), p) + + memory_identity := mulmod( + memory_identity, + mulmod(mload(QMEMORY_EVAL_LOC), mload(POW_PARTIAL_EVALUATION_LOC), p), + p + ) + mstore(SUBRELATION_EVAL_14_LOC, memory_identity) + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ROM LOGUP RELATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + { + // Single-value ROM tables. Wire layout: (w_1, w_2, w_3, w_4) = (index, value, multiplicity, + // inverse), q_c = array id. q_logup_table = q_2 * (1 - q_1), q_logup_read = q_4 * (1 - q_1). + let one_minus_q1 := addmod(1, sub(p, mload(QL_EVAL_LOC)), p) + let q_logup_table := mulmod(mload(QR_EVAL_LOC), one_minus_q1, p) + let q_logup_read := mulmod(mload(Q4_EVAL_LOC), one_minus_q1, p) + + // denom = rom_logup_gamma + w_1 + eta * w_2 + eta_two * q_c + let denom := mload(ROM_LOGUP_GAMMA_CHALLENGE) + denom := addmod(denom, mload(W1_EVAL_LOC), p) + denom := addmod(denom, mulmod(mload(ETA_CHALLENGE), mload(W2_EVAL_LOC), p), p) + denom := addmod(denom, mulmod(mload(ETA_TWO_CHALLENGE), mload(QC_EVAL_LOC), p), p) + + // Inverse correctness: q_logup_any * (w_4 * denom - 1) * q_memory + let inverse_correctness := addmod(mulmod(mload(W4_EVAL_LOC), denom, p), sub(p, 1), p) + inverse_correctness := mulmod(addmod(q_logup_table, q_logup_read, p), inverse_correctness, p) + inverse_correctness := + mulmod( + inverse_correctness, + mulmod(mload(QMEMORY_EVAL_LOC), mload(POW_PARTIAL_EVALUATION_LOC), p), + p + ) + mstore(SUBRELATION_EVAL_20_LOC, inverse_correctness) + + // LogUp sum identity. Linearly dependent, so not scaled by the pow evaluation. + let logup_sum := addmod(q_logup_read, sub(p, mulmod(q_logup_table, mload(W3_EVAL_LOC), p)), p) + logup_sum := mulmod(logup_sum, mload(W4_EVAL_LOC), p) + logup_sum := mulmod(logup_sum, mload(QMEMORY_EVAL_LOC), p) + mstore(SUBRELATION_EVAL_21_LOC, logup_sum) + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* NON NATIVE FIELD RELATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + { + /** + * Non native field arithmetic gate 2 + * _ _ + * / _ _ _ 14 \ + * q_2 . q_4 | (w_1 . w_2) + (w_1 . w_2) + (w_1 . w_4 + w_2 . w_3 - w_3) . 2 - w_3 - w_4 | + * \_ _/ + * + * limb_subproduct = w_1 . w_2_shift + w_1_shift . w_2 + * non_native_field_gate_2 = w_1 * w_4 + w_2 * w_3 - w_3_shift + * non_native_field_gate_2 = non_native_field_gate_2 * limb_size + * non_native_field_gate_2 -= w_4_shift + * non_native_field_gate_2 += limb_subproduct + * non_native_field_gate_2 *= q_4 + * limb_subproduct *= limb_size + * limb_subproduct += w_1_shift * w_2 + * non_native_field_gate_1 = (limb_subproduct + w_3 + w_4) * q_3 + * non_native_field_gate_3 = (limb_subproduct + w_4 - (w_3_shift + w_4_shift)) * q_m + * non_native_field_identity = (non_native_field_gate_1 + non_native_field_gate_2 + non_native_field_gate_3) * q_2 + */ + let limb_subproduct := + addmod( + mulmod(mload(W1_EVAL_LOC), mload(W2_SHIFT_EVAL_LOC), p), + mulmod(mload(W1_SHIFT_EVAL_LOC), mload(W2_EVAL_LOC), p), + p + ) + + let non_native_field_gate_2 := + addmod( + addmod( + mulmod(mload(W1_EVAL_LOC), mload(W4_EVAL_LOC), p), + mulmod(mload(W2_EVAL_LOC), mload(W3_EVAL_LOC), p), + p + ), + sub(p, mload(W3_SHIFT_EVAL_LOC)), + p + ) + non_native_field_gate_2 := mulmod(non_native_field_gate_2, LIMB_SIZE, p) + non_native_field_gate_2 := addmod(non_native_field_gate_2, sub(p, mload(W4_SHIFT_EVAL_LOC)), p) + non_native_field_gate_2 := addmod(non_native_field_gate_2, limb_subproduct, p) + non_native_field_gate_2 := mulmod(non_native_field_gate_2, mload(Q4_EVAL_LOC), p) + + limb_subproduct := mulmod(limb_subproduct, LIMB_SIZE, p) + limb_subproduct := addmod( + limb_subproduct, + mulmod(mload(W1_SHIFT_EVAL_LOC), mload(W2_SHIFT_EVAL_LOC), p), + p + ) + + let non_native_field_gate_1 := + mulmod( + addmod(limb_subproduct, sub(p, addmod(mload(W3_EVAL_LOC), mload(W4_EVAL_LOC), p)), p), + mload(QO_EVAL_LOC), + p + ) + + let non_native_field_gate_3 := + mulmod( + addmod( + addmod(limb_subproduct, mload(W4_EVAL_LOC), p), + sub(p, addmod(mload(W3_SHIFT_EVAL_LOC), mload(W4_SHIFT_EVAL_LOC), p)), + p + ), + mload(QM_EVAL_LOC), + p + ) + let non_native_field_identity := + mulmod( + addmod( + addmod(non_native_field_gate_1, non_native_field_gate_2, p), + non_native_field_gate_3, + p + ), + mload(QR_EVAL_LOC), + p + ) + + mstore(AUX_NON_NATIVE_FIELD_IDENTITY, non_native_field_identity) + } + + { + /** + * limb_accumulator_1 = w_2_omega; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_1_omega; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_3; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_2; + * limb_accumulator_1 *= SUBLIMB_SHIFT; + * limb_accumulator_1 += w_1; + * limb_accumulator_1 -= w_4; + * limb_accumulator_1 *= q_4; + */ + let limb_accumulator_1 := mulmod(mload(W2_SHIFT_EVAL_LOC), SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W1_SHIFT_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W3_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W2_EVAL_LOC), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, SUBLIMB_SHIFT, p) + limb_accumulator_1 := addmod(limb_accumulator_1, mload(W1_EVAL_LOC), p) + limb_accumulator_1 := addmod(limb_accumulator_1, sub(p, mload(W4_EVAL_LOC)), p) + limb_accumulator_1 := mulmod(limb_accumulator_1, mload(Q4_EVAL_LOC), p) + + /** + * limb_accumulator_2 = w_3_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_2_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_1_omega; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_4; + * limb_accumulator_2 *= SUBLIMB_SHIFT; + * limb_accumulator_2 += w_3; + * limb_accumulator_2 -= w_4_omega; + * limb_accumulator_2 *= q_m; + */ + let limb_accumulator_2 := mulmod(mload(W3_SHIFT_EVAL_LOC), SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W2_SHIFT_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W1_SHIFT_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W4_EVAL_LOC), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, SUBLIMB_SHIFT, p) + limb_accumulator_2 := addmod(limb_accumulator_2, mload(W3_EVAL_LOC), p) + limb_accumulator_2 := addmod(limb_accumulator_2, sub(p, mload(W4_SHIFT_EVAL_LOC)), p) + limb_accumulator_2 := mulmod(limb_accumulator_2, mload(QM_EVAL_LOC), p) + + let limb_accumulator_identity := addmod(limb_accumulator_1, limb_accumulator_2, p) + limb_accumulator_identity := mulmod(limb_accumulator_identity, mload(QO_EVAL_LOC), p) + + let nnf_identity := addmod(mload(AUX_NON_NATIVE_FIELD_IDENTITY), limb_accumulator_identity, p) + nnf_identity := mulmod( + nnf_identity, + mulmod(mload(QNNF_EVAL_LOC), mload(POW_PARTIAL_EVALUATION_LOC), p), + p + ) + + mstore(SUBRELATION_EVAL_22_LOC, nnf_identity) + } + + /* + * Poseidon External Relation + */ + { + let s1 := addmod(mload(W1_EVAL_LOC), mload(QL_EVAL_LOC), p) + let s2 := addmod(mload(W2_EVAL_LOC), mload(QR_EVAL_LOC), p) + let s3 := addmod(mload(W3_EVAL_LOC), mload(QO_EVAL_LOC), p) + let s4 := addmod(mload(W4_EVAL_LOC), mload(Q4_EVAL_LOC), p) + + // u1 := s1 * s1 * s1 * s1 * s1; + let t0 := mulmod(s1, s1, p) + let u1 := mulmod(t0, mulmod(t0, s1, p), p) + + // u2 := s2 * s2 * s2 * s2 * s2; + t0 := mulmod(s2, s2, p) + let u2 := mulmod(t0, mulmod(t0, s2, p), p) + + // u3 := s3 * s3 * s3 * s3 * s3; + t0 := mulmod(s3, s3, p) + let u3 := mulmod(t0, mulmod(t0, s3, p), p) + + // u4 := s4 * s4 * s4 * s4 * s4; + t0 := mulmod(s4, s4, p) + let u4 := mulmod(t0, mulmod(t0, s4, p), p) + + // matrix mul v = M_E * u with 14 additions + t0 := addmod(u1, u2, p) + let t1 := addmod(u3, u4, p) + + let t2 := addmod(u2, u2, p) + t2 := addmod(t2, t1, p) + + let t3 := addmod(u4, u4, p) + t3 := addmod(t3, t0, p) + + let v4 := addmod(t1, t1, p) + v4 := addmod(v4, v4, p) + v4 := addmod(v4, t3, p) + + let v2 := addmod(t0, t0, p) + v2 := addmod(v2, v2, p) + v2 := addmod(v2, t2, p) + + let v1 := addmod(t3, v2, p) + let v3 := addmod(t2, v4, p) + + let q_pos_by_scaling := + mulmod(mload(QPOSEIDON2_EXTERNAL_EVAL_LOC), mload(POW_PARTIAL_EVALUATION_LOC), p) + + mstore( + SUBRELATION_EVAL_23_LOC, + mulmod(q_pos_by_scaling, addmod(v1, sub(p, mload(W1_SHIFT_EVAL_LOC)), p), p) + ) + + mstore( + SUBRELATION_EVAL_24_LOC, + mulmod(q_pos_by_scaling, addmod(v2, sub(p, mload(W2_SHIFT_EVAL_LOC)), p), p) + ) + + mstore( + SUBRELATION_EVAL_25_LOC, + mulmod(q_pos_by_scaling, addmod(v3, sub(p, mload(W3_SHIFT_EVAL_LOC)), p), p) + ) + + mstore( + SUBRELATION_EVAL_26_LOC, + mulmod(q_pos_by_scaling, addmod(v4, sub(p, mload(W4_SHIFT_EVAL_LOC)), p), p) + ) + } + + /* + * Poseidon Internal Relation + */ + { + let s1 := addmod(mload(W1_EVAL_LOC), mload(QL_EVAL_LOC), p) + + // apply s-box round + let t0 := mulmod(s1, s1, p) + let u1 := mulmod(t0, mulmod(t0, s1, p), p) + let u2 := mload(W2_EVAL_LOC) + let u3 := mload(W3_EVAL_LOC) + let u4 := mload(W4_EVAL_LOC) + + // matrix mul v = M_I * u 4 muls and 7 additions + let u_sum := addmod(u1, u2, p) + u_sum := addmod(u_sum, addmod(u3, u4, p), p) + + let q_pos_by_scaling := + mulmod(mload(QPOSEIDON2_INTERNAL_EVAL_LOC), mload(POW_PARTIAL_EVALUATION_LOC), p) + + let v1 := addmod(mulmod(u1, POS_INTERNAL_MATRIX_D_0, p), u_sum, p) + + mstore( + SUBRELATION_EVAL_27_LOC, + mulmod(q_pos_by_scaling, addmod(v1, sub(p, mload(W1_SHIFT_EVAL_LOC)), p), p) + ) + let v2 := addmod(mulmod(u2, POS_INTERNAL_MATRIX_D_1, p), u_sum, p) + + mstore( + SUBRELATION_EVAL_28_LOC, + mulmod(q_pos_by_scaling, addmod(v2, sub(p, mload(W2_SHIFT_EVAL_LOC)), p), p) + ) + let v3 := addmod(mulmod(u3, POS_INTERNAL_MATRIX_D_2, p), u_sum, p) + + mstore( + SUBRELATION_EVAL_29_LOC, + mulmod(q_pos_by_scaling, addmod(v3, sub(p, mload(W3_SHIFT_EVAL_LOC)), p), p) + ) + + let v4 := addmod(mulmod(u4, POS_INTERNAL_MATRIX_D_3, p), u_sum, p) + mstore( + SUBRELATION_EVAL_30_LOC, + mulmod(q_pos_by_scaling, addmod(v4, sub(p, mload(W4_SHIFT_EVAL_LOC)), p), p) + ) + } + + // Scale and batch subrelations by subrelation challenges + // linear combination of subrelations + let accumulator := mload(SUBRELATION_EVAL_0_LOC) + + // Below is an unrolled variant of the following loop + // for (uint256 i = 1; i < NUMBER_OF_SUBRELATIONS; ++i) { + // accumulator = accumulator + evaluations[i] * subrelationChallenges[i - 1]; + // } + + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_1_LOC), mload(ALPHA_CHALLENGE_0), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_2_LOC), mload(ALPHA_CHALLENGE_1), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_3_LOC), mload(ALPHA_CHALLENGE_2), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_4_LOC), mload(ALPHA_CHALLENGE_3), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_5_LOC), mload(ALPHA_CHALLENGE_4), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_6_LOC), mload(ALPHA_CHALLENGE_5), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_7_LOC), mload(ALPHA_CHALLENGE_6), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_8_LOC), mload(ALPHA_CHALLENGE_7), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_9_LOC), mload(ALPHA_CHALLENGE_8), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_10_LOC), mload(ALPHA_CHALLENGE_9), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_11_LOC), mload(ALPHA_CHALLENGE_10), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_12_LOC), mload(ALPHA_CHALLENGE_11), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_13_LOC), mload(ALPHA_CHALLENGE_12), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_14_LOC), mload(ALPHA_CHALLENGE_13), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_15_LOC), mload(ALPHA_CHALLENGE_14), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_16_LOC), mload(ALPHA_CHALLENGE_15), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_17_LOC), mload(ALPHA_CHALLENGE_16), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_18_LOC), mload(ALPHA_CHALLENGE_17), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_19_LOC), mload(ALPHA_CHALLENGE_18), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_20_LOC), mload(ALPHA_CHALLENGE_19), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_21_LOC), mload(ALPHA_CHALLENGE_20), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_22_LOC), mload(ALPHA_CHALLENGE_21), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_23_LOC), mload(ALPHA_CHALLENGE_22), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_24_LOC), mload(ALPHA_CHALLENGE_23), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_25_LOC), mload(ALPHA_CHALLENGE_24), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_26_LOC), mload(ALPHA_CHALLENGE_25), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_27_LOC), mload(ALPHA_CHALLENGE_26), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_28_LOC), mload(ALPHA_CHALLENGE_27), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_29_LOC), mload(ALPHA_CHALLENGE_28), p), + p + ) + accumulator := addmod( + accumulator, + mulmod(mload(SUBRELATION_EVAL_30_LOC), mload(ALPHA_CHALLENGE_29), p), + p + ) + + let sumcheck_valid := eq(accumulator, mload(FINAL_ROUND_TARGET_LOC)) + + if iszero(sumcheck_valid) { + mstore(0x00, SUMCHECK_FAILED_SELECTOR) + revert(0x00, 0x04) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SUMCHECK -- Complete */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SHPLEMINI */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // ============= SHPLEMINI INVERSES ============== + // Inverses were computed in the unified batch inversion above. + let unshifted_scalar := 0 + let shifted_scalar := 0 + { + // staging[0] = 1/gemini_r -- needed for shifted_scalar computation + let gemini_r_inv := mload(GEMINI_R_INV_LOC) + + // staging[1..3*LOG_N] maps contiguously to: + // INVERTED_CHALLENGE_POW_MINUS_U_0..14 + // POS_INVERTED_DENOM_0..14 + // NEG_INVERTED_DENOM_0..14 + // Total: 3*LOG_N + + // Compute unshifted_scalar and shifted_scalar using the copied inverses + let pos_inverted_denominator := mload(POS_INVERTED_DENOM_0_LOC) + let neg_inverted_denominator := mload(NEG_INVERTED_DENOM_0_LOC) + let shplonk_nu := mload(SHPLONK_NU_CHALLENGE) + + unshifted_scalar := addmod(pos_inverted_denominator, mulmod(shplonk_nu, neg_inverted_denominator, p), p) + + shifted_scalar := mulmod( + gemini_r_inv, // (1 / gemini_r_challenge) from staging[0] + // (inverse_vanishing_evals[0]) - (shplonk_nu * inverse_vanishing_evals[1]) + addmod( + pos_inverted_denominator, + // - (shplonk_nu * inverse_vanishing_evals[1]) + sub(p, mulmod(shplonk_nu, neg_inverted_denominator, p)), + p + ), + p + ) + } + + // Commitment Accumulation (MSM via sequential ecAdd/ecMul): + // For each commitment C_i with batch scalar s_i, we compute: + // accumulator += s_i * C_i + // The commitments include: shplonk_Q, VK points, wire commitments, + // lookup commitments, Z_PERM, gemini fold univariates. + // The KZG quotient is handled separately. + // The final accumulator is the LHS of the pairing equation. + + // Accumulators + let batching_challenge := 1 + let batched_evaluation := 0 + + let neg_unshifted_scalar := sub(p, unshifted_scalar) + let neg_shifted_scalar := sub(p, shifted_scalar) + + let rho := mload(RHO_CHALLENGE) + + // Unrolled for the loop below - where NUMBER_UNSHIFTED = 36 + // for (uint256 i = 1; i <= NUMBER_UNSHIFTED; ++i) { + // scalars[i] = mem.unshiftedScalar.neg() * mem.batchingChallenge; + // mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[i - 1] * mem.batchingChallenge); + // mem.batchingChallenge = mem.batchingChallenge * tp.rho; + // } + + // Iteration order matches UltraFlavor_Generated::EntityId. Scalar slot N = entity index N + 1 + // pairs with vk[N] in the batchMul block below. + + // 0: SIGMA1_EVAL_LOC + mstore(BATCH_SCALAR_1_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(SIGMA1_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 1: SIGMA2_EVAL_LOC + mstore(BATCH_SCALAR_2_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(SIGMA2_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 2: SIGMA3_EVAL_LOC + mstore(BATCH_SCALAR_3_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(SIGMA3_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 3: SIGMA4_EVAL_LOC + mstore(BATCH_SCALAR_4_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(SIGMA4_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 4: ID1_EVAL_LOC + mstore(BATCH_SCALAR_5_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(ID1_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 5: ID2_EVAL_LOC + mstore(BATCH_SCALAR_6_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(ID2_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 6: ID3_EVAL_LOC + mstore(BATCH_SCALAR_7_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(ID3_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 7: ID4_EVAL_LOC + mstore(BATCH_SCALAR_8_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(ID4_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 8: LAGRANGE_FIRST_EVAL_LOC + mstore(BATCH_SCALAR_9_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(LAGRANGE_FIRST_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 9: LAGRANGE_LAST_EVAL_LOC + mstore(BATCH_SCALAR_10_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(LAGRANGE_LAST_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 10: QLOOKUP_EVAL_LOC + mstore(BATCH_SCALAR_11_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QLOOKUP_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 11: TABLE1_EVAL_LOC + mstore(BATCH_SCALAR_12_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(TABLE1_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 12: TABLE2_EVAL_LOC + mstore(BATCH_SCALAR_13_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(TABLE2_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 13: TABLE3_EVAL_LOC + mstore(BATCH_SCALAR_14_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(TABLE3_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 14: TABLE4_EVAL_LOC + mstore(BATCH_SCALAR_15_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(TABLE4_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 15: QM_EVAL_LOC + mstore(BATCH_SCALAR_16_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QM_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 16: QR_EVAL_LOC + mstore(BATCH_SCALAR_17_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QR_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 17: QO_EVAL_LOC + mstore(BATCH_SCALAR_18_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QO_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 18: QC_EVAL_LOC + mstore(BATCH_SCALAR_19_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QC_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 19: QL_EVAL_LOC + mstore(BATCH_SCALAR_20_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QL_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 20: Q4_EVAL_LOC + mstore(BATCH_SCALAR_21_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(Q4_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 21: QARITH_EVAL_LOC + mstore(BATCH_SCALAR_22_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QARITH_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 22: QRANGE_EVAL_LOC + mstore(BATCH_SCALAR_23_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QRANGE_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 23: QELLIPTIC_EVAL_LOC + mstore(BATCH_SCALAR_24_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QELLIPTIC_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 24: QMEMORY_EVAL_LOC + mstore(BATCH_SCALAR_25_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QMEMORY_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 25: QNNF_EVAL_LOC + mstore(BATCH_SCALAR_26_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QNNF_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 26: QPOSEIDON2_EXTERNAL_EVAL_LOC + mstore(BATCH_SCALAR_27_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QPOSEIDON2_EXTERNAL_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 27: QPOSEIDON2_INTERNAL_EVAL_LOC + mstore(BATCH_SCALAR_28_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(QPOSEIDON2_INTERNAL_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 28: W1_EVAL_LOC + mstore(BATCH_SCALAR_29_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(W1_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 29: W2_EVAL_LOC + mstore(BATCH_SCALAR_30_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(W2_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 30: W3_EVAL_LOC + mstore(BATCH_SCALAR_31_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(W3_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 31: W4_EVAL_LOC + mstore(BATCH_SCALAR_32_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(W4_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 32: Z_PERM_EVAL_LOC + mstore(BATCH_SCALAR_33_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(Z_PERM_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 33: LOOKUP_INVERSES_EVAL_LOC + mstore(BATCH_SCALAR_34_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(LOOKUP_INVERSES_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 34: LOOKUP_READ_COUNTS_EVAL_LOC + mstore(BATCH_SCALAR_35_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(LOOKUP_READ_COUNTS_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 35: LOOKUP_READ_TAGS_EVAL_LOC + mstore(BATCH_SCALAR_36_LOC, mulmod(neg_unshifted_scalar, batching_challenge, p)) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(LOOKUP_READ_TAGS_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // Unrolled for NUMBER_OF_SHIFTED_ENTITIES = 5 + // for (uint256 i = NUMBER_UNSHIFTED + 1; i <= NUMBER_OF_ENTITIES; ++i) { + // scalars[i] = mem.shiftedScalar.neg() * mem.batchingChallenge; + // mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[i - 1] * mem.batchingChallenge); + // mem.batchingChallenge = mem.batchingChallenge * tp.rho; + // } + + // 28: W1_EVAL_LOC + mstore( + BATCH_SCALAR_29_LOC, + addmod(mload(BATCH_SCALAR_29_LOC), mulmod(neg_shifted_scalar, batching_challenge, p), p) + ) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(W1_SHIFT_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 29: W2_EVAL_LOC + mstore( + BATCH_SCALAR_30_LOC, + addmod(mload(BATCH_SCALAR_30_LOC), mulmod(neg_shifted_scalar, batching_challenge, p), p) + ) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(W2_SHIFT_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 30: W3_EVAL_LOC + mstore( + BATCH_SCALAR_31_LOC, + addmod(mload(BATCH_SCALAR_31_LOC), mulmod(neg_shifted_scalar, batching_challenge, p), p) + ) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(W3_SHIFT_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 31: W4_EVAL_LOC + mstore( + BATCH_SCALAR_32_LOC, + addmod(mload(BATCH_SCALAR_32_LOC), mulmod(neg_shifted_scalar, batching_challenge, p), p) + ) + batched_evaluation := addmod(batched_evaluation, mulmod(mload(W4_SHIFT_EVAL_LOC), batching_challenge, p), p) + batching_challenge := mulmod(batching_challenge, rho, p) + + // 32: Z_PERM_EVAL_LOC + mstore( + BATCH_SCALAR_33_LOC, + addmod(mload(BATCH_SCALAR_33_LOC), mulmod(neg_shifted_scalar, batching_challenge, p), p) + ) + batched_evaluation := addmod( + batched_evaluation, + mulmod(mload(Z_PERM_SHIFT_EVAL_LOC), batching_challenge, p), + p + ) + batching_challenge := mulmod(batching_challenge, rho, p) + + // Compute fold pos evaluations + { + mstore(CHALL_POW_LOC, POWERS_OF_EVALUATION_CHALLENGE_23_LOC) + mstore(SUMCHECK_U_LOC, SUM_U_CHALLENGE_23) + mstore(GEMINI_A_LOC, GEMINI_A_EVAL_23) + // Inversion of this value was included in batch inversion above + let inverted_chall_pow_minus_u_loc := INVERTED_CHALLENGE_POW_MINUS_U_23_LOC + let fold_pos_off := FOLD_POS_EVALUATIONS_23_LOC + + let batchedEvalAcc := batched_evaluation + for { let i := LOG_N } gt(i, 0) { i := sub(i, 1) } { + let chall_pow := mload(mload(CHALL_POW_LOC)) + let sum_check_u := mload(mload(SUMCHECK_U_LOC)) + + // challengePower * batchedEvalAccumulator * 2 + let batchedEvalRoundAcc := mulmod(chall_pow, mulmod(batchedEvalAcc, 2, p), p) + // (challengePower * (ONE - u) - u) + let chall_pow_times_1_minus_u := mulmod(chall_pow, addmod(1, sub(p, sum_check_u), p), p) + + batchedEvalRoundAcc := addmod( + batchedEvalRoundAcc, + sub( + p, + mulmod( + mload(mload(GEMINI_A_LOC)), + addmod(chall_pow_times_1_minus_u, sub(p, sum_check_u), p), + p + ) + ), + p + ) + + batchedEvalRoundAcc := mulmod(batchedEvalRoundAcc, mload(inverted_chall_pow_minus_u_loc), p) + + batchedEvalAcc := batchedEvalRoundAcc + mstore(fold_pos_off, batchedEvalRoundAcc) + + mstore(CHALL_POW_LOC, sub(mload(CHALL_POW_LOC), 0x20)) + mstore(SUMCHECK_U_LOC, sub(mload(SUMCHECK_U_LOC), 0x20)) + mstore(GEMINI_A_LOC, sub(mload(GEMINI_A_LOC), 0x20)) + inverted_chall_pow_minus_u_loc := sub(inverted_chall_pow_minus_u_loc, 0x20) + fold_pos_off := sub(fold_pos_off, 0x20) + } + } + + let constant_term_acc := mulmod(mload(FOLD_POS_EVALUATIONS_0_LOC), mload(POS_INVERTED_DENOM_0_LOC), p) + { + let shplonk_nu := mload(SHPLONK_NU_CHALLENGE) + + constant_term_acc := addmod( + constant_term_acc, + mulmod(mload(GEMINI_A_EVAL_0), mulmod(shplonk_nu, mload(NEG_INVERTED_DENOM_0_LOC), p), p), + p + ) + + let shplonk_nu_sqr := mulmod(shplonk_nu, shplonk_nu, p) + batching_challenge := shplonk_nu_sqr + + mstore(SS_POS_INV_DENOM_LOC, POS_INVERTED_DENOM_1_LOC) + mstore(SS_NEG_INV_DENOM_LOC, NEG_INVERTED_DENOM_1_LOC) + + mstore(SS_GEMINI_EVALS_LOC, GEMINI_A_EVAL_1) + let fold_pos_evals_loc := FOLD_POS_EVALUATIONS_1_LOC + + let scalars_loc := BATCH_SCALAR_37_LOC + + for { let i := 0 } lt(i, sub(LOG_N, 1)) { i := add(i, 1) } { + let scaling_factor_pos := mulmod(batching_challenge, mload(mload(SS_POS_INV_DENOM_LOC)), p) + let scaling_factor_neg := + mulmod(batching_challenge, mulmod(shplonk_nu, mload(mload(SS_NEG_INV_DENOM_LOC)), p), p) + + mstore(scalars_loc, addmod(sub(p, scaling_factor_neg), sub(p, scaling_factor_pos), p)) + + let accum_contribution := mulmod(scaling_factor_neg, mload(mload(SS_GEMINI_EVALS_LOC)), p) + accum_contribution := addmod( + accum_contribution, + mulmod(scaling_factor_pos, mload(fold_pos_evals_loc), p), + p + ) + + constant_term_acc := addmod(constant_term_acc, accum_contribution, p) + + batching_challenge := mulmod(batching_challenge, shplonk_nu_sqr, p) + + mstore(SS_POS_INV_DENOM_LOC, add(mload(SS_POS_INV_DENOM_LOC), 0x20)) + mstore(SS_NEG_INV_DENOM_LOC, add(mload(SS_NEG_INV_DENOM_LOC), 0x20)) + mstore(SS_GEMINI_EVALS_LOC, add(mload(SS_GEMINI_EVALS_LOC), 0x20)) + fold_pos_evals_loc := add(fold_pos_evals_loc, 0x20) + scalars_loc := add(scalars_loc, 0x20) + } + } + + let precomp_success_flag := 1 + let q := Q // EC group order + { + // The initial accumulator = 1 * shplonk_q + mcopy(ACCUMULATOR, SHPLONK_Q_X_LOC, 0x40) + } + + // Accumulate vk points + loadVk() + { + // VK batchMul order matches UltraFlavor_Generated::EntityId precomputed layout. + // Accumulator = accumulator + scalar[1] * vk[0] (sigma_1) + mcopy(G1_LOCATION, SIGMA_1_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_1_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[2] * vk[1] (sigma_2) + mcopy(G1_LOCATION, SIGMA_2_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_2_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[3] * vk[2] (sigma_3) + mcopy(G1_LOCATION, SIGMA_3_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_3_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[4] * vk[3] (sigma_4) + mcopy(G1_LOCATION, SIGMA_4_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_4_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[5] * vk[4] (id_1) + mcopy(G1_LOCATION, ID_1_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_5_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[6] * vk[5] (id_2) + mcopy(G1_LOCATION, ID_2_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_6_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[7] * vk[6] (id_3) + mcopy(G1_LOCATION, ID_3_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_7_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[8] * vk[7] (id_4) + mcopy(G1_LOCATION, ID_4_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_8_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[9] * vk[8] (lagrange_first) + mcopy(G1_LOCATION, LAGRANGE_FIRST_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_9_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[10] * vk[9] (lagrange_last) + mcopy(G1_LOCATION, LAGRANGE_LAST_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_10_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[11] * vk[10] (q_lookup) + mcopy(G1_LOCATION, Q_LOOKUP_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_11_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[12] * vk[11] (table_1) + mcopy(G1_LOCATION, TABLE_1_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_12_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[13] * vk[12] (table_2) + mcopy(G1_LOCATION, TABLE_2_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_13_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[14] * vk[13] (table_3) + mcopy(G1_LOCATION, TABLE_3_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_14_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[15] * vk[14] (table_4) + mcopy(G1_LOCATION, TABLE_4_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_15_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[16] * vk[15] (q_m) + mcopy(G1_LOCATION, Q_M_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_16_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[17] * vk[16] (q_r) + mcopy(G1_LOCATION, Q_R_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_17_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[18] * vk[17] (q_o) + mcopy(G1_LOCATION, Q_O_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_18_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[19] * vk[18] (q_c) + mcopy(G1_LOCATION, Q_C_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_19_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[20] * vk[19] (q_l) + mcopy(G1_LOCATION, Q_L_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_20_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[21] * vk[20] (q_4) + mcopy(G1_LOCATION, Q_4_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_21_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[22] * vk[21] (q_arith) + mcopy(G1_LOCATION, Q_ARITH_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_22_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[23] * vk[22] (q_delta_range) + mcopy(G1_LOCATION, Q_DELTA_RANGE_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_23_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[24] * vk[23] (q_elliptic) + mcopy(G1_LOCATION, Q_ELLIPTIC_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_24_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[25] * vk[24] (q_memory) + mcopy(G1_LOCATION, Q_MEMORY_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_25_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[26] * vk[25] (q_nnf) + mcopy(G1_LOCATION, Q_NNF_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_26_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[27] * vk[26] (q_poseidon2_external) + mcopy(G1_LOCATION, Q_POSEIDON_2_EXTERNAL_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_27_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[28] * vk[27] (q_poseidon2_internal) + mcopy(G1_LOCATION, Q_POSEIDON_2_INTERNAL_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_28_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + constant_term_acc * G (generator) + mstore(G1_LOCATION, 0x01) // G1 generator x + mstore(add(G1_LOCATION, 0x20), 0x02) // G1 generator y + mstore(SCALAR_LOCATION, constant_term_acc) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulate proof points + // Accumulator = accumulator + scalar[29] * w_l + mcopy(G1_LOCATION, W_L_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_29_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[30] * w_r + mcopy(G1_LOCATION, W_R_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_30_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[31] * w_o + mcopy(G1_LOCATION, W_O_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_31_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[32] * w_4 + mcopy(G1_LOCATION, W_4_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_32_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[33] * z_perm + mcopy(G1_LOCATION, Z_PERM_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_33_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[34] * lookup_inverses + mcopy(G1_LOCATION, LOOKUP_INVERSES_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_34_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[35] * lookup_read_counts + mcopy(G1_LOCATION, LOOKUP_READ_COUNTS_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_35_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulator = accumulator + scalar[36] * lookup_read_tags + mcopy(G1_LOCATION, LOOKUP_READ_TAGS_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_36_LOC)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + + // Accumulate these LOG_N scalars with the gemini fold univariates + { + { + /// {{ UNROLL_SECTION_START ACCUMULATE_GEMINI_FOLD_UNIVARIATE }} + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_0_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_37_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_1_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_38_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_2_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_39_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_3_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_40_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_4_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_41_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_5_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_42_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_6_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_43_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_7_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_44_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_8_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_45_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_9_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_46_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_10_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_47_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_11_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_48_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_12_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_49_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_13_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_50_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_14_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_51_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_15_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_52_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_16_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_53_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_17_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_54_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_18_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_55_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_19_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_56_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_20_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_57_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_21_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_58_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) + + mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_22_X_LOC, 0x40) + mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_59_LOC)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40)) + precomp_success_flag := + and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40)) +/// {{ UNROLL_SECTION_END ACCUMULATE_GEMINI_FOLD_UNIVARIATE }} + } + } + + { + // Accumulate final quotient commitment into shplonk check + // Accumulator = accumulator + shplonkZ * quotient commitment + mcopy(G1_LOCATION, KZG_QUOTIENT_X_LOC, 0x40) + + mstore(SCALAR_LOCATION, mload(SHPLONK_Z_CHALLENGE)) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 7, G1_LOCATION, 0x60, ACCUMULATOR_2, 0x40) + ) + precomp_success_flag := and( + precomp_success_flag, + staticcall(gas(), 6, ACCUMULATOR, 0x80, ACCUMULATOR, 0x40) + ) + } + + // All G1 points were validated on-curve during input validation. + // precomp_success_flag now only tracks ecAdd/ecMul precompile success. + if iszero(precomp_success_flag) { + mstore(0x00, SHPLEMINI_FAILED_SELECTOR) + revert(0x00, 0x04) + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SHPLEMINI - complete */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PAIRING CHECK */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + { + // P_1 + mstore(0xc0, mload(KZG_QUOTIENT_X_LOC)) + mstore(0xe0, sub(q, mload(KZG_QUOTIENT_Y_LOC))) + + // p_0_agg + // 0x80 - p_0_agg x + // 0xa0 - p_0_agg y + mcopy(0x80, ACCUMULATOR, 0x40) + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PAIRING AGGREGATION */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Read the pairing encoded in the first 8 field elements of the proof (2 limbs per coordinate) + let p0_other_x := mload(PAIRING_POINT_0_X_0_LOC) + p0_other_x := or(shl(136, mload(PAIRING_POINT_0_X_1_LOC)), p0_other_x) + + let p0_other_y := mload(PAIRING_POINT_0_Y_0_LOC) + p0_other_y := or(shl(136, mload(PAIRING_POINT_0_Y_1_LOC)), p0_other_y) + + let p1_other_x := mload(PAIRING_POINT_1_X_0_LOC) + p1_other_x := or(shl(136, mload(PAIRING_POINT_1_X_1_LOC)), p1_other_x) + + let p1_other_y := mload(PAIRING_POINT_1_Y_0_LOC) + p1_other_y := or(shl(136, mload(PAIRING_POINT_1_Y_1_LOC)), p1_other_y) + + // Check if pairing points are default (all zero = infinity = no recursive verification) + let pairing_points_are_default := iszero(or(or(p0_other_x, p0_other_y), or(p1_other_x, p1_other_y))) + + let success := 1 + // Only aggregate if pairing points are non-default + if iszero(pairing_points_are_default) { + // Reconstructed coordinates must be < Q to prevent malleability + if iszero(and( + and(lt(p0_other_x, q), lt(p0_other_y, q)), + and(lt(p1_other_x, q), lt(p1_other_y, q)) + )) { + mstore(0x00, VALUE_GE_GROUP_ORDER_SELECTOR) + revert(0x00, 0x04) + } + + // Validate p_0_other not point of infinity + success := iszero(iszero(or(p0_other_x, p0_other_y))) + // Validate p_1_other not point of infinity + success := and(success, iszero(iszero(or(p1_other_x, p1_other_y)))) + + // p_0 + mstore(0x00, p0_other_x) + mstore(0x20, p0_other_y) + + // p_1 + mstore(0x40, p1_other_x) + mstore(0x60, p1_other_y) + + // p_1_agg is already in the correct location + + let recursion_separator := keccak256(0x00, 0x100) + + // Write separator back to scratch space + mstore(0x00, p0_other_x) + + mstore(0x40, recursion_separator) + // recursion_separator * p_0_other + success := and(success, staticcall(gas(), 0x07, 0x00, 0x60, 0x00, 0x40)) + + // (recursion_separator * p_0_other) + p_0_agg + mcopy(0x40, 0x80, 0x40) + // p_0 = (recursion_separator * p_0_other) + p_0_agg + success := and(success, staticcall(gas(), 6, 0x00, 0x80, 0x00, 0x40)) + + mstore(0x40, p1_other_x) + mstore(0x60, p1_other_y) + mstore(0x80, recursion_separator) + + success := and(success, staticcall(gas(), 7, 0x40, 0x60, 0x40, 0x40)) + + // Write p_1_agg back to scratch space + mcopy(0x80, 0xc0, 0x40) + + // 0xc0 - (recursion_separator * p_1_other) + p_1_agg + success := and(success, staticcall(gas(), 6, 0x40, 0x80, 0xc0, 0x40)) + } + // If default pairing points, use p_0_agg and p_1_agg directly (already at 0x80, 0xc0) + if pairing_points_are_default { + // Copy p_0_agg to 0x00 for pairing input + mcopy(0x00, 0x80, 0x40) + // p_1_agg stays at 0xc0 + } + + // G2 [1] + mstore(0x40, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) + mstore(0x60, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x80, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0xa0, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) + + // G2 [x] + mstore(0x100, 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1) + mstore(0x120, 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0) + mstore(0x140, 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4) + mstore(0x160, 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) + + let pairing_success := and(success, staticcall(gas(), 8, 0x00, 0x180, 0x00, 0x20)) + if iszero(and(pairing_success, mload(0x00))) { + mstore(0x00, SHPLEMINI_FAILED_SELECTOR) + revert(0x00, 0x04) + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PAIRING CHECK - Complete */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + } + { + mstore(0x00, 0x01) + return(0x00, 0x20) // Proof succeeded! + } + } + } + } +} diff --git a/l1-contracts/script/deploy/V6UpgradeSimulation.sol b/l1-contracts/script/deploy/V6UpgradeSimulation.sol new file mode 100644 index 00000000000..ba023c001cd --- /dev/null +++ b/l1-contracts/script/deploy/V6UpgradeSimulation.sol @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +// solhint-disable comprehensive-interface +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; + +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; + +import {IInstance} from "@aztec/core/interfaces/IInstance.sol"; +import {Rollup} from "@aztec/core/Rollup.sol"; +import {Timestamp} from "@aztec/shared/libraries/TimeMath.sol"; + +import {Governance} from "@aztec/governance/Governance.sol"; +import {GSEPayload} from "@aztec/governance/GSEPayload.sol"; +import {GSE, IGSE} from "@aztec/governance/GSE.sol"; +import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; +import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; +import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; +import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; +import {Proposal, ProposalState} from "@aztec/governance/interfaces/IGovernance.sol"; + +import {FlushRewarder} from "@aztec/periphery/FlushRewarder.sol"; +import {V6UpgradePayload} from "@aztec/periphery/V6UpgradePayload.sol"; + +/** + * @title V6UpgradeSimulation + * @author Aztec Labs + * @notice Executes {V6UpgradePayload} through the real governance lifecycle against a state + * snapshot, asserts the resulting state, and reverts the snapshot so nothing persists. + * + * @dev Kept out of DeployRollupForUpgradeV6 on purpose: that file is a configuration table meant + * to be reviewed line by line, and this is cheatcode machinery. The deploy script calls + * {simulate} in one line after it has stopped broadcasting. + * + * Requires forked state (a real registry, GSE and governance), so it runs under `--fork-url` + * or against a live RPC in simulation mode, not against a bare anvil. + * + * What this does and does not prove: it proves the payload's actions execute correctly and + * leave the chain in the expected state. It does NOT predict whether the proposal would + * pass -- a simulation-only voter is given a majority of the voting power so execution is + * reached regardless of how power is really distributed. + */ +contract V6UpgradeSimulation is Test { + error V6UpgradeSimulation__SnapshotRevertFailed(uint256 snapshotId); + + /// @notice Chain state read before execution, so the assertions afterwards compare against what + /// was actually there rather than against hardcoded amounts. + struct Snapshot { + address canonicalRollup; + uint256 versions; + uint256 bonusAttesters; + uint256 rewardDistributorBalance; + uint256 rewardDistributorAvailableToNew; + uint256 earmarkAmount; + uint256 earmarkedToPredecessorBefore; + uint256 totalEarmarkedBefore; + uint256 flushFundsToMove; + uint256 oldFlushBalance; + uint256 newFlushBalance; + uint64 proofOfPossessionGasLimit; + } + + /// @notice Runs the whole lifecycle and reverts the snapshot afterwards. + /// @dev Also the entry point for `forge script ... --sig 'simulate(address)' `. + /// @param _payload The deployed {V6UpgradePayload} to execute + function simulate(address _payload) public { + V6UpgradePayload payload = V6UpgradePayload(_payload); + IRegistry registry = payload.REGISTRY(); + Governance governance = Governance(registry.getGovernance()); + IGSE gse = IGSE(address(payload.ROLLUP().getGSE())); + + Snapshot memory before = _capture(payload, registry, gse); + + uint256 snapshotId = vm.snapshotState(); + + _executeThroughGovernance(payload, governance, gse, before.canonicalRollup); + _assertPostState(payload, registry, gse, before); + + if (!vm.revertToState(snapshotId)) { + revert V6UpgradeSimulation__SnapshotRevertFailed(snapshotId); + } + console.log(unicode"[simulate] payload executes and leaves the expected state ✓"); + } + + function _capture(V6UpgradePayload _payload, IRegistry _registry, IGSE _gse) + internal + view + returns (Snapshot memory s) + { + s.canonicalRollup = address(_registry.getCanonicalRollup()); + s.versions = _registry.numberOfVersions(); + s.bonusAttesters = _gse.getAttesterCountAtTime(_gse.getBonusInstanceAddress(), Timestamp.wrap(block.timestamp)); + s.proofOfPossessionGasLimit = GSE(address(_gse)).proofOfPossessionGasLimit(); + + IRewardDistributor distributor = _registry.getRewardDistributor(); + IERC20 rewardAsset = IERC20(Rollup(address(_payload.ROLLUP())).getFeeAsset()); + s.rewardDistributorBalance = rewardAsset.balanceOf(address(distributor)); + s.rewardDistributorAvailableToNew = distributor.availableTo(address(_payload.ROLLUP())); + + s.earmarkAmount = _payload.EARMARK_AMOUNT(); + s.earmarkedToPredecessorBefore = + RewardDistributor(address(distributor)).specificRecipientBalance(_payload.PREDECESSOR()); + s.totalEarmarkedBefore = RewardDistributor(address(distributor)).totalEarmarkedBalance(); + + FlushRewarder oldFlush = _payload.OLD_FLUSH_REWARDER(); + if (address(oldFlush) != address(0)) { + IERC20 flushAsset = oldFlush.REWARD_ASSET(); + s.flushFundsToMove = oldFlush.rewardsAvailable(); + s.oldFlushBalance = flushAsset.balanceOf(address(oldFlush)); + s.newFlushBalance = flushAsset.balanceOf(address(_payload.NEW_FLUSH_REWARDER())); + } + } + + /// @dev Mirrors what GovernanceProposer does: the payload is wrapped in a GSEPayload before + /// being proposed, so the simulation exercises the same shape governance will see. + function _executeThroughGovernance( + V6UpgradePayload _payload, + Governance _governance, + IGSE _gse, + address _oldCanonical + ) internal { + GSEPayload wrapped = new GSEPayload(IPayload(address(_payload)), _gse, _payload.REGISTRY()); + + // A simulation-only voter holding twice the total power guarantees the proposal reaches + // execution however real power is distributed. Everything here is inside the snapshot. + address simVoter = address(uint160(uint256(keccak256("V6UpgradeSimulation.simVoter")))); + uint256 simPower = _governance.totalPowerAt(Timestamp.wrap(block.timestamp - 1)) * 2; + IERC20 govAsset = _governance.ASSET(); + deal(address(govAsset), simVoter, simPower); + + vm.startPrank(simVoter); + govAsset.approve(address(_governance), simPower); + _governance.deposit(simVoter, simPower); + vm.stopPrank(); + + vm.prank(_governance.governanceProposer()); + uint256 proposalId = _governance.propose(IPayload(address(wrapped))); + + Proposal memory proposal = _governance.getProposal(proposalId); + Timestamp pendingThrough = + Timestamp.wrap(Timestamp.unwrap(proposal.creation) + Timestamp.unwrap(proposal.config.votingDelay)); + Timestamp queuedThrough = Timestamp.wrap( + Timestamp.unwrap(pendingThrough) + Timestamp.unwrap(proposal.config.votingDuration) + + Timestamp.unwrap(proposal.config.executionDelay) + ); + + vm.warp(Timestamp.unwrap(pendingThrough) + 1); + assertEq(uint256(_governance.getProposalState(proposalId)), uint256(ProposalState.Active), "proposal not active"); + + // The outgoing rollup votes with whatever power it and the bonus instance hold, so the + // simulation exercises the real voting path and not only the synthetic voter. + uint256 rollupPower = _gse.getVotingPowerAt(_oldCanonical, pendingThrough); + if (rollupPower > 0) { + vm.prank(_oldCanonical); + _gse.vote(proposalId, rollupPower, true); + } + uint256 bonusPower = _gse.getVotingPowerAt(_gse.getBonusInstanceAddress(), pendingThrough); + if (bonusPower > 0) { + vm.prank(_oldCanonical); + _gse.voteWithBonus(proposalId, bonusPower, true); + } + vm.prank(simVoter); + _governance.vote(proposalId, simPower, true); + + vm.warp(Timestamp.unwrap(queuedThrough) + 1); + + // Where execution is restricted to office hours, step to the next open slot. The proposal + // survives the wait because the longest gap (Friday 17:00 to Monday 08:00, ~63h) is well + // inside the grace period, but assert it rather than assume. + if (_payload.ENFORCE_EXECUTION_WINDOW()) { + for (uint256 i = 0; i < 7 days / 1 hours && !_payload.isWithinExecutionWindow(block.timestamp); i++) { + vm.warp(block.timestamp + 1 hours); + } + assertTrue(_payload.isWithinExecutionWindow(block.timestamp), "no execution window found within a week"); + } + + assertEq( + uint256(_governance.getProposalState(proposalId)), uint256(ProposalState.Executable), "proposal not executable" + ); + + _governance.execute(proposalId); + assertEq( + uint256(_governance.getProposalState(proposalId)), uint256(ProposalState.Executed), "proposal not executed" + ); + } + + function _assertPostState(V6UpgradePayload _payload, IRegistry _registry, IGSE _gse, Snapshot memory _before) + internal + view + { + address newRollup = address(_payload.ROLLUP()); + + assertEq(address(_registry.getCanonicalRollup()), newRollup, "new rollup is not canonical"); + assertEq(_registry.numberOfVersions(), _before.versions + 1, "version count did not grow by one"); + + // The bonus-instance attesters follow the canonical rollup by delegation rather than by + // re-depositing, so the new rollup should see at least everything the bonus bucket held. + assertGe( + _gse.getAttesterCountAtTime(newRollup, Timestamp.wrap(block.timestamp)), + _before.bonusAttesters, + "new rollup did not inherit the bonus instance attesters" + ); + + // Ownership must survive the upgrade: governance owns the rollup, or none of the owner-gated + // configuration (fee margin, fee recipient, queue config) can ever be changed again. + assertEq(Rollup(newRollup).owner(), _registry.getGovernance(), "rollup owner is not governance"); + + // The hatch is installed by the payload, not the deploy, so this is the only place the + // installation is proved before the real execution. + assertEq(address(Rollup(newRollup).getEscapeHatch()), _payload.ESCAPE_HATCH(), "escape hatch was not installed"); + + // The GSE is shared, so the new cap also governs the outgoing rollup's flushes from here on. + uint64 expectedPopGasLimit = _payload.PROOF_OF_POSSESSION_GAS_LIMIT() > 0 + ? _payload.PROOF_OF_POSSESSION_GAS_LIMIT() + : _before.proofOfPossessionGasLimit; + assertEq(GSE(address(_gse)).proofOfPossessionGasLimit(), expectedPopGasLimit, "GSE proof-of-possession gas cap"); + + _assertRewardDistributor(_payload, _registry, _before); + _assertFlushRewarder(_payload, _before); + } + + /// @dev The distributor resolves the canonical rollup live, so no action moves its funds: the + /// implicit pool simply becomes claimable by the new rollup. Asserted against the captured + /// balances rather than any fixed amount. + function _assertRewardDistributor(V6UpgradePayload _payload, IRegistry _registry, Snapshot memory _before) + internal + view + { + address newRollup = address(_payload.ROLLUP()); + IRewardDistributor distributor = _registry.getRewardDistributor(); + IERC20 rewardAsset = IERC20(Rollup(newRollup).getFeeAsset()); + + assertEq(distributor.canonicalRollup(), newRollup, "distributor does not follow the new rollup"); + + // Unchanged whether or not an earmark ran: the reservation moves value between the + // distributor's buckets, it does not move value out of the distributor. + assertEq( + rewardAsset.balanceOf(address(distributor)), + _before.rewardDistributorBalance, + "distributor balance moved during the upgrade" + ); + + // The reservation landed on the OUTGOING rollup, and the payload kept nothing back. + assertEq( + RewardDistributor(address(distributor)).specificRecipientBalance(_payload.PREDECESSOR()), + _before.earmarkedToPredecessorBefore + _before.earmarkAmount, + "earmark did not reach the outgoing rollup" + ); + assertEq( + RewardDistributor(address(distributor)).totalEarmarkedBalance(), + _before.totalEarmarkedBefore + _before.earmarkAmount, + "earmark not reflected in the earmarked total" + ); + assertEq(rewardAsset.balanceOf(address(_payload)), 0, "payload retained reward asset"); + // Anything reserved for the predecessor is exactly what the new rollup no longer sees. + assertGe( + distributor.availableTo(newRollup) + _before.earmarkAmount, + _before.rewardDistributorAvailableToNew, + "new rollup lost more than the earmark" + ); + } + + /// @dev The replacement rewarder should gain exactly what was movable, and the outgoing one + /// should keep the remainder it owes to unclaimed flushers. + function _assertFlushRewarder(V6UpgradePayload _payload, Snapshot memory _before) internal view { + FlushRewarder oldFlush = _payload.OLD_FLUSH_REWARDER(); + if (address(oldFlush) == address(0)) { + return; + } + + IERC20 flushAsset = oldFlush.REWARD_ASSET(); + assertEq( + flushAsset.balanceOf(address(_payload.NEW_FLUSH_REWARDER())), + _before.newFlushBalance + _before.flushFundsToMove, + "new flush rewarder did not receive the migrated funds" + ); + assertEq( + flushAsset.balanceOf(address(oldFlush)), + _before.oldFlushBalance - _before.flushFundsToMove, + "old flush rewarder did not retain exactly its unclaimed remainder" + ); + } +} diff --git a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md new file mode 100644 index 00000000000..c8cbccedd62 --- /dev/null +++ b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md @@ -0,0 +1,418 @@ +# v6 rollup upgrade runbook + +How to deploy the v6 rollup and get it made canonical. Covers `DeployRollupForUpgradeV6.s.sol`, +`V6UpgradePayload.sol` and `V6UpgradeSimulation.sol`. + +Reviewing the payload rather than running the upgrade? Start at +[`src/periphery/V6UpgradePayload.md`](../../src/periphery/V6UpgradePayload.md) — what it intends, +guarantees, and deliberately leaves alone. + +## What this does + +The deploy script deploys, in one broadcast: + +1. `HonkVerifier` — the real epoch proof verifier, from the pinned `script/deploy/HonkVerifier.sol` + (see [Build](#1-build)), not from this tree's `generated/`. +2. `Rollup` — owned by **governance** from construction. Deploying it also constructs its `Inbox`, + `Outbox`, `FeeJuicePortal`, `Slasher` + `SlashingProposer`, and a fresh `RewardBooster`. + **The owner argument is not only the owner:** it also becomes the Slasher's immutable + `GOVERNANCE`, which can execute any slash payload with no vote and no delay. Constructing with + the deploy key would hand that power to the key permanently — `transferOwnership` moves only the + `Ownable` half. This is why no owner-gated setup happens in the script. +3. `EscapeHatch` — built here, but **installed by the payload**, since `setEscapeHatch` is + `onlyOwner` and the owner is governance. +4. `V6UpgradePayload` — and, on a chain with a flush rewarder, a replacement `FlushRewarder` + deployed inside the payload's constructor. + +Nothing is canonical yet. The payload is what governance executes later; it performs: + +| # | Action | Why | +|---|---|---| +| 1 | `assertPredecessorIsCanonical()` | refuses to run if the rollup this payload succeeds is no longer canonical | +| 2 | `assertWithinExecutionWindow()` | mainnet only; Mon–Fri 08:00–17:00 London | +| 3 | `distributor.recoverFrom(v5, payload, amount)` | skipped when the reservation is zero | +| 4 | `payload.forwardEarmark()` | parks the drawn balance in v5's own earmarked bucket | +| 5 | `v5.setRewardConfig(...)` | retunes the OUTGOING rollup's split; skipped when the flag is off | +| 6 | `v6.setEscapeHatch(hatch)` | installs the hatch; `onlyOwner`, so only governance can | +| 7 | `Registry.addRollup(v6)` | makes v6 the canonical rollup | +| 8 | `GSE.addRollup(v6)` | lets existing attesters follow without redepositing | +| 9 | `oldFlushRewarder.recover(asset, newRewarder, rewardsAvailable())` | carries the entry-queue flush incentive across (skipped if there is no old rewarder) | +| 10 | `GSE.setProofOfPossessionGasLimit(300_000)` | raises the GSE's cap on BLS proof-of-possession verification from 250k; skipped when the configured value is zero | + +Actions 3–5 all act on the **outgoing** rollup and must precede action 7. The distributor resolves +`canonicalRollup()` live off the registry, so once v6 is canonical the old rollup can no longer +reach the implicit pool, and the retune would be settling a chain that has already stopped. + +Action 10 has no ordering constraint and goes last. The GSE verifies each new attester's proof of +possession in a sub-call capped at this much gas, and a deposit whose verification does not fit is +refunded and dropped from the entry queue. The cost varies per key, and since Osaka's modexp +repricing about 1 in 28.6k honestly generated keys exceed 250k; at 300k it is about 1 in 2.5M. The +cost of the raise is that a failing entry can burn at most 50k more gas in a flush. The GSE is +shared, so the new cap applies to the outgoing rollup too, from the moment the payload executes. + +## 1. Build + +The verifier and the protocol constants are **pinned in git**, not built from this tree: + +- `script/deploy/HonkVerifier.sol` — the epoch proof verifier from the v6 release build at + `42fae4eb3feb5a493f2e8a2baf78b4716e323c40`; its provenance comment names that commit. + `DeployRollupForUpgradeV6.s.sol` imports it as `./HonkVerifier.sol`. +- `src/core/libraries/ConstantsGen.sol` — from the same build. + +The verifier is a function of the protocol circuits, and the circuits this branch would build +differ from the release's. A verifier built here has a different `VK_HASH` and rejects every proof +the v6 nodes produce, so **do not replace the pinned file with this tree's `generated/HonkVerifier.sol`**. +The verifier, `ConstantsGen.sol` and the three genesis roots in `_config()` come from one build and +move together. + +No noir-projects bootstrap is needed. From a clean clone: + +```bash +git clone --branch --depth 1 https://github.com/AztecProtocol/aztec-packages.git +cd aztec-packages +git submodule update --init --recursive --depth 1 -- l1-contracts/lib +cd l1-contracts +export FOUNDRY_SOLC_VERSION=0.8.30 +forge script script/deploy/DeployRollupForUpgradeV6.s.sol --sig 'run()' \ + --rpc-url $RPC # dry run; see Deploy for the broadcast flags +``` + +- `FOUNDRY_SOLC_VERSION=0.8.30`: `foundry.toml` points `solc` at `./solc-0.8.30`, which only + `./bootstrap.sh` fetches; forge downloads the same binary (`0.8.30+commit.73712a01`). Use the + environment variable rather than `--use 0.8.30`: the flag covers compilation but not `--verify`, + which reads `foundry.toml` again and fails with `solc ./solc-0.8.30 does not exist`. +- The submodule step is required: `forge script` does not fetch `lib/` (only `forge build` does). +- Gas: forge sets each transaction's gas limit from its local simulation (`evm_version = 'prague'`), + which matches pre-Glamsterdam pricing, so the default limits are correct on mainnet before the + fork. Measured pre-fork on Sepolia: verifier 3.75M gas (limit 4.88M), rollup 11.98M (limit + 15.58M). Do **not** add `--gas-estimate-multiplier` before Glamsterdam: the rollup's limit would + exceed the EIP-7825 per-transaction cap of 16,777,216 and the node would reject it after the + verifier had already been sent. The rollup's default limit sits about 1.2M under that cap. +- After Glamsterdam, the default limits are far too low: on post-fork Sepolia the verifier creation + ran out of gas at its 4.88M limit. Measured there: verifier 26.7M, rollup 84.8M, escape hatch + 14.9M, payload 10.1M gas, deployed with `--gas-estimate-multiplier 1000` under a 200M block gas + limit. Re-check that path against mainnet's post-fork rules before using it. + +> Run `forge script` on the deploy script, **not** `forge build`. A whole-project build also compiles +> `DeployRollupLib.sol`, which imports `@generated/HonkVerifier.sol` and fails on a clean clone. +> Do **not** hand-write a stub `generated/HonkVerifier.sol` to get past that: a stub compiles and +> deploys a verifier that accepts every proof. + +Confirm the pin before broadcasting: + +```bash +grep -m1 VK_HASH script/deploy/HonkVerifier.sol +# 0x1fd4eb1d14be45e05dc78c448eb4f29d7f63e4de95129c4a5e1058cd9837f8a6 +``` + +## 2. Fill in the inputs + +All configuration is the `_config()` literal in `DeployRollupForUpgradeV6.s.sol`. Before a real +deploy these must be set: + +| Field | Status | Notes | +|---|---|---| +| `vkTreeRoot` | set | `0x22fff5de…5913f219`, from the v6 release build at `42fae4eb`; see below | +| `protocolContractsHash` | set | `0x0f54271c…5c9854d7`, same build | +| `genesisArchiveRoot` | set | `0x29eb2c52…3d41e5c4`, same build. Must be below the BN254 scalar field modulus | +| `initialEthPerFeeAsset` | set | mainnet `5_934_240` (0.0000059342 ETH per AZTEC, from the AZTEC/ETH Uniswap v4 pool at block 26148995, 2026-10-08); Sepolia `10_000_000`. E12 ETH-per-fee-asset price | +| `sequencerRewardCalculator` | set | `address(0)` on both chains — v6 launches with no calculator, see below | +| `oldFlushRewarder` | set (mainnet) | `0x5B98cA4dcE7b59CCf241D12f81d3d2eCF14e410e`; zero on Sepolia | +| `earmarkAmountForPredecessor` | set | mainnet 1,800,000e18; Sepolia 5,000,000e18. Zero would omit both earmark actions | +| `retunePredecessorRewards` | set | `true` on both chains | +| `predecessorSequencerBps` | set | mainnet 7000 (v5 keeps the split it already runs); Sepolia 8000 | +| `predecessorCheckpointReward` | set | mainnet 50e18; Sepolia 100e18. Both down from the 500e18 v5 runs on | +| `enforcePayloadExecutionWindow` | set | `true` on mainnet, `false` on Sepolia | +| `proofOfPossessionGasLimit` | set | `300_000` on both chains; zero omits the action. The payload constructor reverts unless it exceeds the GSE's cap at deployment | + +Neither chain has outstanding values. The three genesis values are not decisions but build outputs, +pinned with the verifier. + +`run()` refuses to proceed while any of the three genesis roots is zero, so a forgotten root fails +loudly. A *stale* root does not: it is non-zero and deploys happily. Nothing guards the rest either +— `initialEthPerFeeAsset` deploys silently at whatever value is in the table. + +### Sequencer reward policy + +Under AZIP-31 the rollup can call a governance-set `ISequencerRewardCalculator` once per epoch +proof, and `sequencerRewardCalculator` is the address it starts with. + +**v6 launches with `address(0)` on both chains, so every proposer earns the default sequencer +share.** Installing a calculator later is a governance action, `setSequencerRewardCalculator`, and +needs no redeploy. + +Nothing about the calculator path is loud, so if a later deploy does set one: + +- A calculator that reverts, exhausts its stipend (`200k + 100k` per checkpoint), or answers in the + wrong shape causes every checkpoint to fall back to the default reward. There is no revert and no + event; the rollup simply pays as though no calculator were configured. +- Nothing at deploy time checks the address is a calculator, or a contract at all. A + wrong-but-plausible address has the same observable result as `address(0)`. +- Reward values are capped at read time at `min(default, entry)`, so a too-high entry cannot pay + anyone more than the default. Note this cap is now applied on read rather than at construction, + so lowering `sequencerBps` or `checkpointReward` later tightens existing entries automatically. + +Read the value back with `getSequencerRewardCalculator()` after the deploy. Unlike the rest of this +table it is mutable after construction, so it is worth re-checking after any governance action that +touches rewards. + +The three genesis values are produced by the protocol circuits / node build, not by anything in +`l1-contracts`. Get them from the same source the v6 release uses; do not carry v5's forward. + +The values in `_config()` were read off a full build (`make fast`) of the v6 release at +`42fae4eb3feb5a493f2e8a2baf78b4716e323c40`, the same build the pinned verifier and +`ConstantsGen.sol` come from: + +| Field | Value | +|---|---| +| `vkTreeRoot` | `0x22fff5de6ce590153df4468f7f0b152188a803d91b3c9c648b91d93d5913f219` | +| `protocolContractsHash` | `0x0f54271c52865841a77aaa66036eed901aff22fdce9f08e123ec8b205c9854d7` | +| `genesisArchiveRoot` | `0x29eb2c527f8d45276430363214e6c8d709ef3f657a3670ebac3179373d41e5c4` | +| verifier `VK_HASH` | `0x1fd4eb1d14be45e05dc78c448eb4f29d7f63e4de95129c4a5e1058cd9837f8a6` | + +All three moved when the release build advanced to `42fae4eb`, which changes protocol circuit +sources, including `types/src/address/aztec_address.nr` and `types/src/constants.nr`. The verifier +did not: `VK_HASH` is unchanged and the pinned file only gained a new provenance comment. It still +differs from the verifier this branch's own circuits build, which is what the pin exists for. + +Regenerate all of them, and re-pin the verifier and `ConstantsGen.sol`, if the release build moves +to a commit that changes the protocol circuits or protocol contracts: rebuilding the circuits moves +`vkTreeRoot` and the verifier, and rebuilding the protocol contracts moves all three roots. After +`make fast` on the release build, the first two come from the built packages and the third from the +protocol constants: + +```bash +# from labs/yarn-project/prover-node +node --input-type=module -e ' +import { getVKTreeRoot } from "@aztec-labs/noir-protocol-circuits-types/vk-tree"; +import { protocolContractsHash } from "@aztec-labs/protocol-contracts"; +console.log(getVKTreeRoot().toString(), protocolContractsHash.toString());' + +grep -A1 GENESIS_ARCHIVE_ROOT \ + noir-projects/fnd/noir-protocol-circuits/crates/types/src/constants.nr +``` + +## 3. Pre-flight checks + +Read the chain and confirm the assumptions the config is built on. Mainnet registry: +`0x35b22e09Ee0390539439E24f06Da43D83f90e298`. + +```bash +export RPC= +REG=0x35b22e09Ee0390539439E24f06Da43D83f90e298 +ROLLUP=$(cast call $REG "getCanonicalRollup()(address)" --rpc-url $RPC) +GSE=$(cast call $ROLLUP "getGSE()(address)" --rpc-url $RPC) +RD=$(cast call $REG "getRewardDistributor()(address)" --rpc-url $RPC) +BONUS=$(cast call $GSE "BONUS_INSTANCE_ADDRESS()(address)" --rpc-url $RPC) + +cast call $GSE "ACTIVATION_THRESHOLD()(uint256)" --rpc-url $RPC # expect 200_000e18 +cast call $GSE "EJECTION_THRESHOLD()(uint256)" --rpc-url $RPC # expect 100_000e18 +cast call $GSE "proofOfPossessionGasLimit()(uint64)" --rpc-url $RPC # expect 250000, below the configured 300000 +cast call $GSE "owner()(address)" --rpc-url $RPC # governance, or action 10 reverts +cast call $ROLLUP "getActiveAttesterCount()(uint256)" --rpc-url $RPC +cast call $ROLLUP "getIsBootstrapped()(bool)" --rpc-url $RPC + +# must be 0, or funds earmarked to the outgoing rollup strand when it stops being canonical +cast call $RD "totalEarmarkedBalance()(uint256)" --rpc-url $RPC +cast call $RD "specificRecipientBalance(address)(uint256)" $ROLLUP --rpc-url $RPC + +# how much the payload will actually move (balance minus unclaimed debt) +cast call 0x5B98cA4dcE7b59CCf241D12f81d3d2eCF14e410e "rewardsAvailable()(uint256)" --rpc-url $RPC +``` + +Baseline measured 2026-09-15 (block 25980374), for comparison rather than as expected constants: +attesters 3187, all in the bonus instance; `isBootstrapped` true; distributor holds ~98.98M with +`totalEarmarkedBalance` 0; flush rewarder holds 390,000e18 of which 378,900e18 is movable. + +`totalEarmarkedBalance` is the one to re-check immediately before the proposal executes — +`subsidizeAddress` is permissionless, so anyone can earmark funds to the outgoing rollup after +this check and strand them. + +## 4. Dry run + +Run without `--broadcast` first. This executes the whole script including `verify`, +`verifyFlushRewarder` and the governance simulation, against forked state, changing nothing: + +```bash +cd l1-contracts +export FOUNDRY_SOLC_VERSION=0.8.30 +REGISTRY_ADDRESS=0x35b22e09Ee0390539439E24f06Da43D83f90e298 \ +forge script script/deploy/DeployRollupForUpgradeV6.s.sol:DeployRollupForUpgradeV6 \ + --rpc-url $RPC -vvv +``` + +`V6UpgradeSimulation` wraps the payload in a `GSEPayload` exactly as `GovernanceProposer` does, +funds a simulation-only voter with a majority of power, votes, warps past the delays, executes, +asserts the post-state, then reverts the snapshot. It proves the **actions execute correctly**; it +does not predict whether a real proposal would pass. + +A green dry run means the config is self-consistent and the payload works. It does not check the +genesis roots are the right ones — nothing on chain can. + +## 5. Deploy + +```bash +export FOUNDRY_SOLC_VERSION=0.8.30 ETHERSCAN_API_KEY= +REGISTRY_ADDRESS=0x35b22e09Ee0390539439E24f06Da43D83f90e298 \ +forge script script/deploy/DeployRollupForUpgradeV6.s.sol:DeployRollupForUpgradeV6 \ + --slow \ + --rpc-url $RPC --private-key $KEY --broadcast --verify -vvv +``` + +`--slow` sends each transaction only after the previous one is mined, so a failure stops the +broadcast instead of leaving later transactions pending against a contract that does not exist. + +`--verify` submits every contract the broadcast created to Etherscan, including the seven the +`Rollup` constructor deploys (`Slasher`, `SlashingProposer`, `SlashPayloadCloneable`, +`RewardBooster`, `Inbox`, `FeeJuicePortal`, `Outbox`). If verification fails or is skipped, re-run it +from the same checkout without sending anything: the same command with `--broadcast` replaced by +`--resume`. The verified `HonkVerifier` source is the pinned file, so anyone can read its `VK_HASH` +on Etherscan. + +Record the logged addresses: `rollup`, `verifier`, `inbox`, `outbox`, `feeJuicePortal`, `slasher`, +`rewardBooster`, `escapeHatch`, `payload`, `newFlushRewarder`, `version`. + +Re-run the checks against the deployed contracts at any time: + +```bash +forge script ... --sig 'verify(address)' +forge script ... --sig 'verifyFlushRewarder(address,address)' +forge script script/deploy/V6UpgradeSimulation.sol:V6UpgradeSimulation \ + --sig 'simulate(address)' --rpc-url $RPC +``` + +Sanity-check by hand that the rollup is inert and correctly owned: + +```bash +cast call "owner()(address)" --rpc-url $RPC # governance +cast call "getEscapeHatch()(address)" --rpc-url $RPC # ZERO until the payload executes +cast call "ESCAPE_HATCH()(address)" --rpc-url $RPC # the hatch it will install +cast call "owner()(address)" --rpc-url $RPC # governance, from construction +cast call "getVersion()(uint256)" --rpc-url $RPC # not already in the registry + +# the payload is bound to the rollup it succeeds; must equal the OUTGOING rollup, not the new one +cast call "PREDECESSOR()(address)" --rpc-url $RPC +cast call $REG "getCanonicalRollup()(address)" --rpc-url $RPC + +# the proof-of-possession gas cap the payload will set on the GSE +cast call "PROOF_OF_POSSESSION_GAS_LIMIT()(uint64)" --rpc-url $RPC # 300000 +``` + +## 6. Governance proposal + +Propose the **payload address**, not the rollup. `GovernanceProposer` wraps it in a `GSEPayload` +automatically. After proposing, read the exact timings off the proposal rather than assuming: + +```bash +cast call "getProposal(uint256)" --rpc-url $RPC +``` + +On mainnet the configured delays are long (voting delay, then voting duration, then execution +delay — on the order of weeks in total), so expect the proposal to sit before it is executable. + +Before execution, re-run the `totalEarmarkedBalance` and `rewardsAvailable` checks from step 3 — +both can move while the proposal is pending, and `rewardsAvailable` is read at execution time. So +is the earmark amount's headroom: `subsidizeAddress` is permissionless, so anyone can raise +`totalEarmarkedBalance` and shrink the implicit pool the reservation draws from. + +### Executing on mainnet: office hours only + +The mainnet payload will only execute **Monday to Friday, 08:00–17:00 London**, DST included — +08:00–17:00 UTC in winter, 07:00–16:00 UTC in summer. Outside that the first action reverts and +the whole execution rolls back, including the `Executed` flag, so the proposal stays executable and +can simply be retried when the window next opens. Nothing is consumed by a rejected attempt. + +Check before sending, rather than discovering it from a revert: + +```bash +# true when the window is open right now +cast call "isWithinExecutionWindow(uint256)(bool)" $(date +%s) --rpc-url $RPC + +# and confirm the chain agrees the restriction applies +cast call "ENFORCE_EXECUTION_WINDOW()(bool)" --rpc-url $RPC +``` + +The longest closed stretch is Friday 17:00 to Monday 08:00, well inside the grace period, so the +window cannot strand a proposal on its own. Sepolia is unrestricted. + +### Before signalling or voting, check the payload is still live + +The payload only authorises a transition **from** the rollup that was canonical when it was +deployed, and it refuses to execute once that is no longer true. Both halves are readable on-chain, +and both should be checked before anything is committed to: + +```bash +# (a) the guard is actually in the action list — expect assertPredecessorIsCanonical first +cast call "getActions()((address,bytes)[])" --rpc-url $RPC + +# (b) the rollup it is bound to is still canonical — these two must be equal +cast call "PREDECESSOR()(address)" --rpc-url $RPC +cast call $REG "getCanonicalRollup()(address)" --rpc-url $RPC +``` + +A registration payload without that guard is the hazard the guard exists to remove: registration is +append-only with last-write-wins and `execute` is permissionless, so an accepted-but-abandoned +payload can be executed by anyone later and demote whatever replaced it — permanently, since +neither the registry nor the GSE re-admits a rollup it already holds. + +**The consequence, which is intended but worth stating.** Once *any* other registration executes, +this payload is dead: it can never execute, even if its proposal was accepted and is still inside +its grace period. Registering v6 then requires deploying a fresh payload — which picks up the new +canonical rollup as its `PREDECESSOR` — and taking it through the full governance cycle again. So a +patched replacement for a bad v6 is not a quick swap; budget the whole cycle for it. + +## 7. After execution + +```bash +cast call $REG "getCanonicalRollup()(address)" --rpc-url $RPC # the new rollup +cast call $RD "canonicalRollup()(address)" --rpc-url $RPC # follows automatically +cast call "getActiveAttesterCount()(uint256)" --rpc-url $RPC # inherits the bonus bucket +cast call $ROLLUP "getActiveAttesterCount()(uint256)" --rpc-url $RPC # outgoing rollup, expect 0 +cast call "rewardsAvailable()(uint256)" --rpc-url $RPC + +# the reservation: total distributor balance UNCHANGED, the amount moved between buckets +cast call $TOKEN "balanceOf(address)(uint256)" $RD --rpc-url $RPC # same as before +cast call $RD "totalEarmarkedBalance()(uint256)" --rpc-url $RPC # up by the amount +cast call $RD "specificRecipientBalance(address)(uint256)" $ROLLUP --rpc-url $RPC # up by the amount +cast call "..." # payload holds none of the asset + +# the retune, read off the OUTGOING rollup +cast call $ROLLUP "getRewardConfig()" --rpc-url $RPC + +# the proof-of-possession gas cap, shared by every rollup on the GSE +cast call $GSE "proofOfPossessionGasLimit()(uint64)" --rpc-url $RPC # 300000 +``` + +The distributor's **total balance not moving** is the check that matters for the reservation: the +funds change bucket, they do not leave. If the total fell, something claimed rather than earmarked. + +The outgoing rollup dropping to **exactly 0** is expected, not a fault: every mainnet attester is +registered against the bonus instance (`moveWithLatestRollup = true`), and the bonus instance is +only visible to whichever rollup is currently canonical. + +## Deliberately not done + +- **Protocol fee recipient and margin.** The rollup launches with `protocolFeeMarginBps = 0` and + `protocolFeeRecipient` set to a placeholder address. Both are `onlyOwner`, so they now require a + governance proposal. **Set the recipient before, or in the same payload as, any non-zero + margin** — otherwise the protocol fee tranche is transferred to an unrecoverable address on + every claim. The first margin increase is exempt from the 30-day cooldown but capped at + 5000 bps by the ×3/2 step on the fee multiplier. +- **Reward distributor migration.** Not needed: the distributor resolves the canonical rollup live + off the registry, so its implicit pool follows v6 the moment action 1 executes. This holds only + while `totalEarmarkedBalance` is 0. +- **Registry address pinning.** `REGISTRY_ADDRESS` is an unvalidated env input. Everything else — + fee asset, staking asset, GSE, governance, reward distributor — is derived from it, so a wrong + registry silently changes all of them together. Double-check it on the command line. + +## Do not renounce ownership of the outgoing rollup + +Retired rollups have had ownership renounced in the past. Two things break if that is done here: + +- the outgoing `FlushRewarder` keeps its unclaimed `debt` and must stay callable for + `claimRewards()`; +- `updateStakingQueueConfig` is the only way to recover a rollup whose entry queue is wedged, and + it is `onlyOwner`. + +Mainnet's outgoing rollup is already `isBootstrapped = true`, so its queue still flushes at +1 validator/epoch from zero and it remains restartable. Renouncing ownership removes the fallback. diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol new file mode 100644 index 00000000000..d6c43ee32d2 --- /dev/null +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -0,0 +1,32 @@ +// GENERATED FILE - DO NOT EDIT +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.27; + +/** + * @title Constants Library + * @author Aztec Labs + * @notice Library that contains constants used throughout the Aztec protocol + */ +library Constants { + // Prime field modulus + uint256 internal constant P = + 21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617; + + uint256 internal constant MAX_FIELD_VALUE = + 21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_616; + uint256 internal constant L1_TO_L2_MSG_SUBTREE_HEIGHT = 10; + uint256 internal constant MAX_L1_TO_L2_MSGS_PER_BLOCK = 256; + uint256 internal constant MAX_L1_TO_L2_MSGS_PER_CHECKPOINT = 1024; + uint256 internal constant MAX_L2_TO_L1_MSGS_PER_TX = 8; + uint256 internal constant INITIAL_CHECKPOINT_NUMBER = 1; + uint256 internal constant MAX_CHECKPOINTS_PER_EPOCH = 32; + uint256 internal constant GENESIS_ARCHIVE_ROOT = + 18_960_341_753_124_503_935_458_443_175_457_612_933_904_560_152_578_324_286_385_834_188_202_890_618_308; + uint256 internal constant EMPTY_EPOCH_OUT_HASH = + 355_785_372_471_781_095_838_790_036_702_437_931_769_306_153_278_986_832_745_847_530_947_941_691_539; + uint256 internal constant FEE_JUICE_ADDRESS = 3; + uint256 internal constant BLS12_POINT_COMPRESSED_BYTES = 48; + uint256 internal constant ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH = 113; + uint256 internal constant DOM_SEP__INBOX_ROLLING_HASH = 3_737_216_265; +} diff --git a/l1-contracts/src/periphery/V6UpgradePayload.md b/l1-contracts/src/periphery/V6UpgradePayload.md new file mode 100644 index 00000000000..2d830a5a974 --- /dev/null +++ b/l1-contracts/src/periphery/V6UpgradePayload.md @@ -0,0 +1,109 @@ +# V6UpgradePayload — what governance is being asked to approve + +A single-use payload that makes the v6 rollup canonical, and nothing else. Deployed by +`script/deploy/DeployRollupForUpgradeV6.s.sol`; executed once, by governance, weeks later. + +This is the reviewer's map. The contract's NatSpec carries the detail — this says what the payload +intends, what it guarantees, and what it deliberately leaves alone. + +## The actions, in order + +Governance runs each as `target.call(data)` from its own address, in this order, in one transaction. + +| # | Action | Present when | Why | +|---|---|---|---| +| 1 | `this.assertPredecessorIsCanonical()` | always | this payload only authorises a transition **from** the rollup that was canonical when it was deployed | +| 2 | `this.assertWithinExecutionWindow()` | `ENFORCE_EXECUTION_WINDOW` | mainnet only: UK weekdays 08:00–17:00 London, so an upgrade is not executed unattended | +| 3 | `distributor.recoverFrom(v5, payload, EARMARK_AMOUNT)` | `EARMARK_AMOUNT > 0` | draws the reservation for the outgoing rollup while it can still reach the implicit pool | +| 4 | `this.forwardEarmark()` | `EARMARK_AMOUNT > 0` | parks it in the outgoing rollup's own earmarked bucket | +| 5 | `v5.setRewardConfig(...)` | `RETUNE_PREDECESSOR_REWARDS` | retunes the outgoing rollup's reward split | +| 6 | `v6.setEscapeHatch(ESCAPE_HATCH)` | always | installs the escape hatch, before v6 is canonical | +| 7 | `Registry.addRollup(v6)` | always | makes v6 canonical | +| 8 | `GSE.addRollup(v6)` | always | existing attesters follow without withdrawing and redepositing | +| 9 | `oldFlushRewarder.recover(asset, newRewarder, rewardsAvailable())` | a rewarder exists | carries the entry-queue flush incentive to the replacement | +| 10 | `GSE.setProofOfPossessionGasLimit(PROOF_OF_POSSESSION_GAS_LIMIT)` | `PROOF_OF_POSSESSION_GAS_LIMIT > 0` | raises the cap on BLS proof-of-possession verification so honest keys with an expensive verification are not refunded and dropped | + +Four to ten actions depending on chain. Sepolia has neither the window nor a rewarder. + +Action 10 depends on nothing above it, and nothing above depends on it: the cap is only read when an +entry queue is flushed, which cannot happen inside this transaction. It goes last so the other +actions keep their positions whether or not it is present. The GSE is shared, so the new cap also +applies to the outgoing rollup's flushes from the moment the payload executes. + +## What it guarantees + +- **Only the approved transition.** `PREDECESSOR` is read from the registry in the constructor and + compared at execution. If any other registration lands first, this payload can never execute. +- **All or nothing.** `Governance.execute` requires success per action inside one transaction, so a + failed check leaves *nothing* behind — not a registration, not a moved GSE, not a moved balance. + This holds regardless of where in the list the failure happens. +- **It stays readable when it is dead.** Both checks are actions rather than reverts inside + `getActions()`, so explorers, `GSEPayload.amIValid` and the deploy simulation can still read what + the payload would do after it can no longer do it. +- **The deploy key holds no power over v6, at any point.** The rollup is constructed owned by + governance rather than transferred afterwards. That matters beyond ownership: the same + constructor argument becomes the Slasher's immutable `GOVERNANCE`, which can slash any attester + with no vote and no delay, and `transferOwnership` cannot move it. Constructing with governance + is what keeps it out of the deploy key's hands — at the cost of the escape hatch having to be + installed here, by governance, rather than during the deploy. + +## What it deliberately does not do + +- **No reward-distributor migration.** The distributor resolves the canonical rollup live off the + registry, so its implicit pool follows v6 the moment `Registry.addRollup` executes. +- **No protocol fee margin or recipient.** The rollup launches with margin `0` and a placeholder + recipient. Both are `onlyOwner` and now need their own proposal. **Set the recipient before, or + in the same payload as, any non-zero margin**, or the fee tranche goes to an unrecoverable + address on every claim. +- **It does not retire the outgoing rollup.** Its ownership must NOT be renounced: the old + `FlushRewarder` keeps unclaimed debt and must stay callable, and `updateStakingQueueConfig` is the + only way to recover a wedged entry queue. + +## What to check before signalling or voting + +```bash +cast call "PREDECESSOR()(address)" # must equal the CURRENT canonical rollup +cast call $REG "getCanonicalRollup()(address)" +cast call "getActions()((address,bytes)[])" # guard must be present, and first +cast call "owner()(address)" # governance, from construction +cast call "GOVERNANCE()(address)" # governance -- NOT the deploy key +cast call "PROOF_OF_POSSESSION_GAS_LIMIT()(uint64)" # 300000; 0 means no cap action +cast call "proofOfPossessionGasLimit()(uint64)" # the cap it replaces, 250000 today +``` + +A registration payload without that guard is the hazard the guard exists to remove. + +## Known limits, stated rather than discovered + +- **Once any other registration executes, this payload is dead.** Intended — voters re-approve + "v6 succeeds X" explicitly rather than letting execution order decide — but it means a patched + replacement needs a fresh deploy and a full governance cycle, not a quick swap. +- **`totalEarmarkedBalance` is not enforced on-chain.** A non-zero earmark at execution shrinks the + implicit pool v6 inherits. It is recoverable by governance via `recoverFrom`, and it is *not* + enforced here on purpose: `subsidizeAddress` is permissionless, so a 1-wei call from anyone would + otherwise block the upgrade indefinitely. Check it before execution; treat it as an accounting + surprise, not a lost-funds event. +- **The execution window hardcodes the UK DST rule.** Derived from the rule rather than tabulated, + so it has no expiry — but it would be wrong if the rule itself changed. +- **The proof-of-possession gas cap is checked against the GSE only at deployment.** The constructor + rejects a non-zero value that does not exceed the cap it reads then, since a lower cap would start + rejecting keys that verify today. If governance changes the cap while the proposal is pending, + the action still sets the value in the payload. It sets no upper bound: the value is a reviewed + literal in the deploy table, and a higher cap only raises what a failing entry can burn in a flush. + It also does not make every honest key fit: the verification cost has an unbounded tail, so 300k + lowers the rejection rate (about 1 in 2.5M keys, against 1 in 28.6k at 250k, under Osaka pricing) + rather than removing it. +- **`REGISTRY_ADDRESS` is an unvalidated deploy input.** Everything else is derived from it, so a + wrong registry changes all of them together and silently. + +## Where things are + +| | | +|---|---| +| Contract | `src/periphery/V6UpgradePayload.sol` | +| Unit tests — actions, constructor, calendar | `test/periphery/V6UpgradePayload.t.sol` | +| Atomicity through real governance | `test/governance/scenario/V6UpgradeAtomicity.t.sol` | +| Deploy script and its config table | `script/deploy/DeployRollupForUpgradeV6.s.sol` | +| Config table deployed on both chains | `test/script/DeployRollupForUpgradeV6.t.sol` | +| Fork simulation of the full lifecycle | `script/deploy/V6UpgradeSimulation.sol` | +| How to run the upgrade | `script/deploy/V6_UPGRADE_RUNBOOK.md` | diff --git a/l1-contracts/src/periphery/V6UpgradePayload.sol b/l1-contracts/src/periphery/V6UpgradePayload.sol new file mode 100644 index 00000000000..a1a0f9fd477 --- /dev/null +++ b/l1-contracts/src/periphery/V6UpgradePayload.sol @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {IEscapeHatch} from "@aztec/core/interfaces/IEscapeHatch.sol"; +import {IInstance} from "@aztec/core/interfaces/IInstance.sol"; +import {IRollupCore} from "@aztec/core/interfaces/IRollup.sol"; +import {IValidatorSelectionCore} from "@aztec/core/interfaces/IValidatorSelection.sol"; +import {Bps, MutableRewardConfig} from "@aztec/core/libraries/rollup/RewardLib.sol"; +import {IGSECore} from "@aztec/governance/GSE.sol"; +import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; +import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; +import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; +import {FlushRewarder} from "@aztec/periphery/FlushRewarder.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; + +/** + * @title V6UpgradePayload + * @author Aztec Labs + * @notice The payload governance executes to make the v6 rollup canonical. + * @dev Governance executes each action as `target.call(data)` from its own address, in order, in a + * single transaction that reverts as a whole if any action fails. Every target below is + * `onlyOwner` and governance owns all of them, which is what makes this payload the only way + * to perform these steps. + * + * Extend `getActions` to bundle further governance-gated calls (for example + * `setProtocolFeeRecipient` / `setProtocolFeeMargin`, which cannot be set any other way once + * the deploy script has handed rollup ownership to governance). + */ +contract V6UpgradePayload is IPayload { + /// @notice London-local hour the window opens, inclusive. + uint256 public constant WINDOW_OPEN_HOUR = 8; + + /// @notice London-local hour the window closes, exclusive. + uint256 public constant WINDOW_CLOSE_HOUR = 17; + + /// @notice The registry that resolves the canonical rollup for the protocol. + IRegistry public immutable REGISTRY; + + /// @notice The rollup being made canonical. + IInstance public immutable ROLLUP; + + /// @notice The canonical rollup at deployment -- the one {ROLLUP} is built to succeed. + /// @dev Public so it can be checked before signalling: a payload whose PREDECESSOR is no longer + /// canonical can never execute, and that is readable on-chain before any vote is committed. + address public immutable PREDECESSOR; + + /// @notice The escape hatch this payload installs on {ROLLUP}. + /// @dev Installed HERE rather than during the deploy. `setEscapeHatch` is `onlyOwner`, and the + /// rollup is constructed owned by governance -- because that same constructor argument also + /// becomes the Slasher's immutable GOVERNANCE, which can slash any attester with no vote. + /// Constructing with the deployer to do owner-gated setup would hand that power to the + /// deploy key permanently, and no later `transferOwnership` could take it back. + address public immutable ESCAPE_HATCH; + + /// @notice The flush rewarder serving the outgoing rollup, or zero on a chain that has none. + FlushRewarder public immutable OLD_FLUSH_REWARDER; + + /// @notice The replacement flush rewarder, deployed by this constructor and bound to {ROLLUP}. + /// Zero when {OLD_FLUSH_REWARDER} is zero. + FlushRewarder public immutable NEW_FLUSH_REWARDER; + + /// @notice Whether execution is restricted to UK office hours. Set per chain by the deploy script. + bool public immutable ENFORCE_EXECUTION_WINDOW; + + /// @notice The distributor holding the reward pool, read off the registry at construction. + /// @dev Zero when {EARMARK_AMOUNT} is zero: a payload that reserves nothing has no business + /// requiring the chain to have a distributor at all. + IRewardDistributor public immutable REWARD_DISTRIBUTOR; + + /// @notice The asset that distributor pays out, read off {ROLLUP} at construction. + /// @dev Derived rather than configured: mainnet and Sepolia use different distributors and + /// different fee assets, and both follow from the registry and the rollup, so they cannot + /// drift from the chain the payload is actually pointed at. + IERC20 public immutable REWARD_ASSET; + + /// @notice Reward-pool balance to reserve for {PREDECESSOR} before it stops being canonical. + /// Zero omits the reservation entirely. + /// @dev The recipient is {PREDECESSOR} -- the OUTGOING rollup -- not {ROLLUP}. Earmarking to the + /// incoming rollup would be pointless: it becomes canonical and gains the whole implicit + /// pool regardless. The outgoing rollup is the one that loses implicit-pool access the + /// moment `Registry.addRollup` runs, because the distributor resolves `canonicalRollup()` + /// live off the registry, so anything it should keep has to be moved into its own earmarked + /// bucket first. Whatever is reserved here is subtracted from what {ROLLUP} can claim. + uint256 public immutable EARMARK_AMOUNT; + + /// @notice Whether to retune {PREDECESSOR}'s reward split before it stops being canonical. + bool public immutable RETUNE_PREDECESSOR_REWARDS; + + /// @notice The sequencer share, in basis points, to leave {PREDECESSOR} on. + /// @dev Applies to the OUTGOING rollup only. {ROLLUP} keeps whatever the deploy script gave it. + /// A flag gates this rather than a sentinel value, because both zero and 10000 are + /// meaningful splits and neither can stand in for "leave it alone". + uint16 public immutable PREDECESSOR_SEQUENCER_BPS; + + /// @notice The checkpoint reward to leave {PREDECESSOR} on. + uint96 public immutable PREDECESSOR_CHECKPOINT_REWARD; + + /// @notice The proof-of-possession gas cap to set on the GSE. Zero omits the action entirely. + /// @dev The GSE verifies each new attester's BLS proof of possession in a sub-call capped at this + /// much gas, and a deposit whose verification does not fit is refunded and dropped. The + /// verification cost varies per key, because `BN254Lib.hashToPoint` is a rejection-sampling + /// loop with an unbounded tail, and the Osaka modexp repricing (EIP-7883) raised the cost of + /// each of its iterations. Under that pricing about 1 in 28.6k honestly generated keys cost + /// more than the GSE's original 250k cap, and about 1 in 2.5M cost more than 300k. The price + /// of a higher cap is that an entry whose verification fails can burn more gas in the flush + /// that processes it. + /// + /// The GSE is shared, so the new cap applies to every rollup it serves, {PREDECESSOR} + /// included, from the moment the payload executes. + uint64 public immutable PROOF_OF_POSSESSION_GAS_LIMIT; + + /// @notice Thrown when the supplied flush rewarder serves a rollup other than the outgoing one. + error V6UpgradePayload__FlushRewarderRollupMismatch(address served, address outgoing); + + /// @notice Thrown when governance executes outside the permitted window. + error V6UpgradePayload__OutsideExecutionWindow(uint256 timestamp); + + /// @notice Thrown when the rollup this payload was built to succeed is no longer canonical. + error V6UpgradePayload__PredecessorNotCanonical(address expected, address actual); + + /// @notice Thrown when the supplied escape hatch was built for a different rollup. + error V6UpgradePayload__EscapeHatchRollupMismatch(address served, address expected); + + /// @notice Thrown when a non-zero proof-of-possession gas cap does not exceed the GSE's current one. + error V6UpgradePayload__ProofOfPossessionGasLimitNotRaised(uint64 current, uint64 proposed); + + /** + * @notice Binds the payload to the rollup it will make canonical, and deploys the replacement + * flush rewarder when there is an outgoing one to migrate. + * @param _registry The registry to register the rollup in + * @param _rollup The newly deployed rollup + * @param _escapeHatch The escape hatch built for {_rollup}, installed by this payload + * @param _oldFlushRewarder The flush rewarder serving the outgoing rollup, or zero to skip the + * flush-incentive migration entirely + * @param _enforceExecutionWindow Whether to restrict execution to UK office hours + * @param _earmarkAmount Reward-pool balance to reserve for the outgoing rollup, or zero for none + * @param _retunePredecessorRewards Whether to rewrite the outgoing rollup's reward split + * @param _predecessorSequencerBps The sequencer share to leave the outgoing rollup on + * @param _predecessorCheckpointReward The checkpoint reward to leave the outgoing rollup on + * @param _proofOfPossessionGasLimit The proof-of-possession gas cap to set on the GSE, or zero to + * leave it unchanged. When non-zero it must exceed the GSE's cap at deployment + */ + constructor( + IRegistry _registry, + IInstance _rollup, + IEscapeHatch _escapeHatch, + FlushRewarder _oldFlushRewarder, + bool _enforceExecutionWindow, + uint256 _earmarkAmount, + bool _retunePredecessorRewards, + uint16 _predecessorSequencerBps, + uint96 _predecessorCheckpointReward, + uint64 _proofOfPossessionGasLimit + ) { + REGISTRY = _registry; + ROLLUP = _rollup; + + // The hatch names the rollup it was built for, and `setEscapeHatch` is one-shot, so a hatch + // bound elsewhere would burn the only chance to install one. + address hatchRollup = _escapeHatch.getRollup(); + require(hatchRollup == address(_rollup), V6UpgradePayload__EscapeHatchRollupMismatch(hatchRollup, address(_rollup))); + ESCAPE_HATCH = address(_escapeHatch); + OLD_FLUSH_REWARDER = _oldFlushRewarder; + ENFORCE_EXECUTION_WINDOW = _enforceExecutionWindow; + // Only reached for when the reservation is actually used, so a payload without one stays + // constructible against a registry or rollup that does not answer these. + EARMARK_AMOUNT = _earmarkAmount; + REWARD_DISTRIBUTOR = _earmarkAmount > 0 ? _registry.getRewardDistributor() : IRewardDistributor(address(0)); + REWARD_ASSET = _earmarkAmount > 0 ? _rollup.getFeeAsset() : IERC20(address(0)); + RETUNE_PREDECESSOR_REWARDS = _retunePredecessorRewards; + PREDECESSOR_SEQUENCER_BPS = _predecessorSequencerBps; + PREDECESSOR_CHECKPOINT_REWARD = _predecessorCheckpointReward; + + // The GSE accepts any value, and a cap below the cost of keys that verify today would start + // rejecting deposits that currently succeed, so this only ever raises it. Checked against the + // cap at deployment, because that is what voters compare the payload against. Like the other + // optional actions, the GSE is only read when the action is used. + if (_proofOfPossessionGasLimit > 0) { + uint64 currentLimit = _rollup.getGSE().proofOfPossessionGasLimit(); + require( + _proofOfPossessionGasLimit > currentLimit, + V6UpgradePayload__ProofOfPossessionGasLimitNotRaised(currentLimit, _proofOfPossessionGasLimit) + ); + } + PROOF_OF_POSSESSION_GAS_LIMIT = _proofOfPossessionGasLimit; + + // Read ONCE and bound as an immutable, so the rewarder check below and the execution-time + // guard are talking about the same rollup by construction. Unconditional, so this reverts + // with `Registry__NoRollupsRegistered` against an empty registry -- this payload succeeds a + // rollup and cannot be used for a first registration. + address outgoingRollup = address(_registry.getCanonicalRollup()); + PREDECESSOR = outgoingRollup; + + // Nothing on the rollup points back at its flush rewarder -- a FlushRewarder is a + // permissionless wrapper around the permissionless `flushEntryQueue`, so a rollup may have + // any number of them and knows about none. The address therefore has to be supplied, but it + // can be checked: the rewarder names the rollup it serves, which must be the one being + // replaced. This rejects a rewarder for a foreign or already-retired rollup. + if (address(_oldFlushRewarder) != address(0)) { + address servedRollup = address(_oldFlushRewarder.ROLLUP()); + require( + servedRollup == outgoingRollup, V6UpgradePayload__FlushRewarderRollupMismatch(servedRollup, outgoingRollup) + ); + } + + // A FlushRewarder's `ROLLUP` is immutable, so the incentive cannot follow the upgrade: the + // replacement has to be a new contract. Asset and rate are mirrored off the outgoing one so + // the incentive carries over unchanged, and governance owns it so it stays adjustable. + NEW_FLUSH_REWARDER = address(_oldFlushRewarder) == address(0) + ? FlushRewarder(address(0)) + : new FlushRewarder( + _registry.getGovernance(), _rollup, _oldFlushRewarder.REWARD_ASSET(), _oldFlushRewarder.rewardPerInsertion() + ); + } + + /// @notice Moves everything this payload holds into the distributor, reserved for {PREDECESSOR}. + /// @dev Reads its own balance rather than taking an amount, so it cannot disagree with the + /// `recoverFrom` action that funds it. Permissionless by design: it can only ever push the + /// payload's own balance to one fixed recipient, and leaving it callable means a stray + /// transfer here is recoverable rather than stranded. + // solhint-disable-next-line comprehensive-interface + function forwardEarmark() external { + uint256 amount = REWARD_ASSET.balanceOf(address(this)); + REWARD_ASSET.approve(address(REWARD_DISTRIBUTOR), amount); + REWARD_DISTRIBUTOR.subsidizeAddress(PREDECESSOR, amount); + } + + /// @inheritdoc IPayload + function getActions() external view override(IPayload) returns (IPayload.Action[] memory) { + bool migrateFlushRewarder = address(OLD_FLUSH_REWARDER) != address(0); + uint256 next = 0; + + bool earmark = EARMARK_AMOUNT > 0; + bool raiseProofOfPossessionGasLimit = PROOF_OF_POSSESSION_GAS_LIMIT > 0; + + // Always: predecessor guard, setEscapeHatch, Registry.addRollup, GSE.addRollup. + IPayload.Action[] memory res = new IPayload + .Action[]( + 4 + (ENFORCE_EXECUTION_WINDOW ? 1 : 0) + (earmark ? 2 : 0) + (RETUNE_PREDECESSOR_REWARDS ? 1 : 0) + + (migrateFlushRewarder ? 1 : 0) + (raiseProofOfPossessionGasLimit ? 1 : 0) + ); + + // FIRST, before the window and before anything is written: this payload only authorises a + // transition FROM the rollup that was canonical when it was deployed. + // + // Registration is append-only with last-write-wins, and `execute` is permissionless, so two + // accepted registrations are a hazard: if this one is abandoned and a replacement executes + // first, anyone could later execute this one and demote the replacement -- permanently, since + // neither registry re-admits a rollup it already holds. Binding the predecessor makes a stale + // payload inert instead: it reverts, the proposal stays Executable until it expires, and + // nothing moves. + // + // Ahead of the window check deliberately. Both are non-mutating so the order is free, and of + // the two failures "this payload must never run" is the one worth surfacing over "come back + // on Monday". + res[next++] = + Action({target: address(this), data: abi.encodeWithSelector(this.assertPredecessorIsCanonical.selector)}); + + // The window is enforced as its own action rather than inside `getActions` so that reading the + // proposal stays possible at any time -- explorers, `GSEPayload.amIValid` and the deploy + // script's simulation all call `getActions`, and a revert here would break them out of hours. + // The same is true of the guard above, which is why it is an action too: a stale payload must + // still be READABLE, so that what it would do stays inspectable after it can no longer do it. + // Governance reverts the whole execution on any failed action and rolls back the Executed flag + // with it, so a rejected attempt leaves the proposal executable again once the window opens. + if (ENFORCE_EXECUTION_WINDOW) { + res[next++] = + Action({target: address(this), data: abi.encodeWithSelector(this.assertWithinExecutionWindow.selector)}); + } + + if (earmark) { + // Reserves part of the reward pool for the OUTGOING rollup, and must run before + // `Registry.addRollup` below. The distributor resolves `canonicalRollup()` live, so + // {PREDECESSOR} can only reach the implicit (un-earmarked) pool while it is still canonical; + // one action later that access is gone for good. + // + // Drawing FROM {PREDECESSOR} and earmarking back TO {PREDECESSOR} reads like a no-op and is + // not: it converts pool access that is about to lapse into a balance that survives, because + // `specificRecipientBalance` is keyed by address and is indifferent to which rollup is + // canonical. The predecessor guard above is what makes the draw safe -- it has already + // established that {PREDECESSOR} is canonical at this point. + res[next++] = Action({ + target: address(REWARD_DISTRIBUTOR), + data: abi.encodeWithSelector( + IRewardDistributor.recoverFrom.selector, PREDECESSOR, address(this), EARMARK_AMOUNT + ) + }); + + // `subsidizeAddress` pulls via `transferFrom`, so it needs an allowance from the holder, and + // governance can never grant one: `Governance.execute` refuses any action targeting the + // asset. Routing through this contract is what makes the round trip possible at all. + res[next++] = Action({target: address(this), data: abi.encodeWithSelector(this.forwardEarmark.selector)}); + } + + if (RETUNE_PREDECESSOR_REWARDS) { + // Retunes the OUTGOING rollup's split, and must also land before `Registry.addRollup`: the + // point is for it to be in force for whatever {PREDECESSOR} still settles on its way out. + // {ROLLUP} is untouched and keeps the values it was constructed with. + // + // `setRewardConfig` has no cooldown and no step cap -- unlike `setProvingCostPerMana` and + // `setProtocolFeeMargin`, which are both rate limited -- so this lands the moment the payload + // executes. The split is read at PROOF time, so it also reaches checkpoints already proposed + // under the old split but not yet proven. + res[next++] = Action({ + target: PREDECESSOR, + data: abi.encodeWithSelector( + IRollupCore.setRewardConfig.selector, + MutableRewardConfig({ + sequencerBps: Bps.wrap(PREDECESSOR_SEQUENCER_BPS), checkpointReward: PREDECESSOR_CHECKPOINT_REWARD + }) + ) + }); + } + + // Installs the escape hatch, BEFORE the rollup becomes canonical. `setEscapeHatch` is + // `onlyOwner` and one-shot, and governance has owned this rollup since construction -- see + // {ESCAPE_HATCH} for why it is not owned by the deployer even briefly. + res[next++] = Action({ + target: address(ROLLUP), + data: abi.encodeWithSelector(IValidatorSelectionCore.setEscapeHatch.selector, ESCAPE_HATCH) + }); + + // Registers the rollup under its own version, making it the canonical rollup. Reverts if that + // version is already registered. + res[next++] = + Action({target: address(REGISTRY), data: abi.encodeWithSelector(IRegistry.addRollup.selector, address(ROLLUP))}); + + // Lets the GSE recognise the rollup as a valid instance, so existing attesters follow the + // upgrade without withdrawing and redepositing their stake. + res[next++] = Action({ + target: address(ROLLUP.getGSE()), data: abi.encodeWithSelector(IGSECore.addRollup.selector, address(ROLLUP)) + }); + + if (migrateFlushRewarder) { + // Moves the outgoing rewarder's unowed balance to the replacement. `recover` caps the + // reward asset at `rewardsAvailable()` (balance minus rewards already accrued to flushers + // who have not claimed), so the outgoing rewarder deliberately keeps that remainder and + // must stay callable for `claimRewards`. Read at execution time, not at deploy time. + res[next++] = Action({ + target: address(OLD_FLUSH_REWARDER), + data: abi.encodeWithSelector( + FlushRewarder.recover.selector, + address(NEW_FLUSH_REWARDER.REWARD_ASSET()), + address(NEW_FLUSH_REWARDER), + OLD_FLUSH_REWARDER.rewardsAvailable() + ) + }); + } + + if (raiseProofOfPossessionGasLimit) { + // Raises the GSE's proof-of-possession gas cap; see {PROOF_OF_POSSESSION_GAS_LIMIT}. It does + // not depend on the registrations above and nothing above depends on it: the cap is read + // only when an entry queue is flushed, which cannot happen inside this transaction. It goes + // last so the transition's own actions keep their positions whether or not it is present. + res[next++] = Action({ + target: address(ROLLUP.getGSE()), + data: abi.encodeCall(IGSECore.setProofOfPossessionGasLimit, (PROOF_OF_POSSESSION_GAS_LIMIT)) + }); + } + + return res; + } + + /// @notice Reverts unless {PREDECESSOR} is still the canonical rollup. + /// @dev Targeted by the first action. Voters can also call it directly before signalling to + /// confirm the payload is still live. + // solhint-disable-next-line comprehensive-interface + function assertPredecessorIsCanonical() external view { + address canonical = address(REGISTRY.getCanonicalRollup()); + require(canonical == PREDECESSOR, V6UpgradePayload__PredecessorNotCanonical(PREDECESSOR, canonical)); + } + + /// @notice Reverts unless the current time is inside the permitted execution window. + /// @dev Targeted by an action, after the predecessor guard, when {ENFORCE_EXECUTION_WINDOW} is set. + // solhint-disable-next-line comprehensive-interface + function assertWithinExecutionWindow() external view { + require(isWithinExecutionWindow(block.timestamp), V6UpgradePayload__OutsideExecutionWindow(block.timestamp)); + } + + /// @inheritdoc IPayload + function getURI() external pure override(IPayload) returns (string memory) { + return "V6UpgradePayload"; + } + + /// @notice Whether `_timestamp` falls on a UK weekday between 08:00 (inclusive) and 17:00 + /// (exclusive) London time. + /// @dev Public so the window can be queried before proposing or executing. + // solhint-disable-next-line comprehensive-interface + function isWithinExecutionWindow(uint256 _timestamp) public pure returns (bool) { + uint256 london = _timestamp + (_isBritishSummerTime(_timestamp) ? 1 hours : 0); + + // 1970-01-01 was a Thursday, so adding 4 puts Sunday at 0 and Saturday at 6. + uint256 weekday = (london / 1 days + 4) % 7; + if (weekday == 0 || weekday == 6) { + return false; + } + + uint256 hourOfDay = (london % 1 days) / 1 hours; + return hourOfDay >= WINDOW_OPEN_HOUR && hourOfDay < WINDOW_CLOSE_HOUR; + } + + /// @dev British Summer Time runs from 01:00 UTC on the last Sunday of March to 01:00 UTC on the + /// last Sunday of October. Derived rather than tabulated so the payload has no expiry date. + function _isBritishSummerTime(uint256 _timestamp) private pure returns (bool) { + uint256 year = _yearFromDays(_timestamp / 1 days); + uint256 start = _lastSundayOfMonth(year, 3) * 1 days + 1 hours; + uint256 end = _lastSundayOfMonth(year, 10) * 1 days + 1 hours; + return _timestamp >= start && _timestamp < end; + } + + /// @dev Days since the epoch of the last Sunday in `_month` of `_year`. Only used for March and + /// October, which both have 31 days, so the month length needs no lookup. + function _lastSundayOfMonth(uint256 _year, uint256 _month) private pure returns (uint256) { + uint256 lastDay = _daysFromCivil(_year, _month, 31); + return lastDay - ((lastDay + 4) % 7); + } + + /// @dev Days since 1970-01-01 for a proleptic Gregorian date. Howard Hinnant's `days_from_civil`, + /// valid for every date this contract can see. + function _daysFromCivil(uint256 _year, uint256 _month, uint256 _day) private pure returns (uint256) { + uint256 y = _year - (_month <= 2 ? 1 : 0); + uint256 era = y / 400; + uint256 yoe = y - era * 400; + uint256 doy = (153 * (_month > 2 ? _month - 3 : _month + 9) + 2) / 5 + _day - 1; + uint256 doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146_097 + doe - 719_468; + } + + /// @dev Calendar year containing `_days` days since 1970-01-01. The inverse of {_daysFromCivil}, + /// keeping only the year. + function _yearFromDays(uint256 _days) private pure returns (uint256) { + uint256 z = _days + 719_468; + uint256 era = z / 146_097; + uint256 doe = z - era * 146_097; + uint256 yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; + uint256 doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + uint256 mp = (5 * doy + 2) / 153; + return (mp < 10 ? mp + 3 : mp - 9) <= 2 ? yoe + era * 400 + 1 : yoe + era * 400; + } +} diff --git a/l1-contracts/test/fork/MainnetV6PopGasLimit.t.sol b/l1-contracts/test/fork/MainnetV6PopGasLimit.t.sol new file mode 100644 index 00000000000..7f9155ac884 --- /dev/null +++ b/l1-contracts/test/fork/MainnetV6PopGasLimit.t.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 Aztec Labs. +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; + +import {IEscapeHatch} from "@aztec/core/interfaces/IEscapeHatch.sol"; +import {IInstance} from "@aztec/core/interfaces/IInstance.sol"; +import {GSE} from "@aztec/governance/GSE.sol"; +import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; +import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; +import {FlushRewarder} from "@aztec/periphery/FlushRewarder.sol"; +import {V6UpgradePayload} from "@aztec/periphery/V6UpgradePayload.sol"; + +/// @dev Enough of a rollup for the payload's constructor and action list: a GSE to point at. +contract ForkStandInRollup { + address internal immutable GSE_; + + constructor(address _gse) { + GSE_ = _gse; + } + + function getGSE() external view returns (GSE) { + return GSE(GSE_); + } +} + +/// @dev The payload only reads `getRollup()` off the hatch. +contract ForkStandInEscapeHatch { + address internal immutable ROLLUP_; + + constructor(address _rollup) { + ROLLUP_ = _rollup; + } + + function getRollup() external view returns (address) { + return ROLLUP_; + } +} + +/** + * @notice Checks the v6 payload's proof-of-possession gas cap action against the live mainnet GSE: + * that governance owns the GSE, so the action can execute, and that the encoded call sets + * the cap when governance makes it. + * @dev Skipped unless `MAINNET_FORK_RPC_URL` is set, so it never runs in CI. Forks the latest block: + * what matters is who owns the GSE when the payload executes, not at some pinned block. + * MAINNET_FORK_RPC_URL= forge test --match-path test/fork/MainnetV6PopGasLimit.t.sol + */ +contract MainnetV6PopGasLimitTest is Test { + string internal constant MAINNET_RPC_URL_ENV = "MAINNET_FORK_RPC_URL"; + + IRegistry internal constant MAINNET_REGISTRY = IRegistry(0x35b22e09Ee0390539439E24f06Da43D83f90e298); + address internal constant MAINNET_GSE = 0xa92ecFD0E70c9cd5E5cd76c50Af0F7Da93567a4f; + + uint64 internal constant POP_GAS_LIMIT = 300_000; + + function setUp() public { + string memory rpcUrl = vm.envOr(MAINNET_RPC_URL_ENV, string("")); + if (bytes(rpcUrl).length == 0) { + vm.skip(true); + return; + } + vm.createSelectFork(rpcUrl); + } + + function test_LiveGseIsOwnedByGovernance() public view { + address canonical = address(MAINNET_REGISTRY.getCanonicalRollup()); + assertEq(address(IInstance(canonical).getGSE()), MAINNET_GSE, "canonical rollup uses another GSE"); + assertEq(GSE(MAINNET_GSE).owner(), MAINNET_REGISTRY.getGovernance(), "GSE owner is not governance"); + } + + function test_PayloadActionRaisesTheLiveCap() public { + GSE gse = GSE(MAINNET_GSE); + assertLt(gse.proofOfPossessionGasLimit(), POP_GAS_LIMIT, "live cap is already at or above the target"); + + address incoming = address(new ForkStandInRollup(MAINNET_GSE)); + V6UpgradePayload payload = new V6UpgradePayload( + MAINNET_REGISTRY, + IInstance(incoming), + IEscapeHatch(address(new ForkStandInEscapeHatch(incoming))), + FlushRewarder(address(0)), + false, + 0, + false, + 0, + 0, + POP_GAS_LIMIT + ); + + IPayload.Action[] memory actions = payload.getActions(); + IPayload.Action memory raise = actions[actions.length - 1]; + assertEq(raise.target, MAINNET_GSE, "cap action does not target the live GSE"); + + // Executed alone, as governance would execute it: the other actions need a real v6 rollup. + vm.prank(MAINNET_REGISTRY.getGovernance()); + (bool ok,) = raise.target.call(raise.data); + assertTrue(ok, "governance could not set the cap"); + assertEq(gse.proofOfPossessionGasLimit(), POP_GAS_LIMIT, "cap not raised"); + } +} diff --git a/l1-contracts/test/governance/scenario/V6UpgradeAtomicity.t.sol b/l1-contracts/test/governance/scenario/V6UpgradeAtomicity.t.sol new file mode 100644 index 00000000000..0a57e274700 --- /dev/null +++ b/l1-contracts/test/governance/scenario/V6UpgradeAtomicity.t.sol @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: UNLICENSED +// solhint-disable +pragma solidity >=0.8.27; + +import {IInstance} from "@aztec/core/interfaces/IInstance.sol"; +import {Rollup} from "@aztec/core/Rollup.sol"; +import {Governance} from "@aztec/governance/Governance.sol"; +import {GovernanceProposer} from "@aztec/governance/proposer/GovernanceProposer.sol"; +import {GSEPayload} from "@aztec/governance/GSEPayload.sol"; +import {IGSE, GSE} from "@aztec/governance/GSE.sol"; +import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; +import {IRegistry, IHaveVersion} from "@aztec/governance/interfaces/IRegistry.sol"; +import {Registry} from "@aztec/governance/Registry.sol"; +import {Errors} from "@aztec/governance/libraries/Errors.sol"; +import {Proposal, ProposalState} from "@aztec/governance/interfaces/IGovernance.sol"; +import {IEscapeHatch} from "@aztec/core/interfaces/IEscapeHatch.sol"; +import {FlushRewarder} from "@aztec/periphery/FlushRewarder.sol"; +import {V6UpgradePayload} from "@aztec/periphery/V6UpgradePayload.sol"; +import {Timestamp, Slot} from "@aztec/core/libraries/TimeLib.sol"; +import {StakingQueueConfig} from "@aztec/core/libraries/compressed-data/StakingQueueConfig.sol"; +import {BN254Lib} from "@aztec/shared/libraries/BN254Lib.sol"; +import {MultiAdder, CheatDepositArgs} from "@aztec/mock/MultiAdder.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; +import {TestBase} from "@test/base/Base.sol"; +import {TestConstants} from "../../harnesses/TestConstants.sol"; +import {RollupBuilder} from "../../builder/RollupBuilder.sol"; +import {UncompressedProposalWrapper} from "@test/governance/helpers/UncompressedProposalTestLib.sol"; + +/// @dev The payload only reads `getRollup()` off the hatch it installs. +contract StubEscapeHatch { + address internal immutable ROLLUP_; + + constructor(address _rollup) { + ROLLUP_ = _rollup; + } + + function getRollup() external view returns (address) { + return ROLLUP_; + } +} + +/// @dev Stands in for a rollup being registered: a version to key on, a GSE to point at. +contract StandInRollup { + IGSE public immutable GSE_; + + constructor(IGSE _gse) { + GSE_ = _gse; + } + + function getVersion() external view returns (uint256) { + return uint256(keccak256(abi.encodePacked(bytes("aztec_rollup"), block.chainid, address(this)))); + } + + function getGSE() external view returns (GSE) { + return GSE(address(GSE_)); + } + + /// @dev Lets a payload that installs a hatch execute in full. Unguarded: only governance calls it here. + function setEscapeHatch(address) external {} +} + +/** + * @dev Two actions where the FIRST succeeds and the SECOND reverts, so the test can ask what + * `Governance.execute` leaves behind. Shaped like the real payload -- `Registry.addRollup` + * then something else -- because that is the ordering whose rollback actually matters. + */ +contract RegisterThenFailPayload is IPayload { + IRegistry public immutable REGISTRY; + address public immutable ROLLUP; + + error DeliberateFailure(); + + constructor(IRegistry _registry, address _rollup) { + REGISTRY = _registry; + ROLLUP = _rollup; + } + + function getActions() external view override(IPayload) returns (IPayload.Action[] memory) { + IPayload.Action[] memory res = new IPayload.Action[](2); + res[0] = Action({target: address(REGISTRY), data: abi.encodeWithSelector(IRegistry.addRollup.selector, ROLLUP)}); + res[1] = Action({target: address(this), data: abi.encodeWithSelector(this.alwaysReverts.selector)}); + return res; + } + + function alwaysReverts() external pure { + revert DeliberateFailure(); + } + + function getURI() external pure override(IPayload) returns (string memory) { + return "RegisterThenFailPayload"; + } +} + +contract V6UpgradeAtomicityTest is TestBase { + UncompressedProposalWrapper internal upw = new UncompressedProposalWrapper(); + + TestERC20 internal token; + Registry internal registry; + Governance internal governance; + GovernanceProposer internal governanceProposer; + Rollup internal rollup; + IGSE internal gse; + + address internal constant EMPEROR = address(uint160(bytes20("EMPEROR"))); + uint256 internal constant VALIDATOR_COUNT = 4; + uint256 internal constant REWARD_PER_INSERTION = 1000e18; + uint256 internal constant REWARDER_BALANCE = 390_000e18; + uint64 internal constant POP_GAS_LIMIT = 300_000; + + function setUp() external { + StakingQueueConfig memory stakingQueueConfig = TestConstants.getStakingQueueConfig(); + stakingQueueConfig.normalFlushSizeMin = VALIDATOR_COUNT * 2; + + vm.warp(100_000); + RollupBuilder builder = new RollupBuilder(address(this)).setGovProposerN(7).setGovProposerM(10) + .setStakingQueueConfig(stakingQueueConfig).setTargetCommitteeSize(0); + builder.deploy(); + + rollup = builder.getConfig().rollup; + registry = builder.getConfig().registry; + token = builder.getConfig().testERC20; + governance = builder.getConfig().governance; + governanceProposer = GovernanceProposer(governance.governanceProposer()); + gse = IGSE(address(rollup.getGSE())); + + CheatDepositArgs[] memory initialValidators = new CheatDepositArgs[](VALIDATOR_COUNT); + for (uint256 i = 1; i <= VALIDATOR_COUNT; i++) { + address validator = vm.addr(uint256(keccak256(abi.encode("validator", i)))); + initialValidators[i - 1] = CheatDepositArgs({ + attester: validator, + withdrawer: validator, + publicKeyInG1: BN254Lib.g1Zero(), + publicKeyInG2: BN254Lib.g2Zero(), + proofOfPossession: BN254Lib.g1Zero() + }); + } + MultiAdder multiAdder = new MultiAdder(address(rollup), address(this)); + uint256 activationThreshold = rollup.getActivationThreshold(); + vm.prank(token.owner()); + token.mint(address(multiAdder), activationThreshold * VALIDATOR_COUNT); + multiAdder.addValidators(initialValidators); + } + + /** + * @dev THE QUESTION THIS FILE EXISTS FOR: when the predecessor guard fails, is anything left + * behind? Every later action's effect is asserted absent, not just the first one. + */ + function test_StalePayloadExecutesNothingAtAll() public { + FlushRewarder oldRewarder = _deployRewarder(); + V6UpgradePayload payload = _deployV6Payload(oldRewarder); + address newRollup = address(payload.ROLLUP()); + + registry.transferOwnership(address(governance)); + uint256 proposalId = _proposeAndQueue(IPayload(address(payload))); + + // Another registration lands while this proposal sat in its delay -- the abandoned-payload + // scenario. Pranked as governance, the registry's owner, rather than run through a second + // proposal round: how canonical moved is irrelevant to the guard, only that it moved. It has + // to happen AFTER the proposal is queued, because signalling resolves proposers off whatever + // rollup is canonical at the time. + StandInRollup other = new StandInRollup(gse); + vm.prank(address(governance)); + registry.addRollup(IHaveVersion(address(other))); + + // Snapshot everything the payload would touch if it ran at all. + address canonicalBefore = address(registry.getCanonicalRollup()); + address gseLatestBefore = gse.getLatestRollup(); + uint256 versionsBefore = registry.numberOfVersions(); + uint256 oldRewarderBefore = token.balanceOf(address(oldRewarder)); + uint256 newRewarderBefore = token.balanceOf(address(payload.NEW_FLUSH_REWARDER())); + uint64 popGasLimitBefore = GSE(address(gse)).proofOfPossessionGasLimit(); + + // The failing action is the payload's own self-targeted guard, so that is the target + // `Governance` names when it refuses the whole execution. + vm.expectRevert(abi.encodeWithSelector(Errors.Governance__CallFailed.selector, address(payload))); + governance.execute(proposalId); + + // Action 1 (Registry.addRollup) did not run. + assertEq(address(registry.getCanonicalRollup()), canonicalBefore, "canonical rollup moved"); + assertEq(registry.numberOfVersions(), versionsBefore, "a version was registered"); + assertNotEq(address(registry.getCanonicalRollup()), newRollup, "v6 became canonical"); + // Action 2 (GSE.addRollup) did not run. + assertEq(gse.getLatestRollup(), gseLatestBefore, "GSE latest moved"); + // Action 3 (FlushRewarder.recover) did not run. + assertEq(token.balanceOf(address(oldRewarder)), oldRewarderBefore, "old rewarder was drained"); + assertEq(token.balanceOf(address(payload.NEW_FLUSH_REWARDER())), newRewarderBefore, "new rewarder was funded"); + // Action 4 (GSE.setProofOfPossessionGasLimit) did not run. + assertEq(GSE(address(gse)).proofOfPossessionGasLimit(), popGasLimitBefore, "GSE proof-of-possession cap moved"); + + // And the proposal was not consumed: the Executed flag is rolled back with everything else, so + // a rejected attempt leaves it executable until it expires on its own. + assertTrue(governance.getProposalState(proposalId) == ProposalState.Executable, "proposal was consumed"); + } + + /** + * @dev The general property, independent of where the guard sits: a failure in a LATER action + * rolls back an earlier one that already succeeded. `Governance.execute` requires success + * per action inside one transaction, so a partial execution is not representable. + */ + function test_AFailureAfterAddRollupRollsBackTheRegistration() public { + StandInRollup target = new StandInRollup(gse); + RegisterThenFailPayload payload = new RegisterThenFailPayload(IRegistry(address(registry)), address(target)); + registry.transferOwnership(address(governance)); + + uint256 proposalId = _proposeAndQueue(IPayload(address(payload))); + + address canonicalBefore = address(registry.getCanonicalRollup()); + uint256 versionsBefore = registry.numberOfVersions(); + + vm.expectRevert(abi.encodeWithSelector(Errors.Governance__CallFailed.selector, address(payload))); + governance.execute(proposalId); + + assertEq(address(registry.getCanonicalRollup()), canonicalBefore, "addRollup survived a later failure"); + assertEq(registry.numberOfVersions(), versionsBefore, "a version survived a later failure"); + assertNotEq(address(registry.getCanonicalRollup()), address(target), "target became canonical"); + } + + /// @dev The positive counterpart to the tests above: the payload executes in full through real + /// governance, and the GSE leaves it with the raised proof-of-possession gas cap. + function test_ExecutionRaisesTheProofOfPossessionGasLimit() public { + FlushRewarder oldRewarder = _deployRewarder(); + V6UpgradePayload payload = _deployV6Payload(oldRewarder); + address newRollup = address(payload.ROLLUP()); + assertEq(GSE(address(gse)).proofOfPossessionGasLimit(), 250_000, "unexpected starting cap"); + + registry.transferOwnership(address(governance)); + uint256 proposalId = _proposeAndQueue(IPayload(address(payload))); + governance.execute(proposalId); + + assertTrue(governance.getProposalState(proposalId) == ProposalState.Executed, "proposal not executed"); + assertEq(address(registry.getCanonicalRollup()), newRollup, "v6 is not canonical"); + assertEq(gse.getLatestRollup(), newRollup, "GSE latest is not v6"); + assertEq(GSE(address(gse)).proofOfPossessionGasLimit(), POP_GAS_LIMIT, "proof-of-possession gas cap not raised"); + } + + // ----------------------------------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------------------------------- + + function _deployRewarder() internal returns (FlushRewarder) { + FlushRewarder rewarder = + new FlushRewarder(address(governance), IInstance(address(rollup)), IERC20(address(token)), REWARD_PER_INSERTION); + vm.prank(token.owner()); + token.mint(address(rewarder), REWARDER_BALANCE); + return rewarder; + } + + function _deployV6Payload(FlushRewarder _old) internal returns (V6UpgradePayload) { + StandInRollup newRollup = new StandInRollup(gse); + // Window off: this file is about atomicity, and the clock would only add a second reason to + // revert. The window itself is covered in test/periphery/V6UpgradePayload.t.sol. + IEscapeHatch hatch = IEscapeHatch(address(new StubEscapeHatch(address(newRollup)))); + return new V6UpgradePayload( + IRegistry(address(registry)), IInstance(address(newRollup)), hatch, _old, false, 0, false, 0, 0, POP_GAS_LIMIT + ); + } + + /// @dev Signal → submit → vote → warp, leaving the proposal Executable. + function _proposeAndQueue(IPayload _payload) internal returns (uint256) { + vm.warp(Timestamp.unwrap(rollup.getTimestampForSlot(Slot.wrap(1)))); + for (uint256 i = 0; i < 10; i++) { + vm.prank(rollup.getCurrentProposer()); + governanceProposer.signal(_payload); + vm.warp(Timestamp.unwrap(rollup.getTimestampForSlot(rollup.getCurrentSlot() + Slot.wrap(1)))); + } + governanceProposer.submitRoundWinner(0); + + Proposal memory proposal = governance.getProposal(0); + assertEq(address(GSEPayload(address(proposal.payload)).getOriginalPayload()), address(_payload)); + + vm.prank(token.owner()); + token.mint(EMPEROR, 10_000 ether); + vm.startPrank(EMPEROR); + token.approve(address(governance), 10_000 ether); + governance.deposit(EMPEROR, 10_000 ether); + vm.stopPrank(); + + vm.warp(Timestamp.unwrap(upw.pendingThrough(proposal)) + 1); + vm.prank(EMPEROR); + governance.vote(0, 10_000 ether, true); + + vm.warp(Timestamp.unwrap(upw.activeThrough(proposal)) + 1); + vm.warp(Timestamp.unwrap(upw.queuedThrough(proposal)) + 1); + assertTrue(governance.getProposalState(0) == ProposalState.Executable, "proposal is not executable"); + return 0; + } +} diff --git a/l1-contracts/test/periphery/V6UpgradePayload.t.sol b/l1-contracts/test/periphery/V6UpgradePayload.t.sol new file mode 100644 index 00000000000..e0a7378c783 --- /dev/null +++ b/l1-contracts/test/periphery/V6UpgradePayload.t.sol @@ -0,0 +1,547 @@ +// SPDX-License-Identifier: UNLICENSED +// solhint-disable +pragma solidity >=0.8.27; + +import {IInstance} from "@aztec/core/interfaces/IInstance.sol"; +import {GSE, IGSECore} from "@aztec/governance/GSE.sol"; +import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; +import {IHaveVersion, IRegistry} from "@aztec/governance/interfaces/IRegistry.sol"; +import {Registry} from "@aztec/governance/Registry.sol"; +import {Errors} from "@aztec/governance/libraries/Errors.sol"; +import {IEscapeHatch} from "@aztec/core/interfaces/IEscapeHatch.sol"; +import {IValidatorSelectionCore} from "@aztec/core/interfaces/IValidatorSelection.sol"; +import {FlushRewarder} from "@aztec/periphery/FlushRewarder.sol"; +import {V6UpgradePayload} from "@aztec/periphery/V6UpgradePayload.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; +import {TestBase} from "@test/base/Base.sol"; + +/** + * @dev Enough of a rollup for the payload: a version the registry can key on, and a GSE address the + * action list can target. Deliberately NOT a real Rollup -- these tests are about the payload's + * action list and its preconditions, and a real rollup would only add deploy cost and noise. + */ +contract StubRollup { + address internal immutable GSE_ADDR; + + constructor(address _gse) { + GSE_ADDR = _gse; + } + + function getVersion() external view returns (uint256) { + return uint256(keccak256(abi.encodePacked(bytes("aztec_rollup"), block.chainid, address(this)))); + } + + function getGSE() external view returns (GSE) { + return GSE(GSE_ADDR); + } +} + +/// @dev The payload reads only the proof-of-possession gas cap off the GSE, and only when it raises it. +contract StubGSE { + uint64 public proofOfPossessionGasLimit = 250_000; +} + +/// @dev The payload only reads `getRollup()` off the hatch, so a real EscapeHatch -- whose +/// constructor interrogates the rollup -- would add nothing but cost here. +contract StubEscapeHatch { + address internal immutable ROLLUP_; + + constructor(address _rollup) { + ROLLUP_ = _rollup; + } + + function getRollup() external view returns (address) { + return ROLLUP_; + } +} + +contract V6UpgradePayloadTest is TestBase { + TestERC20 internal token; + Registry internal registry; + + IInstance internal outgoing; // canonical when the payload is deployed + IInstance internal incoming; // the rollup the payload makes canonical + address internal gseAddr; + + uint256 internal constant REWARD_PER_INSERTION = 1000e18; + uint64 internal constant POP_GAS_LIMIT = 300_000; + + /// @dev Day index of 2026-11-01, the month the v6 upgrade is prepared in. + uint256 internal constant FIRST_DAY = 20_758; + + /// @dev The hatch the payload installs, bound to the incoming rollup. + IEscapeHatch internal hatch; + + /// @dev `isWithinExecutionWindow` is pure; this instance is only a host to call it on. + V6UpgradePayload internal payloadForWindow; + + function setUp() public { + token = new TestERC20("test", "TEST", address(this)); + // Owner is the test contract, standing in for governance: it lets the test register rollups + // directly, which is what moves the canonical pointer the guard reads. + registry = new Registry(address(this), IERC20(address(token))); + gseAddr = address(new StubGSE()); + + outgoing = IInstance(address(new StubRollup(gseAddr))); + incoming = IInstance(address(new StubRollup(gseAddr))); + registry.addRollup(_haveVersion(outgoing)); + + hatch = IEscapeHatch(address(new StubEscapeHatch(address(incoming)))); + payloadForWindow = _deploy({_window: true, _withRewarder: false}); + } + + // --------------------------------------------------------------------------------------------- + // Predecessor guard + // --------------------------------------------------------------------------------------------- + + function test_PredecessorIsTheCanonicalRollupAtDeploy() public { + V6UpgradePayload payload = _deploy({_window: false, _withRewarder: false}); + assertEq(payload.PREDECESSOR(), address(outgoing), "predecessor is not the outgoing rollup"); + assertEq(payload.PREDECESSOR(), address(registry.getCanonicalRollup()), "predecessor is not canonical"); + } + + function test_GuardPassesWhilePredecessorIsStillCanonical() public { + V6UpgradePayload payload = _deploy({_window: false, _withRewarder: false}); + // Nothing has moved since deployment, so this must not revert. Called rather than asserted: + // the guard's whole contract is "reverts, or does nothing". + payload.assertPredecessorIsCanonical(); + } + + function test_GuardRevertsOnceAnotherRollupIsRegistered() public { + V6UpgradePayload payload = _deploy({_window: false, _withRewarder: false}); + + // Someone else's registration lands first -- the abandoned-payload scenario. + IInstance other = IInstance(address(new StubRollup(gseAddr))); + registry.addRollup(_haveVersion(other)); + + vm.expectRevert( + abi.encodeWithSelector( + V6UpgradePayload.V6UpgradePayload__PredecessorNotCanonical.selector, address(outgoing), address(other) + ) + ); + payload.assertPredecessorIsCanonical(); + } + + /// @dev The whole point of the guard being an action rather than a revert inside `getActions`: + /// a payload that can no longer execute must still be able to say what it would have done. + function test_GetActionsStaysReadableWhenStale() public { + V6UpgradePayload payload = _deploy({_window: true, _withRewarder: true}); + + IInstance other = IInstance(address(new StubRollup(gseAddr))); + registry.addRollup(_haveVersion(other)); + + IPayload.Action[] memory actions = payload.getActions(); + assertEq(actions.length, 6, "stale payload no longer describes itself"); + assertEq(bytes4(actions[0].data), payload.assertPredecessorIsCanonical.selector, "guard action lost"); + } + + function test_GuardActionIsFirstInEveryConfiguration() public { + bool[2] memory windows = [true, false]; + bool[2] memory rewarders = [true, false]; + + for (uint256 i = 0; i < windows.length; i++) { + for (uint256 j = 0; j < rewarders.length; j++) { + V6UpgradePayload payload = _deploy({_window: windows[i], _withRewarder: rewarders[j]}); + IPayload.Action[] memory actions = payload.getActions(); + assertEq(actions[0].target, address(payload), "guard is not self-targeted"); + assertEq( + bytes4(actions[0].data), payload.assertPredecessorIsCanonical.selector, "guard is not the first action" + ); + } + } + } + + // --------------------------------------------------------------------------------------------- + // Action list: shape, order and arguments + // --------------------------------------------------------------------------------------------- + + function test_ActionListShapeWindowAndRewarder() public { + V6UpgradePayload payload = _deploy({_window: true, _withRewarder: true}); + IPayload.Action[] memory a = payload.getActions(); + assertEq(a.length, 6, "wrong action count"); + + _assertGuard(a[0], payload); + _assertWindow(a[1], payload); + _assertSetEscapeHatch(a[2], payload); + _assertAddRollup(a[3]); + _assertGseAddRollup(a[4]); + _assertRecover(a[5], payload); + } + + function test_ActionListShapeWindowNoRewarder() public { + V6UpgradePayload payload = _deploy({_window: true, _withRewarder: false}); + IPayload.Action[] memory a = payload.getActions(); + assertEq(a.length, 5, "wrong action count"); + + _assertGuard(a[0], payload); + _assertWindow(a[1], payload); + _assertSetEscapeHatch(a[2], payload); + _assertAddRollup(a[3]); + _assertGseAddRollup(a[4]); + } + + function test_ActionListShapeNoWindowWithRewarder() public { + V6UpgradePayload payload = _deploy({_window: false, _withRewarder: true}); + IPayload.Action[] memory a = payload.getActions(); + assertEq(a.length, 5, "wrong action count"); + + _assertGuard(a[0], payload); + _assertSetEscapeHatch(a[1], payload); + _assertAddRollup(a[2]); + _assertGseAddRollup(a[3]); + _assertRecover(a[4], payload); + } + + function test_ActionListShapeNoWindowNoRewarder() public { + V6UpgradePayload payload = _deploy({_window: false, _withRewarder: false}); + IPayload.Action[] memory a = payload.getActions(); + assertEq(a.length, 4, "wrong action count"); + + _assertGuard(a[0], payload); + _assertSetEscapeHatch(a[1], payload); + _assertAddRollup(a[2]); + _assertGseAddRollup(a[3]); + } + + /// @dev `rewardsAvailable()` is read when the action list is built, not when the payload is + /// deployed, so a balance that moves during the governance delay moves the recovered amount. + function test_RecoverAmountIsReadAtCallTime() public { + V6UpgradePayload payload = _deploy({_window: false, _withRewarder: true}); + FlushRewarder old = payload.OLD_FLUSH_REWARDER(); + + IPayload.Action[] memory before = payload.getActions(); + (,, uint256 amountBefore) = _decodeRecover(before[4].data); + + token.mint(address(old), 500e18); + + IPayload.Action[] memory later = payload.getActions(); + (,, uint256 amountAfter) = _decodeRecover(later[4].data); + + assertEq(amountAfter, amountBefore + 500e18, "recover amount did not follow the balance"); + assertEq(amountAfter, old.rewardsAvailable(), "recover amount is not rewardsAvailable at call time"); + } + + function test_ActionListShapeWithPopGasLimitWindowAndRewarder() public { + V6UpgradePayload payload = _deployWithPopGasLimit({_window: true, _withRewarder: true, _popGasLimit: POP_GAS_LIMIT}); + IPayload.Action[] memory a = payload.getActions(); + assertEq(a.length, 7, "wrong action count"); + + _assertGuard(a[0], payload); + _assertWindow(a[1], payload); + _assertSetEscapeHatch(a[2], payload); + _assertAddRollup(a[3]); + _assertGseAddRollup(a[4]); + _assertRecover(a[5], payload); + _assertSetPopGasLimit(a[6]); + } + + function test_ActionListShapeWithPopGasLimitOnly() public { + V6UpgradePayload payload = + _deployWithPopGasLimit({_window: false, _withRewarder: false, _popGasLimit: POP_GAS_LIMIT}); + IPayload.Action[] memory a = payload.getActions(); + assertEq(a.length, 5, "wrong action count"); + + _assertGuard(a[0], payload); + _assertSetEscapeHatch(a[1], payload); + _assertAddRollup(a[2]); + _assertGseAddRollup(a[3]); + _assertSetPopGasLimit(a[4]); + } + + function test_ZeroPopGasLimitAddsNoAction() public { + bool[2] memory flags = [true, false]; + + for (uint256 i = 0; i < flags.length; i++) { + for (uint256 j = 0; j < flags.length; j++) { + V6UpgradePayload payload = _deploy({_window: flags[i], _withRewarder: flags[j]}); + assertEq(payload.PROOF_OF_POSSESSION_GAS_LIMIT(), 0, "cap should be unset"); + + IPayload.Action[] memory actions = payload.getActions(); + assertEq(actions.length, 4 + (flags[i] ? 1 : 0) + (flags[j] ? 1 : 0), "wrong action count"); + for (uint256 k = 0; k < actions.length; k++) { + assertTrue( + bytes4(actions[k].data) != IGSECore.setProofOfPossessionGasLimit.selector, "cap action should be absent" + ); + } + } + } + } + + // --------------------------------------------------------------------------------------------- + // Constructor + // --------------------------------------------------------------------------------------------- + + /// @dev Zero means "no action"; anything else must raise the GSE's current cap of 250k, since a + /// lower cap would start rejecting deposits that verify today. + function testFuzz_ConstructorAcceptsOnlyARaiseOfThePopGasLimit(uint64 _limit) public { + uint64 current = StubGSE(gseAddr).proofOfPossessionGasLimit(); + if (_limit != 0 && _limit <= current) { + vm.expectRevert( + abi.encodeWithSelector( + V6UpgradePayload.V6UpgradePayload__ProofOfPossessionGasLimitNotRaised.selector, current, _limit + ) + ); + } + V6UpgradePayload payload = _deployWithPopGasLimit({_window: false, _withRewarder: false, _popGasLimit: _limit}); + if (_limit == 0 || _limit > current) { + assertEq(payload.PROOF_OF_POSSESSION_GAS_LIMIT(), _limit, "cap not recorded"); + } + } + + /// @dev Like the earmark, the GSE is only consulted when the action is used. + function test_ZeroPopGasLimitDoesNotReadTheGSE() public { + StubRollup bare = new StubRollup(address(0xdead)); + IEscapeHatch bareHatch = IEscapeHatch(address(new StubEscapeHatch(address(bare)))); + V6UpgradePayload payload = new V6UpgradePayload( + IRegistry(address(registry)), + IInstance(address(bare)), + bareHatch, + FlushRewarder(address(0)), + false, + 0, + false, + 0, + 0, + 0 + ); + assertEq(payload.PROOF_OF_POSSESSION_GAS_LIMIT(), 0, "cap should be unset"); + } + + function test_ConstructorRevertsWhenRewarderServesAnotherRollup() public { + // A rewarder bound to the INCOMING rollup, not the outgoing one it claims to replace. + FlushRewarder foreign = new FlushRewarder(address(this), incoming, IERC20(address(token)), REWARD_PER_INSERTION); + + vm.expectRevert( + abi.encodeWithSelector( + V6UpgradePayload.V6UpgradePayload__FlushRewarderRollupMismatch.selector, address(incoming), address(outgoing) + ) + ); + new V6UpgradePayload(IRegistry(address(registry)), incoming, hatch, foreign, false, 0, false, 0, 0, 0); + } + + function test_ConstructorRevertsAgainstAnEmptyRegistry() public { + Registry empty = new Registry(address(this), IERC20(address(token))); + vm.expectRevert(abi.encodeWithSelector(Errors.Registry__NoRollupsRegistered.selector)); + new V6UpgradePayload( + IRegistry(address(empty)), incoming, hatch, FlushRewarder(address(0)), false, 0, false, 0, 0, 0 + ); + } + + function test_ConstructorRevertsWhenTheHatchServesAnotherRollup() public { + // `setEscapeHatch` is one-shot, so a hatch bound elsewhere would burn the only chance to + // install one on this rollup. + IEscapeHatch foreign = IEscapeHatch(address(new StubEscapeHatch(address(outgoing)))); + vm.expectRevert( + abi.encodeWithSelector( + V6UpgradePayload.V6UpgradePayload__EscapeHatchRollupMismatch.selector, address(outgoing), address(incoming) + ) + ); + new V6UpgradePayload( + IRegistry(address(registry)), incoming, foreign, FlushRewarder(address(0)), false, 0, false, 0, 0, 0 + ); + } + + function test_NewRewarderIsZeroWhenThereIsNoOldOne() public { + V6UpgradePayload payload = _deploy({_window: false, _withRewarder: false}); + assertEq(address(payload.NEW_FLUSH_REWARDER()), address(0), "deployed a rewarder with nothing to migrate"); + } + + function test_NewRewarderMirrorsTheOldOneAndFollowsTheNewRollup() public { + V6UpgradePayload payload = _deploy({_window: false, _withRewarder: true}); + FlushRewarder old = payload.OLD_FLUSH_REWARDER(); + FlushRewarder fresh = payload.NEW_FLUSH_REWARDER(); + + assertEq(address(fresh.REWARD_ASSET()), address(old.REWARD_ASSET()), "reward asset not mirrored"); + assertEq(fresh.rewardPerInsertion(), old.rewardPerInsertion(), "reward rate not mirrored"); + assertEq(address(fresh.ROLLUP()), address(incoming), "replacement is not bound to the new rollup"); + assertEq(fresh.owner(), registry.getGovernance(), "replacement is not owned by governance"); + } + + // --------------------------------------------------------------------------------------------- + // Execution window + // + // The date math is hand-rolled -- no library, no tabulated DST table -- so it is checked against + // the real IANA Europe/London calendar rather than against a reimplementation of itself. Every + // vector below was generated from the tz database; a reimplementation in Solidity would only + // reproduce whatever bug the contract has. + // --------------------------------------------------------------------------------------------- + + function test_WindowMatchesTheRealLondonCalendar() public view { + // Boundaries in GMT: 08:00 opens, 17:00 closes. + _vec(1_799_654_340, false); // 2027-01-11 07:59 GMT Mon + _vec(1_799_654_400, true); // 2027-01-11 08:00 GMT Mon + _vec(1_800_032_340, true); // 2027-01-15 16:59 GMT Fri + _vec(1_800_032_400, false); // 2027-01-15 17:00 GMT Fri + + // The same boundaries in BST, which are an hour earlier in UTC. + _vec(1_815_375_540, false); // 2027-07-12 07:59 BST Mon + _vec(1_815_375_600, true); // 2027-07-12 08:00 BST Mon + _vec(1_815_753_540, true); // 2027-07-16 16:59 BST Fri + _vec(1_815_753_600, false); // 2027-07-16 17:00 BST Fri + + // Weekends, whatever the hour. + _vec(1_800_100_800, false); // 2027-01-16 12:00 GMT Sat + _vec(1_800_187_200, false); // 2027-01-17 12:00 GMT Sun + + // Leap days, including one that lands on a weekend. + _vec(1_835_420_400, false); // 2028-02-29 07:00 GMT Tue + _vec(1_835_438_400, true); // 2028-02-29 12:00 GMT Tue + _vec(1_961_668_800, false); // 2032-02-29 12:00 GMT Sun + _vec(2_087_899_200, true); // 2036-02-29 12:00 GMT Fri + + // `_lastSundayOfMonth` at both extremes: the Monday after BST starts in a year where March's + // last Sunday is the 25th, and in one where it is the 31st. The transition instant itself is + // always a Sunday, so it is closed by the weekday rule either way -- the first observable + // consequence of getting the offset wrong is on the Monday that follows. + _vec(1_869_202_740, false); // 2029-03-26 07:59 BST Mon (BST began 25 Mar) + _vec(1_869_202_800, true); // 2029-03-26 08:00 BST Mon + _vec(1_901_257_140, false); // 2030-04-01 07:59 BST Mon (BST began 31 Mar) + _vec(1_901_257_200, true); // 2030-04-01 08:00 BST Mon + _vec(1_887_955_140, false); // 2029-10-29 07:59 GMT Mon (BST ended 28 Oct) + _vec(1_887_955_200, true); // 2029-10-29 08:00 GMT Mon + + // The cliff date itself, for the upgrade this payload exists for. + _vec(1_794_571_200, true); // 2026-11-13 12:00 GMT Fri + _vec(1_794_589_200, false); // 2026-11-13 17:00 GMT Fri + _vec(1_794_657_600, false); // 2026-11-14 12:00 GMT Sat + } + + /// @dev A property the calendar must satisfy for every day in the payload's plausible life: + /// nine open hours on a weekday, none at a weekend. Verified independently against the tz + /// database across the same range, including every DST transition in it. + /// + /// It constrains the SHAPE of the window, not its alignment: dropping the BST offset + /// entirely still leaves nine open hours a day, just an hour out, and this test still + /// passes. That mutation is caught by the vectors above, which is why both exist. + function testFuzz_NineOpenHoursOnWeekdaysNoneAtWeekends(uint256 _day) public view { + uint256 day = bound(_day, FIRST_DAY, FIRST_DAY + 6 * 365); + + uint256 open = 0; + for (uint256 h = 0; h < 24; h++) { + if (payloadForWindow.isWithinExecutionWindow(day * 1 days + h * 1 hours)) { + open++; + } + } + + uint256 weekday = (day + 4) % 7; + bool isWeekend = weekday == 0 || weekday == 6; + assertEq(open, isWeekend ? 0 : 9, "wrong number of open hours in the day"); + } + + function test_AssertWithinExecutionWindowFollowsTheWindow() public { + vm.warp(1_799_654_340); // Mon 07:59 GMT, one minute early + vm.expectRevert( + abi.encodeWithSelector(V6UpgradePayload.V6UpgradePayload__OutsideExecutionWindow.selector, block.timestamp) + ); + payloadForWindow.assertWithinExecutionWindow(); + + vm.warp(1_799_654_400); // Mon 08:00 GMT + payloadForWindow.assertWithinExecutionWindow(); + } + + /// @dev The window is enforced only where the deploy script asks for it, but the predicate is + /// always readable -- so a chain without the window still answers the same question. + function test_WindowNotEnforcedStillAnswersTheQuestion() public { + V6UpgradePayload noWindow = _deploy({_window: false, _withRewarder: false}); + assertFalse(noWindow.ENFORCE_EXECUTION_WINDOW(), "window should be off"); + assertTrue(noWindow.isWithinExecutionWindow(1_799_654_400), "predicate should still work"); + + IPayload.Action[] memory actions = noWindow.getActions(); + for (uint256 i = 0; i < actions.length; i++) { + assertTrue( + bytes4(actions[i].data) != noWindow.assertWithinExecutionWindow.selector, "window action should be absent" + ); + } + } + + function _vec(uint256 _timestamp, bool _expected) internal view { + assertEq(payloadForWindow.isWithinExecutionWindow(_timestamp), _expected, vm.toString(_timestamp)); + } + + // --------------------------------------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------------------------------------- + + function _deploy(bool _window, bool _withRewarder) internal returns (V6UpgradePayload) { + return _deployWithPopGasLimit({_window: _window, _withRewarder: _withRewarder, _popGasLimit: 0}); + } + + function _deployWithPopGasLimit(bool _window, bool _withRewarder, uint64 _popGasLimit) + internal + returns (V6UpgradePayload) + { + FlushRewarder old = _withRewarder + ? new FlushRewarder(address(this), outgoing, IERC20(address(token)), REWARD_PER_INSERTION) + : FlushRewarder(address(0)); + if (_withRewarder) { + token.mint(address(old), 1000e18); + } + return + new V6UpgradePayload(IRegistry(address(registry)), incoming, hatch, old, _window, 0, false, 0, 0, _popGasLimit); + } + + function _haveVersion(IInstance _rollup) internal pure returns (IHaveVersion) { + return IHaveVersion(address(_rollup)); + } + + function _assertGuard(IPayload.Action memory _a, V6UpgradePayload _payload) internal view { + assertEq(_a.target, address(_payload), "guard target"); + assertEq(bytes4(_a.data), _payload.assertPredecessorIsCanonical.selector, "guard selector"); + assertEq(_a.data.length, 4, "guard takes no arguments"); + } + + function _assertWindow(IPayload.Action memory _a, V6UpgradePayload _payload) internal view { + assertEq(_a.target, address(_payload), "window target"); + assertEq(bytes4(_a.data), _payload.assertWithinExecutionWindow.selector, "window selector"); + } + + function _assertSetEscapeHatch(IPayload.Action memory _a, V6UpgradePayload _payload) internal view { + assertEq(_a.target, address(incoming), "setEscapeHatch must target the NEW rollup"); + assertEq(bytes4(_a.data), IValidatorSelectionCore.setEscapeHatch.selector, "setEscapeHatch selector"); + assertEq(abi.decode(_slice4(_a.data), (address)), _payload.ESCAPE_HATCH(), "setEscapeHatch argument"); + } + + function _assertAddRollup(IPayload.Action memory _a) internal view { + assertEq(_a.target, address(registry), "addRollup target"); + assertEq(bytes4(_a.data), IRegistry.addRollup.selector, "addRollup selector"); + assertEq(abi.decode(_slice4(_a.data), (address)), address(incoming), "addRollup argument"); + } + + function _assertGseAddRollup(IPayload.Action memory _a) internal view { + assertEq(_a.target, gseAddr, "gse addRollup target"); + assertEq(bytes4(_a.data), IGSECore.addRollup.selector, "gse addRollup selector"); + assertEq(abi.decode(_slice4(_a.data), (address)), address(incoming), "gse addRollup argument"); + } + + function _assertRecover(IPayload.Action memory _a, V6UpgradePayload _payload) internal view { + assertEq(_a.target, address(_payload.OLD_FLUSH_REWARDER()), "recover target"); + assertEq(bytes4(_a.data), FlushRewarder.recover.selector, "recover selector"); + (address asset, address to, uint256 amount) = _decodeRecover(_a.data); + assertEq(asset, address(_payload.NEW_FLUSH_REWARDER().REWARD_ASSET()), "recover asset"); + assertEq(to, address(_payload.NEW_FLUSH_REWARDER()), "recover recipient"); + assertEq(amount, _payload.OLD_FLUSH_REWARDER().rewardsAvailable(), "recover amount"); + } + + function _assertSetPopGasLimit(IPayload.Action memory _a) internal view { + assertEq(_a.target, gseAddr, "setProofOfPossessionGasLimit must target the GSE"); + assertEq( + _a.data, + abi.encodeWithSelector(IGSECore.setProofOfPossessionGasLimit.selector, POP_GAS_LIMIT), + "setProofOfPossessionGasLimit calldata" + ); + } + + function _decodeRecover(bytes memory _data) internal pure returns (address, address, uint256) { + return abi.decode(_slice4(_data), (address, address, uint256)); + } + + /// @dev Everything after the 4-byte selector, so arguments can be decoded rather than eyeballed. + function _slice4(bytes memory _data) internal pure returns (bytes memory) { + bytes memory out = new bytes(_data.length - 4); + for (uint256 i = 0; i < out.length; i++) { + out[i] = _data[i + 4]; + } + return out; + } +} diff --git a/l1-contracts/test/script/DeployRollupForUpgradeV6.t.sol b/l1-contracts/test/script/DeployRollupForUpgradeV6.t.sol new file mode 100644 index 00000000000..4019134ad98 --- /dev/null +++ b/l1-contracts/test/script/DeployRollupForUpgradeV6.t.sol @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; +import {stdJson} from "forge-std/StdJson.sol"; + +import {DeployAztecL1Contracts, DeployAztecL1ContractsOutput} from "../../script/deploy/DeployAztecL1Contracts.s.sol"; +import {DeployRollupForUpgradeV6} from "../../script/deploy/DeployRollupForUpgradeV6.s.sol"; +import {IInstance} from "@aztec/core/interfaces/IInstance.sol"; +import {Rollup} from "@aztec/core/Rollup.sol"; +import {IGSECore} from "@aztec/governance/GSE.sol"; +import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; +import {Registry} from "@aztec/governance/Registry.sol"; +import {FlushRewarder} from "@aztec/periphery/FlushRewarder.sol"; +import {Slasher} from "@aztec/core/slashing/Slasher.sol"; +import {V6UpgradePayload} from "@aztec/periphery/V6UpgradePayload.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; + +/** + * @dev The deploy script with the governance simulation stubbed out, and nothing else changed. + * + * Every literal in the table is the real one, the genesis roots included: these tests deploy + * the table as written and let the constructors pass judgement on it. + */ +contract V6ConfigHarness is DeployRollupForUpgradeV6 { + /// @dev The governance simulation needs forked state -- real voters, mainnet timings -- and is + /// the one step of `run()` a local stack cannot satisfy. Skipped here so the config table + /// itself can be exercised; the simulation is what runs on deploy day, against a fork. + function _simulate(address) internal override {} +} + +/// @dev The table with its genesis roots zeroed, so the guard in `run()` is still exercised now +/// that the real table has them filled in. +contract V6ZeroRootsHarness is DeployRollupForUpgradeV6 { + function _config() internal view override returns (Config memory c) { + c = super._config(); + c.vkTreeRoot = bytes32(0); + c.protocolContractsHash = bytes32(0); + c.genesisArchiveRoot = bytes32(0); + } +} + +/** + * @title DeployRollupForUpgradeV6Test + * @notice Deploys the v6 config table against a local stack, on both chains it supports. + * @dev Nothing here checks the VALUES are the ones we want -- that is a review question, and the + * comments in `_config()` carry the v5 comparison for it. What this checks is that the table + * is internally consistent and deployable: that the constructors accept it, and that `verify()` + * reads every value back off the deployed contracts. Those are the failures that would + * otherwise surface on deploy day, against mainnet, with a broadcast in flight. + */ +contract DeployRollupForUpgradeV6Test is Test { + using stdJson for string; + + uint256 internal constant MAINNET_CHAIN_ID = 1; + uint256 internal constant SEPOLIA_CHAIN_ID = 11_155_111; + + /// @dev Where the v5 flush rewarder lives on mainnet, per the config table. + address internal constant MAINNET_OLD_FLUSH_REWARDER = 0x5B98cA4dcE7b59CCf241D12f81d3d2eCF14e410e; + + uint64 internal constant EXPECTED_POP_GAS_LIMIT = 300_000; + + Registry internal registry; + Rollup internal outgoing; + + modifier skipWhenCoverage() { + if (vm.envOr("FORGE_COVERAGE", false)) { + vm.skip(true); + } + _; + } + + function setUp() public skipWhenCoverage { + _loadNetworkDefaults(); + + DeployAztecL1Contracts fullDeploy = new DeployAztecL1Contracts(); + fullDeploy.run(); + + DeployAztecL1ContractsOutput memory out = fullDeploy.output(); + registry = out.registry; + outgoing = out.rollup.rollup; + + vm.setEnv("REGISTRY_ADDRESS", vm.toString(address(registry))); + + // Registry ownership stays with the real Governance. The script never writes to the registry -- + // it reads the outgoing rollup off it and hands registration to the payload -- and `run()` + // finishes by driving that payload through the real governance lifecycle, which needs + // `getGovernance()` to actually be a Governance contract. + } + + function test_MainnetConfigDeploysAndVerifies() public { + vm.chainId(MAINNET_CHAIN_ID); + _placeOutgoingFlushRewarder(); + + V6ConfigHarness harness = new V6ConfigHarness(); + harness.run(); + + Rollup deployed = harness.deployedRollup(); + assertNotEq(address(deployed), address(outgoing), "deployed the outgoing rollup"); + + // `run()` already calls these, but calling them again from the test is what pins that they are + // callable stand-alone -- which is how the runbook tells an operator to re-check a deploy. + harness.verify(address(deployed)); + harness.verifyFlushRewarder(address(deployed), address(harness.deployedPayload())); + + _assertPopGasLimitAction(harness.deployedPayload(), deployed); + } + + function test_SepoliaConfigDeploysAndVerifies() public { + vm.chainId(SEPOLIA_CHAIN_ID); + // No rewarder to place: Sepolia's override sets `oldFlushRewarder` to zero, and the payload + // skips the migration entirely. + + V6ConfigHarness harness = new V6ConfigHarness(); + harness.run(); + + Rollup deployed = harness.deployedRollup(); + harness.verify(address(deployed)); + + V6UpgradePayload payload = harness.deployedPayload(); + assertEq(address(payload.OLD_FLUSH_REWARDER()), address(0), "sepolia should have no old rewarder"); + assertEq(address(payload.NEW_FLUSH_REWARDER()), address(0), "sepolia should deploy no new rewarder"); + assertFalse(payload.ENFORCE_EXECUTION_WINDOW(), "sepolia should not enforce the window"); + _assertPopGasLimitAction(payload, deployed); + } + + /// @dev The Rollup constructor's `_governance` becomes BOTH the Ownable owner and the Slasher's + /// immutable GOVERNANCE, which may execute any slash payload with no vote. Ownership is + /// handed over after construction; the Slasher's copy cannot be. + function test_SlasherGovernanceIsGovernanceNotTheDeployer() public { + vm.chainId(MAINNET_CHAIN_ID); + _placeOutgoingFlushRewarder(); + + V6ConfigHarness harness = new V6ConfigHarness(); + harness.run(); + Rollup deployed = harness.deployedRollup(); + + address slasherGov = Slasher(deployed.getSlasher()).GOVERNANCE(); + emit log_named_address("slasher GOVERNANCE", slasherGov); + emit log_named_address("rollup owner ", deployed.owner()); + emit log_named_address("registry governance", registry.getGovernance()); + emit log_named_address("deployer (this) ", address(harness)); + + assertEq(slasherGov, registry.getGovernance(), "slasher GOVERNANCE is not governance"); + } + + function test_UnsupportedChainReverts() public { + vm.chainId(31_337); + V6ConfigHarness harness = new V6ConfigHarness(); + vm.expectRevert( + abi.encodeWithSelector(DeployRollupForUpgradeV6.DeployRollupForUpgradeV6__UnsupportedChain.selector, 31_337) + ); + harness.run(); + } + + /// @dev The script must refuse to deploy while the genesis roots are unset. The real table now + /// carries them, so the zero case is constructed rather than read off it. + function test_RefusesWhileGenesisRootsAreZero() public { + vm.chainId(MAINNET_CHAIN_ID); + V6ZeroRootsHarness zeroed = new V6ZeroRootsHarness(); + vm.expectRevert(bytes("vkTreeRoot not set")); + zeroed.run(); + } + + // ----------------------------------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------------------------------- + + /// @dev Both chains raise the shared GSE's proof-of-possession gas cap to 300k, as the payload's + /// last action. + function _assertPopGasLimitAction(V6UpgradePayload _payload, Rollup _rollup) internal view { + assertEq(_payload.PROOF_OF_POSSESSION_GAS_LIMIT(), EXPECTED_POP_GAS_LIMIT, "proofOfPossessionGasLimit"); + + IPayload.Action[] memory actions = _payload.getActions(); + IPayload.Action memory last = actions[actions.length - 1]; + assertEq(last.target, address(_rollup.getGSE()), "cap action does not target the GSE"); + assertEq( + last.data, abi.encodeCall(IGSECore.setProofOfPossessionGasLimit, (EXPECTED_POP_GAS_LIMIT)), "cap action calldata" + ); + } + + /** + * @dev Put a flush rewarder bound to the outgoing rollup at the mainnet address the config names. + * Etched rather than deployed: the address is a literal in the table, and the point is to + * exercise the table as written rather than a version of it with the address swapped out. + * Immutables live in the runtime code so ROLLUP and REWARD_ASSET survive the etch; storage + * does not, so `rewardPerInsertion` reads zero, which the payload simply mirrors. + */ + function _placeOutgoingFlushRewarder() internal { + FlushRewarder template = new FlushRewarder( + registry.getGovernance(), IInstance(address(outgoing)), IERC20(address(outgoing.getStakingAsset())), 0 + ); + vm.etch(MAINNET_OLD_FLUSH_REWARDER, address(template).code); + assertEq( + address(FlushRewarder(MAINNET_OLD_FLUSH_REWARDER).ROLLUP()), address(outgoing), "etched rewarder is not bound" + ); + } + + function _loadNetworkDefaults() internal { + string memory json = vm.readFile(string.concat(vm.projectRoot(), "/scripts/network-defaults.json")); + + // The GSE thresholds are the one part of the v6 table that is NOT set by the deploy -- they are + // asserted against the GSE that already exists, because the new rollup inherits it. The local + // stack's defaults are 100e18/50e18, so they are raised to production values here; otherwise + // the test would have to weaken that assertion, which is one of the few in the script that + // catches a rollup pointed at the wrong GSE. + vm.setEnv("AZTEC_ACTIVATION_THRESHOLD", vm.toString(uint256(200_000e18))); + vm.setEnv("AZTEC_EJECTION_THRESHOLD", vm.toString(uint256(100_000e18))); + + vm.setEnv("ETHEREUM_SLOT_DURATION", vm.toString(json.readUint(".ETHEREUM_SLOT_DURATION"))); + vm.setEnv("AZTEC_SLOT_DURATION", vm.toString(json.readUint(".AZTEC_SLOT_DURATION"))); + vm.setEnv("AZTEC_EPOCH_DURATION", vm.toString(json.readUint(".AZTEC_EPOCH_DURATION"))); + vm.setEnv("AZTEC_PROOF_SUBMISSION_EPOCHS", vm.toString(json.readUint(".AZTEC_PROOF_SUBMISSION_EPOCHS"))); + vm.setEnv("AZTEC_TARGET_COMMITTEE_SIZE", vm.toString(json.readUint(".AZTEC_TARGET_COMMITTEE_SIZE"))); + vm.setEnv( + "AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET", vm.toString(json.readUint(".AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET")) + ); + vm.setEnv("AZTEC_LAG_IN_EPOCHS_FOR_RANDAO", vm.toString(json.readUint(".AZTEC_LAG_IN_EPOCHS_FOR_RANDAO"))); + vm.setEnv("AZTEC_LOCAL_EJECTION_THRESHOLD", json.readString(".AZTEC_LOCAL_EJECTION_THRESHOLD")); + vm.setEnv("AZTEC_EXIT_DELAY_SECONDS", vm.toString(json.readUint(".AZTEC_EXIT_DELAY_SECONDS"))); + vm.setEnv( + "AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE", + vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE")) + ); + vm.setEnv( + "AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE")) + ); + vm.setEnv("AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN"))); + vm.setEnv( + "AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT")) + ); + vm.setEnv("AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE"))); + vm.setEnv("AZTEC_MANA_TARGET", vm.toString(json.readUint(".AZTEC_MANA_TARGET"))); + vm.setEnv("AZTEC_PROVING_COST_PER_MANA", vm.toString(json.readUint(".AZTEC_PROVING_COST_PER_MANA"))); + vm.setEnv("AZTEC_INITIAL_ETH_PER_FEE_ASSET", vm.toString(json.readUint(".AZTEC_INITIAL_ETH_PER_FEE_ASSET"))); + vm.setEnv("AZTEC_SEQUENCER_REWARD_CALCULATOR", json.readString(".AZTEC_SEQUENCER_REWARD_CALCULATOR")); + vm.setEnv("AZTEC_SLASHER_ENABLED", vm.toString(json.readBool(".AZTEC_SLASHER_ENABLED"))); + vm.setEnv("AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS", vm.toString(json.readUint(".AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS"))); + vm.setEnv("AZTEC_SLASHING_OFFSET_IN_ROUNDS", vm.toString(json.readUint(".AZTEC_SLASHING_OFFSET_IN_ROUNDS"))); + vm.setEnv("AZTEC_SLASHING_LIFETIME_IN_ROUNDS", vm.toString(json.readUint(".AZTEC_SLASHING_LIFETIME_IN_ROUNDS"))); + vm.setEnv( + "AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS", + vm.toString(json.readUint(".AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS")) + ); + vm.setEnv("AZTEC_SLASHING_DISABLE_DURATION", vm.toString(json.readUint(".AZTEC_SLASHING_DISABLE_DURATION"))); + vm.setEnv("AZTEC_SLASHING_VETOER", json.readString(".AZTEC_SLASHING_VETOER")); + vm.setEnv("AZTEC_SLASH_AMOUNT_SMALL", json.readString(".AZTEC_SLASH_AMOUNT_SMALL")); + vm.setEnv("AZTEC_SLASH_AMOUNT_MEDIUM", json.readString(".AZTEC_SLASH_AMOUNT_MEDIUM")); + vm.setEnv("AZTEC_SLASH_AMOUNT_LARGE", json.readString(".AZTEC_SLASH_AMOUNT_LARGE")); + } +}