From c4075b84cb6c450a7b64a03df9f5bc4f963e3636 Mon Sep 17 00:00:00 2001 From: Amin Sammara Date: Tue, 6 Oct 2026 14:08:45 +0700 Subject: [PATCH 01/12] feat(l1): v6 rollup upgrade deploy script, payload and runbook Deploys the v6 rollup and the governance payload that makes it canonical, plus a simulation harness and an operator runbook. Every configuration value is a literal in `DeployRollupForUpgradeV6._config()`, so reviewing that one file is sufficient to review the deployment: no env-var defaults, no `network-defaults.json` fallbacks. Mainnet is the literal and Sepolia a short override list underneath it, so the only thing to check for Sepolia is that list; a chain matching neither reverts rather than silently deploying mainnet values. `verify(address)` reads every value back off the deployed contracts, so a typo in the table or a drift in how the Rollup consumes it fails loudly rather than at the next governance vote. Deploy order is itself a safety property. The rollup is constructed owned by governance, because the owner argument 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, and `transferOwnership` moves only the Ownable half. The EscapeHatch is therefore built here but installed by the payload. The payload acts on the outgoing rollup before making v6 canonical: it draws the reservation, forwards the earmark, retunes v5's reward split, then registers v6 with the Registry and GSE and carries the entry-queue flush incentive across to a fresh FlushRewarder. The distributor resolves the canonical rollup live, so once v6 is canonical the old rollup can no longer reach the pool and a retune would be settling a stopped chain. Sequencer reward policy is left at `address(0)`. v5 carried per-registry ATP reductions in the rollup; AZIP-28 moved that to a governance-set calculator, and no production calculator exists yet, so v6 launches paying every proposer the default share and governance installs one in a follow-up payload. The GSE proof-of-possession gas cap is raised to 300k, with a mainnet fork test covering it. Known outstanding, documented in the runbook: `initialEthPerFeeAsset` is a point-in-time price that must be refreshed at deploy time, and the three genesis roots were read off a build at d521f0d9 and are stale -- the protocol circuits have moved, so `vkTreeRoot` must be regenerated before any deploy. `run()` rejects a zero root but not a stale one. Co-authored-by: Santiago Palladino --- .../deploy/DeployRollupForUpgradeV6.s.sol | 695 ++++++++++++++++++ .../script/deploy/V6UpgradeSimulation.sol | 285 +++++++ .../script/deploy/V6_UPGRADE_RUNBOOK.md | 381 ++++++++++ .../src/periphery/V6UpgradePayload.md | 109 +++ .../src/periphery/V6UpgradePayload.sol | 441 +++++++++++ .../test/fork/MainnetV6PopGasLimit.t.sol | 100 +++ .../scenario/V6UpgradeAtomicity.t.sol | 288 ++++++++ .../test/periphery/V6UpgradePayload.t.sol | 547 ++++++++++++++ .../script/DeployRollupForUpgradeV6.t.sol | 252 +++++++ 9 files changed, 3098 insertions(+) create mode 100644 l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol create mode 100644 l1-contracts/script/deploy/V6UpgradeSimulation.sol create mode 100644 l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md create mode 100644 l1-contracts/src/periphery/V6UpgradePayload.md create mode 100644 l1-contracts/src/periphery/V6UpgradePayload.sol create mode 100644 l1-contracts/test/fork/MainnetV6PopGasLimit.t.sol create mode 100644 l1-contracts/test/governance/scenario/V6UpgradeAtomicity.t.sol create mode 100644 l1-contracts/test/periphery/V6UpgradePayload.t.sol create mode 100644 l1-contracts/test/script/DeployRollupForUpgradeV6.t.sol diff --git a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol new file mode 100644 index 00000000000..79ede0237e7 --- /dev/null +++ b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol @@ -0,0 +1,695 @@ +// 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 "@generated/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 and is therefore + // a point-in-time market value that must be refreshed before the deploy. + 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-28): 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({ + // STALE: read off a build at aztec-packages d521f0d940d096fbea5b62010d9c9c70f1dc0fd2, which + // was this branch's base before it was rebased. The protocol circuits have changed since, so + // `vkTreeRoot` no longer matches what this tree builds and must be regenerated before any + // deploy. The other two are expected to hold (no compiler bump, no transpiler or protocol + // contract change) but should be confirmed from the same rebuild. `run()` only guards against + // zero, so a stale root 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: 0x2d89003cc2dc62b06f07d83d3635c66c63fc43668369d30e7ee516f908ee10e3, + protocolContractsHash: 0x0030cdae9792549b9edb5b865f4e10e91bb87565f22ab80d405213f7e991b378, + genesisArchiveRoot: 0x2ef904bbd5edc11a43cf48c4270edbf631d14aeaddafe307f8fa959e8113bfb6, + 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) + initialEthPerFeeAsset: 10_000_000, // v5 production: 9_512_195, priced 2026-06-11. TODO: refresh before v6 deploy + 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 + // Deliberately none at genesis. v5 production carried the per-registry reductions for the + // auction and genesis-sale ATP registries in the rollup itself; AZIP-28 moved that policy out + // to a calculator contract, and no production calculator exists yet (the reference + // implementations are test fixtures). v6 therefore launches paying every proposer the default + // sequencer share, and governance installs a calculator in a follow-up payload via + // `setSequencerRewardCalculator`. Until that lands, ATP positions earn the default. + 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; + // Pinned rather than inherited so the mainnet TODO below does not read as outstanding here: + // 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 calculator on Sepolia either. The ATP registries any reduction policy would key on are + // a mainnet arrangement, so Sepolia has nothing to express and the mainnet value of zero + // already applies; this line records that as a decision rather than an omission. + 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/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..7f4bfa3ffa0 --- /dev/null +++ b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md @@ -0,0 +1,381 @@ +# 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. +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 script needs two generated artifacts that are not in git: + +- `src/core/libraries/ConstantsGen.sol` — from `./scripts/remake-constants.sh` +- `generated/HonkVerifier.sol` — copied by `l1-contracts/bootstrap.sh build_verifier` from + `noir-projects/fnd/noir-protocol-circuits/target/keys/rollup_root_verifier.sol`, so + **noir-projects must have been bootstrapped first** + +From the repo root: + +```bash +make l1-contracts # or: cd l1-contracts && ./bootstrap.sh +``` + +This also fetches the pinned `solc-0.8.30` that `foundry.toml` points at. Then confirm: + +```bash +cd l1-contracts && forge build --skip test +``` + +> Do **not** hand-write a stub `generated/HonkVerifier.sol`. A stub compiles and deploys a verifier +> that accepts every proof. + +## 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` | **STALE — both chains** | read off the build at `d521f0d9`, which is no longer this branch's base; the protocol circuits have moved since. Regenerate, see below | +| `protocolContractsHash` | **recheck — both chains** | same build; expected to hold, but confirm from the same rebuild | +| `genesisArchiveRoot` | **recheck — both chains** | same; expected to hold. Must be below the BN254 scalar field modulus | +| `initialEthPerFeeAsset` | **TODO — mainnet** | E12 ETH-per-fee-asset price; a point-in-time market value, refresh at deploy time | +| `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 | + +Sepolia has no outstanding values. On mainnet only `initialEthPerFeeAsset` is an open decision; the +three genesis values are not decisions but build outputs, and are stale because the branch was +rebased, not because nobody has chosen them. + +`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 + +v5 carried per-registry reward reductions for the auction and genesis-sale ATP registries inside +the rollup. AZIP-28 removed that: the rollup now calls a governance-set `ISequencerRewardCalculator` +once per epoch proof, and `sequencerRewardCalculator` is the address it starts with. + +**v6 launches with `address(0)` on both chains, which means every proposer earns the default +sequencer share — ATP positions included.** This is a deliberate choice, not an omission. No +production calculator exists yet; the two reference implementations +(`RegistryReductionCalculator`, `PremiumRewardCalculator`) are test fixtures under +`test/reward-calculators/` and are not built into `src`. Reinstating the reduction is a follow-up +governance payload that calls `setSequencerRewardCalculator`, which needs no redeploy. + +**If that follow-up slips, ATP holders are paid the full default in the interim.** Whoever signs +off on this upgrade should know that and agree to it. + +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 currently in `_config()` were read off a full build of aztec-packages +`d521f0d940d096fbea5b62010d9c9c70f1dc0fd2`, which **was this branch's base and no longer is**: + +| Field | Value (from `d521f0d9`) | After the rebase | +|---|---|---| +| `vkTreeRoot` | `0x2d89003cc2dc62b06f07d83d3635c66c63fc43668369d30e7ee516f908ee10e3` | **moves** | +| `protocolContractsHash` | `0x0030cdae9792549b9edb5b865f4e10e91bb87565f22ab80d405213f7e991b378` | expected to hold | +| `genesisArchiveRoot` | `0x2ef904bbd5edc11a43cf48c4270edbf631d14aeaddafe307f8fa959e8113bfb6` | expected to hold | + +`vkTreeRoot` moves because the protocol circuits changed between `d521f0d9` and this branch's +current base — `private-kernel-lib` (gas meter, tail validator, transient-data reset), `blob`, and +`types/src/hash.nr`. Circuit changes rotate the verification keys and therefore the tree root. + +The other two are expected to hold: the Noir compiler was not bumped, `avm-transpiler` is unchanged, +and nothing under the protocol contracts moved, so no protocol class id rotates. That is an +expectation, not a guarantee — `types/src/hash.nr` is shared, so confirm both from the same rebuild +rather than carrying them forward on this note. + +Regenerate whenever the deploy is cut from a different commit: rebuilding the protocol circuits +moves `vkTreeRoot`, and rebuilding the protocol contracts moves all three. After `./bootstrap.sh`, +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 +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 +REGISTRY_ADDRESS=0x35b22e09Ee0390539439E24f06Da43D83f90e298 \ +forge script script/deploy/DeployRollupForUpgradeV6.s.sol:DeployRollupForUpgradeV6 \ + --rpc-url $RPC --private-key $KEY --broadcast --verify -vvv +``` + +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/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")); + } +} From e1aee3d772d55da17bdccb3b28fc42257f606667 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Wed, 7 Oct 2026 10:46:05 +0000 Subject: [PATCH 02/12] chore(l1): pin v6 verifier, ConstantsGen and genesis roots for DeployRollupForUpgradeV6 Verifier and ConstantsGen copied from the v6 release build at 9f7f5055e989b21a7da3f512e72f6f004ed09909. vkTreeRoot regenerated from the same build; protocolContractsHash and genesisArchiveRoot confirmed unchanged. --- l1-contracts/.gitignore | 1 - .../deploy/DeployRollupForUpgradeV6.s.sol | 4 +- l1-contracts/script/deploy/HonkVerifier.sol | 5723 +++++++++++++++++ .../src/core/libraries/ConstantsGen.sol | 32 + 4 files changed, 5757 insertions(+), 3 deletions(-) create mode 100644 l1-contracts/script/deploy/HonkVerifier.sol create mode 100644 l1-contracts/src/core/libraries/ConstantsGen.sol 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/script/deploy/DeployRollupForUpgradeV6.s.sol b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol index 79ede0237e7..63fe54db8f8 100644 --- a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol +++ b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol @@ -29,7 +29,7 @@ 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 "@generated/HonkVerifier.sol"; +import {HonkVerifier} from "./HonkVerifier.sol"; import {FlushRewarder} from "@aztec/periphery/FlushRewarder.sol"; import {V6UpgradePayload} from "@aztec/periphery/V6UpgradePayload.sol"; @@ -197,7 +197,7 @@ contract DeployRollupForUpgradeV6 is Script, StdAssertions { // zero, so a stale root 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: 0x2d89003cc2dc62b06f07d83d3635c66c63fc43668369d30e7ee516f908ee10e3, + vkTreeRoot: 0x2b93bfe8572e35ace261fa350a4e40327ec2dc83d5d5e2966e161561d2e9e04d, protocolContractsHash: 0x0030cdae9792549b9edb5b865f4e10e91bb87565f22ab80d405213f7e991b378, genesisArchiveRoot: 0x2ef904bbd5edc11a43cf48c4270edbf631d14aeaddafe307f8fa959e8113bfb6, ethereumSlotDuration: 12, // L1 slot time; no v5 equivalent, the v5 inbox took an explicit lag instead diff --git a/l1-contracts/script/deploy/HonkVerifier.sol b/l1-contracts/script/deploy/HonkVerifier.sol new file mode 100644 index 00000000000..c325622e462 --- /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@9f7f5055e989b21a7da3f512e72f6f004ed09909 + +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/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol new file mode 100644 index 00000000000..76f07a753e2 --- /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 = + 21_246_368_624_984_810_056_997_782_223_963_634_317_886_840_379_204_818_011_598_235_099_924_970_848_182; + 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; +} From c902090a55dfc12fe2d5538e5751113200152271 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Wed, 7 Oct 2026 10:49:00 +0000 Subject: [PATCH 03/12] docs(l1): update v6 runbook for the pinned verifier and confirmed genesis roots Build section now describes the pinned artifacts and the clean-clone deploy path (forge script with --use 0.8.30; a whole-project forge build fails on DeployRollupLib's @generated import). Input table and _config() comment record the roots from 9f7f5055. --- .../deploy/DeployRollupForUpgradeV6.s.sol | 10 +- .../script/deploy/V6_UPGRADE_RUNBOOK.md | 93 +++++++++++-------- 2 files changed, 58 insertions(+), 45 deletions(-) diff --git a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol index 63fe54db8f8..98d66ca6392 100644 --- a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol +++ b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol @@ -189,12 +189,10 @@ contract DeployRollupForUpgradeV6 is Script, StdAssertions { /// overrides it. function _config() internal view virtual returns (Config memory c) { c = Config({ - // STALE: read off a build at aztec-packages d521f0d940d096fbea5b62010d9c9c70f1dc0fd2, which - // was this branch's base before it was rebased. The protocol circuits have changed since, so - // `vkTreeRoot` no longer matches what this tree builds and must be regenerated before any - // deploy. The other two are expected to hold (no compiler bump, no transpiler or protocol - // contract change) but should be confirmed from the same rebuild. `run()` only guards against - // zero, so a stale root deploys silently. See "Fill in the inputs" in V6_UPGRADE_RUNBOOK.md. + // Read off the v6 release build at 9f7f5055e989b21a7da3f512e72f6f004ed09909, 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: 0x2b93bfe8572e35ace261fa350a4e40327ec2dc83d5d5e2966e161561d2e9e04d, diff --git a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md index 7f4bfa3ffa0..3424fe6922c 100644 --- a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md +++ b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md @@ -11,7 +11,8 @@ guarantees, and deliberately leaves alone. The deploy script deploys, in one broadcast: -1. `HonkVerifier` — the real epoch proof verifier. +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 @@ -51,28 +52,44 @@ shared, so the new cap applies to the outgoing rollup too, from the moment the p ## 1. Build -The script needs two generated artifacts that are not in git: +The verifier and the protocol constants are **pinned in git**, not built from this tree: -- `src/core/libraries/ConstantsGen.sol` — from `./scripts/remake-constants.sh` -- `generated/HonkVerifier.sol` — copied by `l1-contracts/bootstrap.sh build_verifier` from - `noir-projects/fnd/noir-protocol-circuits/target/keys/rollup_root_verifier.sol`, so - **noir-projects must have been bootstrapped first** +- `script/deploy/HonkVerifier.sol` — the epoch proof verifier from the v6 release build at + `9f7f5055e989b21a7da3f512e72f6f004ed09909`; its provenance comment names that commit. + `DeployRollupForUpgradeV6.s.sol` imports it as `./HonkVerifier.sol`. +- `src/core/libraries/ConstantsGen.sol` — from the same build. -From the repo root: +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 -make l1-contracts # or: cd l1-contracts && ./bootstrap.sh +git clone --branch --depth 1 https://github.com/AztecProtocol/aztec-packages.git +cd aztec-packages/l1-contracts +forge script script/deploy/DeployRollupForUpgradeV6.s.sol --sig 'run()' --use 0.8.30 \ + --rpc-url $RPC # add --private-key/--broadcast only for the real deploy ``` -This also fetches the pinned `solc-0.8.30` that `foundry.toml` points at. Then confirm: +`--use 0.8.30` is needed because `foundry.toml` points `solc` at `./solc-0.8.30`, which only +`./bootstrap.sh` fetches; forge downloads the same binary (`0.8.30+commit.73712a01`). The +`lib/` submodules are fetched by forge on first run. + +> 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 -cd l1-contracts && forge build --skip test +grep -m1 VK_HASH script/deploy/HonkVerifier.sol +# 0x1fd4eb1d14be45e05dc78c448eb4f29d7f63e4de95129c4a5e1058cd9837f8a6 ``` -> Do **not** hand-write a stub `generated/HonkVerifier.sol`. A stub compiles and deploys a verifier -> that accepts every proof. - ## 2. Fill in the inputs All configuration is the `_config()` literal in `DeployRollupForUpgradeV6.s.sol`. Before a real @@ -80,9 +97,9 @@ deploy these must be set: | Field | Status | Notes | |---|---|---| -| `vkTreeRoot` | **STALE — both chains** | read off the build at `d521f0d9`, which is no longer this branch's base; the protocol circuits have moved since. Regenerate, see below | -| `protocolContractsHash` | **recheck — both chains** | same build; expected to hold, but confirm from the same rebuild | -| `genesisArchiveRoot` | **recheck — both chains** | same; expected to hold. Must be below the BN254 scalar field modulus | +| `vkTreeRoot` | set | `0x2b93bfe8…d2e9e04d`, from the v6 release build at `9f7f5055`; see below | +| `protocolContractsHash` | set | `0x0030cdae…e991b378`, same build | +| `genesisArchiveRoot` | set | `0x2ef904bb…8113bfb6`, same build. Must be below the BN254 scalar field modulus | | `initialEthPerFeeAsset` | **TODO — mainnet** | E12 ETH-per-fee-asset price; a point-in-time market value, refresh at deploy time | | `sequencerRewardCalculator` | set | `address(0)` on both chains — v6 launches with no calculator, see below | | `oldFlushRewarder` | set (mainnet) | `0x5B98cA4dcE7b59CCf241D12f81d3d2eCF14e410e`; zero on Sepolia | @@ -94,8 +111,7 @@ deploy these must be set: | `proofOfPossessionGasLimit` | set | `300_000` on both chains; zero omits the action. The payload constructor reverts unless it exceeds the GSE's cap at deployment | Sepolia has no outstanding values. On mainnet only `initialEthPerFeeAsset` is an open decision; the -three genesis values are not decisions but build outputs, and are stale because the branch was -rebased, not because nobody has chosen them. +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 @@ -135,27 +151,26 @@ 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 currently in `_config()` were read off a full build of aztec-packages -`d521f0d940d096fbea5b62010d9c9c70f1dc0fd2`, which **was this branch's base and no longer is**: - -| Field | Value (from `d521f0d9`) | After the rebase | -|---|---|---| -| `vkTreeRoot` | `0x2d89003cc2dc62b06f07d83d3635c66c63fc43668369d30e7ee516f908ee10e3` | **moves** | -| `protocolContractsHash` | `0x0030cdae9792549b9edb5b865f4e10e91bb87565f22ab80d405213f7e991b378` | expected to hold | -| `genesisArchiveRoot` | `0x2ef904bbd5edc11a43cf48c4270edbf631d14aeaddafe307f8fa959e8113bfb6` | expected to hold | - -`vkTreeRoot` moves because the protocol circuits changed between `d521f0d9` and this branch's -current base — `private-kernel-lib` (gas meter, tail validator, transient-data reset), `blob`, and -`types/src/hash.nr`. Circuit changes rotate the verification keys and therefore the tree root. - -The other two are expected to hold: the Noir compiler was not bumped, `avm-transpiler` is unchanged, -and nothing under the protocol contracts moved, so no protocol class id rotates. That is an -expectation, not a guarantee — `types/src/hash.nr` is shared, so confirm both from the same rebuild -rather than carrying them forward on this note. - -Regenerate whenever the deploy is cut from a different commit: rebuilding the protocol circuits -moves `vkTreeRoot`, and rebuilding the protocol contracts moves all three. After `./bootstrap.sh`, -the first two come from the built packages and the third from the protocol constants: +The values in `_config()` were read off a full build (`make fast`) of the v6 release at +`9f7f5055e989b21a7da3f512e72f6f004ed09909`, the same build the pinned verifier and +`ConstantsGen.sol` come from: + +| Field | Value | +|---|---| +| `vkTreeRoot` | `0x2b93bfe8572e35ace261fa350a4e40327ec2dc83d5d5e2966e161561d2e9e04d` | +| `protocolContractsHash` | `0x0030cdae9792549b9edb5b865f4e10e91bb87565f22ab80d405213f7e991b378` | +| `genesisArchiveRoot` | `0x2ef904bbd5edc11a43cf48c4270edbf631d14aeaddafe307f8fa959e8113bfb6` | +| verifier `VK_HASH` | `0x1fd4eb1d14be45e05dc78c448eb4f29d7f63e4de95129c4a5e1058cd9837f8a6` | + +Only `vkTreeRoot` differs from the earlier values read at `d521f0d9` (`0x2d89003c…908ee10e3`): the +protocol circuits changed, rotating the verification keys. The Noir compiler, `avm-transpiler` and +the protocol contracts did not change, so the other two hold. + +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 From cb52d74e67482c63223410838270a81935c97954 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Wed, 7 Oct 2026 11:47:19 +0000 Subject: [PATCH 04/12] docs(l1): v6 runbook clean-clone deploy needs submodule init and a gas multiplier forge script does not fetch lib/, and its prague-based gas estimates are far below post-fork contract-creation costs; record measured Sepolia gas for each deployment. --- .../script/deploy/V6_UPGRADE_RUNBOOK.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md index 3424fe6922c..2ce78ea4650 100644 --- a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md +++ b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md @@ -69,14 +69,23 @@ 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/l1-contracts +cd aztec-packages +git submodule update --init --recursive --depth 1 -- l1-contracts/lib +cd l1-contracts forge script script/deploy/DeployRollupForUpgradeV6.s.sol --sig 'run()' --use 0.8.30 \ - --rpc-url $RPC # add --private-key/--broadcast only for the real deploy + --gas-estimate-multiplier 1000 \ + --rpc-url $RPC # add --private-key/--broadcast/--slow only for the real deploy ``` -`--use 0.8.30` is needed because `foundry.toml` points `solc` at `./solc-0.8.30`, which only -`./bootstrap.sh` fetches; forge downloads the same binary (`0.8.30+commit.73712a01`). The -`lib/` submodules are fetched by forge on first run. +- `--use 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`). +- The submodule step is required: `forge script` does not fetch `lib/` (only `forge build` does). +- `--gas-estimate-multiplier 1000`: forge sets each transaction's gas limit from its local + simulation (`evm_version = 'prague'`), which predates the fork that raised contract-creation + costs. Without the multiplier the verifier creation runs out of gas on-chain. Measured on Sepolia + after the fork: verifier 26.7M, rollup 84.8M, escape hatch 14.9M, payload 10.1M gas (136.5M + total); the multiplier's limits are 37.5M / 119.8M / 21.2M / 15.1M, all under the 200M block gas + limit. Only gas used is charged. > 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. From a5db7845433f4e04917523bc713372b3c38cf56b Mon Sep 17 00:00:00 2001 From: aminsammara Date: Wed, 7 Oct 2026 12:02:45 +0000 Subject: [PATCH 05/12] docs(l1): v6 runbook uses FOUNDRY_SOLC_VERSION and documents Etherscan verification --use covers compilation but not --verify, which re-reads foundry.toml's solc path. Deploy command gains --gas-estimate-multiplier and --slow; add the --resume re-verify path. --- .../script/deploy/V6_UPGRADE_RUNBOOK.md | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md index 2ce78ea4650..f5eac35426e 100644 --- a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md +++ b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md @@ -72,13 +72,16 @@ git clone --branch --depth 1 https://github.com/AztecProtocol/a cd aztec-packages git submodule update --init --recursive --depth 1 -- l1-contracts/lib cd l1-contracts -forge script script/deploy/DeployRollupForUpgradeV6.s.sol --sig 'run()' --use 0.8.30 \ +export FOUNDRY_SOLC_VERSION=0.8.30 +forge script script/deploy/DeployRollupForUpgradeV6.s.sol --sig 'run()' \ --gas-estimate-multiplier 1000 \ - --rpc-url $RPC # add --private-key/--broadcast/--slow only for the real deploy + --rpc-url $RPC # dry run; see Deploy for the broadcast flags ``` -- `--use 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`). +- `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-estimate-multiplier 1000`: forge sets each transaction's gas limit from its local simulation (`evm_version = 'prague'`), which predates the fork that raised contract-creation @@ -235,9 +238,10 @@ Run without `--broadcast` first. This executes the whole script including `verif ```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 + --gas-estimate-multiplier 1000 --rpc-url $RPC -vvv ``` `V6UpgradeSimulation` wraps the payload in a `GSEPayload` exactly as `GovernanceProposer` does, @@ -251,11 +255,23 @@ 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 \ + --gas-estimate-multiplier 1000 --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`. From f878e3bd46163833fafbb44fa36dbcb5f94f038f Mon Sep 17 00:00:00 2001 From: aminsammara Date: Wed, 7 Oct 2026 13:37:37 +0000 Subject: [PATCH 06/12] docs(l1): v6 runbook drops the gas multiplier for pre-Glamsterdam mainnet Forge's prague-based limits are correct before the fork, and a 10x multiplier would push the rollup creation past the EIP-7825 per-transaction cap. Record the post-fork numbers separately. --- .../script/deploy/V6_UPGRADE_RUNBOOK.md | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md index f5eac35426e..6b2afc08ded 100644 --- a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md +++ b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md @@ -74,7 +74,6 @@ 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()' \ - --gas-estimate-multiplier 1000 \ --rpc-url $RPC # dry run; see Deploy for the broadcast flags ``` @@ -83,12 +82,16 @@ forge script script/deploy/DeployRollupForUpgradeV6.s.sol --sig 'run()' \ 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-estimate-multiplier 1000`: forge sets each transaction's gas limit from its local - simulation (`evm_version = 'prague'`), which predates the fork that raised contract-creation - costs. Without the multiplier the verifier creation runs out of gas on-chain. Measured on Sepolia - after the fork: verifier 26.7M, rollup 84.8M, escape hatch 14.9M, payload 10.1M gas (136.5M - total); the multiplier's limits are 37.5M / 119.8M / 21.2M / 15.1M, all under the 200M block gas - limit. Only gas used is charged. +- 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. @@ -241,7 +244,7 @@ cd l1-contracts export FOUNDRY_SOLC_VERSION=0.8.30 REGISTRY_ADDRESS=0x35b22e09Ee0390539439E24f06Da43D83f90e298 \ forge script script/deploy/DeployRollupForUpgradeV6.s.sol:DeployRollupForUpgradeV6 \ - --gas-estimate-multiplier 1000 --rpc-url $RPC -vvv + --rpc-url $RPC -vvv ``` `V6UpgradeSimulation` wraps the payload in a `GSEPayload` exactly as `GovernanceProposer` does, @@ -258,7 +261,7 @@ genesis roots are the right ones — nothing on chain can. export FOUNDRY_SOLC_VERSION=0.8.30 ETHERSCAN_API_KEY= REGISTRY_ADDRESS=0x35b22e09Ee0390539439E24f06Da43D83f90e298 \ forge script script/deploy/DeployRollupForUpgradeV6.s.sol:DeployRollupForUpgradeV6 \ - --gas-estimate-multiplier 1000 --slow \ + --slow \ --rpc-url $RPC --private-key $KEY --broadcast --verify -vvv ``` From 2fca9ab40b6459f6ddea7089b656027252bb3273 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Thu, 8 Oct 2026 06:16:46 +0000 Subject: [PATCH 07/12] docs(l1): the sequencer reward calculator is AZIP-31, not AZIP-28 --- l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol | 4 ++-- l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol index 98d66ca6392..334bb67bc5b 100644 --- a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol +++ b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol @@ -116,7 +116,7 @@ contract DeployRollupForUpgradeV6 is Script, StdAssertions { uint96 checkpointReward; RewardBoostConfig rewardBoost; // The initial ISequencerRewardCalculator, or zero for none. Per-registry reward policy is no - // longer built into the rollup (AZIP-28): the rollup instead calls a governance-set calculator + // 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. @@ -225,7 +225,7 @@ contract DeployRollupForUpgradeV6 is Script, StdAssertions { increment: 101_400, maxScore: 367_500, a: 250_000, minimum: 10_000, k: 1_000_000 }), // AZIP-5; same as v5 // Deliberately none at genesis. v5 production carried the per-registry reductions for the - // auction and genesis-sale ATP registries in the rollup itself; AZIP-28 moved that policy out + // auction and genesis-sale ATP registries in the rollup itself; AZIP-31 moved that policy out // to a calculator contract, and no production calculator exists yet (the reference // implementations are test fixtures). v6 therefore launches paying every proposer the default // sequencer share, and governance installs a calculator in a follow-up payload via diff --git a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md index 6b2afc08ded..eaf4b02dd5f 100644 --- a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md +++ b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md @@ -135,7 +135,7 @@ loudly. A *stale* root does not: it is non-zero and deploys happily. Nothing gua ### Sequencer reward policy v5 carried per-registry reward reductions for the auction and genesis-sale ATP registries inside -the rollup. AZIP-28 removed that: the rollup now calls a governance-set `ISequencerRewardCalculator` +the rollup. AZIP-31 removed that: the rollup now calls a governance-set `ISequencerRewardCalculator` once per epoch proof, and `sequencerRewardCalculator` is the address it starts with. **v6 launches with `address(0)` on both chains, which means every proposer earns the default From 3548ab89e96f6eda16978358b26b307a541f4602 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Thu, 8 Oct 2026 07:26:13 +0000 Subject: [PATCH 08/12] chore(l1): exclude the pinned HonkVerifier from forge fmt The verifier is copied verbatim from the release build and must stay byte-identical to it; v5-next excludes its pinned verifier the same way. --- l1-contracts/foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index 169e985d43b..0756e659f55 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -74,6 +74,7 @@ variable_override_spacing = false wrap_comments = true number_underscore = "thousands" override_spacing = false +ignore = ["script/deploy/HonkVerifier.sol"] [lint] ignore = ["./lib/**"] From 6b2ed51d58c83fa85a3cd203fb4eda81596d80e9 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Thu, 8 Oct 2026 16:47:22 +0000 Subject: [PATCH 09/12] chore(l1): re-pin v6 verifier, ConstantsGen and genesis roots to 42fae4eb The verifier is byte-identical (VK_HASH unchanged); only its provenance comment moves. vkTreeRoot, protocolContractsHash and genesisArchiveRoot all move with the release build, and ConstantsGen now matches what v6 generates after the address domain separator change. --- .../deploy/DeployRollupForUpgradeV6.s.sol | 8 +++---- l1-contracts/script/deploy/HonkVerifier.sol | 2 +- .../script/deploy/V6_UPGRADE_RUNBOOK.md | 23 ++++++++++--------- .../src/core/libraries/ConstantsGen.sol | 2 +- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol index 334bb67bc5b..8305ad4308b 100644 --- a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol +++ b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol @@ -189,15 +189,15 @@ contract DeployRollupForUpgradeV6 is Script, StdAssertions { /// overrides it. function _config() internal view virtual returns (Config memory c) { c = Config({ - // Read off the v6 release build at 9f7f5055e989b21a7da3f512e72f6f004ed09909, the same build + // 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: 0x2b93bfe8572e35ace261fa350a4e40327ec2dc83d5d5e2966e161561d2e9e04d, - protocolContractsHash: 0x0030cdae9792549b9edb5b865f4e10e91bb87565f22ab80d405213f7e991b378, - genesisArchiveRoot: 0x2ef904bbd5edc11a43cf48c4270edbf631d14aeaddafe307f8fa959e8113bfb6, + 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, diff --git a/l1-contracts/script/deploy/HonkVerifier.sol b/l1-contracts/script/deploy/HonkVerifier.sol index c325622e462..54f5c4aa1e3 100644 --- a/l1-contracts/script/deploy/HonkVerifier.sol +++ b/l1-contracts/script/deploy/HonkVerifier.sol @@ -3,7 +3,7 @@ // Copyright 2022 Aztec pragma solidity ^0.8.27; -// Note: copied from v6@9f7f5055e989b21a7da3f512e72f6f004ed09909 +// Note: copied from v6@42fae4eb3feb5a493f2e8a2baf78b4716e323c40 interface IVerifier { function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool); diff --git a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md index eaf4b02dd5f..ca5f4ce3b5f 100644 --- a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md +++ b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md @@ -55,7 +55,7 @@ shared, so the new cap applies to the outgoing rollup too, from the moment the p 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 - `9f7f5055e989b21a7da3f512e72f6f004ed09909`; its provenance comment names that commit. + `42fae4eb3feb5a493f2e8a2baf78b4716e323c40`; its provenance comment names that commit. `DeployRollupForUpgradeV6.s.sol` imports it as `./HonkVerifier.sol`. - `src/core/libraries/ConstantsGen.sol` — from the same build. @@ -112,9 +112,9 @@ deploy these must be set: | Field | Status | Notes | |---|---|---| -| `vkTreeRoot` | set | `0x2b93bfe8…d2e9e04d`, from the v6 release build at `9f7f5055`; see below | -| `protocolContractsHash` | set | `0x0030cdae…e991b378`, same build | -| `genesisArchiveRoot` | set | `0x2ef904bb…8113bfb6`, same build. Must be below the BN254 scalar field modulus | +| `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` | **TODO — mainnet** | E12 ETH-per-fee-asset price; a point-in-time market value, refresh at deploy time | | `sequencerRewardCalculator` | set | `address(0)` on both chains — v6 launches with no calculator, see below | | `oldFlushRewarder` | set (mainnet) | `0x5B98cA4dcE7b59CCf241D12f81d3d2eCF14e410e`; zero on Sepolia | @@ -167,19 +167,20 @@ The three genesis values are produced by the protocol circuits / node build, not `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 -`9f7f5055e989b21a7da3f512e72f6f004ed09909`, the same build the pinned verifier and +`42fae4eb3feb5a493f2e8a2baf78b4716e323c40`, the same build the pinned verifier and `ConstantsGen.sol` come from: | Field | Value | |---|---| -| `vkTreeRoot` | `0x2b93bfe8572e35ace261fa350a4e40327ec2dc83d5d5e2966e161561d2e9e04d` | -| `protocolContractsHash` | `0x0030cdae9792549b9edb5b865f4e10e91bb87565f22ab80d405213f7e991b378` | -| `genesisArchiveRoot` | `0x2ef904bbd5edc11a43cf48c4270edbf631d14aeaddafe307f8fa959e8113bfb6` | +| `vkTreeRoot` | `0x22fff5de6ce590153df4468f7f0b152188a803d91b3c9c648b91d93d5913f219` | +| `protocolContractsHash` | `0x0f54271c52865841a77aaa66036eed901aff22fdce9f08e123ec8b205c9854d7` | +| `genesisArchiveRoot` | `0x29eb2c527f8d45276430363214e6c8d709ef3f657a3670ebac3179373d41e5c4` | | verifier `VK_HASH` | `0x1fd4eb1d14be45e05dc78c448eb4f29d7f63e4de95129c4a5e1058cd9837f8a6` | -Only `vkTreeRoot` differs from the earlier values read at `d521f0d9` (`0x2d89003c…908ee10e3`): the -protocol circuits changed, rotating the verification keys. The Noir compiler, `avm-transpiler` and -the protocol contracts did not change, so the other two hold. +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 diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 76f07a753e2..d6c43ee32d2 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -22,7 +22,7 @@ library Constants { uint256 internal constant INITIAL_CHECKPOINT_NUMBER = 1; uint256 internal constant MAX_CHECKPOINTS_PER_EPOCH = 32; uint256 internal constant GENESIS_ARCHIVE_ROOT = - 21_246_368_624_984_810_056_997_782_223_963_634_317_886_840_379_204_818_011_598_235_099_924_970_848_182; + 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; From 4354a1772830aa45701b67f1c43ccb9d1974e8d7 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Thu, 8 Oct 2026 16:57:41 +0000 Subject: [PATCH 10/12] chore(l1): set v6 mainnet initialEthPerFeeAsset from the AZTEC/ETH Uniswap pool 5_934_240 (0.0000059342 ETH per AZTEC), read from the AZTEC/ETH Uniswap v4 pool 0xce2899b1... at mainnet block 26148995. Removes the last TODO in the v6 deploy config. --- .../script/deploy/DeployRollupForUpgradeV6.s.sol | 13 +++++++------ l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol index 8305ad4308b..e14b89c8f18 100644 --- a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol +++ b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol @@ -105,8 +105,8 @@ contract DeployRollupForUpgradeV6 is Script, StdAssertions { uint256 entryQueueNormalFlushSizeMin; uint256 entryQueueNormalFlushSizeQuotient; uint256 entryQueueMaxFlushSize; - // Fees. `initialEthPerFeeAsset` is an E12 fixed-point ETH-per-fee-asset price and is therefore - // a point-in-time market value that must be refreshed before the deploy. + // 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; @@ -218,7 +218,9 @@ contract DeployRollupForUpgradeV6 is Script, StdAssertions { 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) - initialEthPerFeeAsset: 10_000_000, // v5 production: 9_512_195, priced 2026-06-11. TODO: refresh before v6 deploy + // 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({ @@ -283,9 +285,8 @@ contract DeployRollupForUpgradeV6 is Script, StdAssertions { // 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; - // Pinned rather than inherited so the mainnet TODO below does not read as outstanding here: - // testnet fees are not priced against a real market, and this value only sets the starting - // point the fee oracle moves from. + // 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 diff --git a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md index ca5f4ce3b5f..731d0351f1c 100644 --- a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md +++ b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md @@ -115,7 +115,7 @@ deploy these must be set: | `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` | **TODO — mainnet** | E12 ETH-per-fee-asset price; a point-in-time market value, refresh at deploy time | +| `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 | @@ -125,8 +125,8 @@ deploy these must be set: | `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 | -Sepolia has no outstanding values. On mainnet only `initialEthPerFeeAsset` is an open decision; the -three genesis values are not decisions but build outputs, pinned with the verifier. +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 From 75ec172fda14e1b0ec3309bb06fbd02c8ae402f4 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Thu, 8 Oct 2026 17:09:27 +0000 Subject: [PATCH 11/12] chore(l1): drop the sequencerRewardCalculator comment from the v6 config --- l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol index e14b89c8f18..4b689cacbf9 100644 --- a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol +++ b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol @@ -226,12 +226,6 @@ contract DeployRollupForUpgradeV6 is Script, StdAssertions { rewardBoost: RewardBoostConfig({ increment: 101_400, maxScore: 367_500, a: 250_000, minimum: 10_000, k: 1_000_000 }), // AZIP-5; same as v5 - // Deliberately none at genesis. v5 production carried the per-registry reductions for the - // auction and genesis-sale ATP registries in the rollup itself; AZIP-31 moved that policy out - // to a calculator contract, and no production calculator exists yet (the reference - // implementations are test fixtures). v6 therefore launches paying every proposer the default - // sequencer share, and governance installs a calculator in a follow-up payload via - // `setSequencerRewardCalculator`. Until that lands, ATP positions earn the default. sequencerRewardCalculator: address(0), slasherEnabled: true, slashingRoundSizeInEpochs: 4, From 1d8161a8ce67e457a628a7305139325c9928a994 Mon Sep 17 00:00:00 2001 From: aminsammara Date: Thu, 8 Oct 2026 17:15:30 +0000 Subject: [PATCH 12/12] docs(l1): simplify the v6 sequencer reward calculator notes --- .../deploy/DeployRollupForUpgradeV6.s.sol | 4 +--- .../script/deploy/V6_UPGRADE_RUNBOOK.md | 19 ++++++------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol index 4b689cacbf9..c5a1e7093f2 100644 --- a/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol +++ b/l1-contracts/script/deploy/DeployRollupForUpgradeV6.s.sol @@ -289,9 +289,7 @@ contract DeployRollupForUpgradeV6 is Script, StdAssertions { // 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 calculator on Sepolia either. The ATP registries any reduction policy would key on are - // a mainnet arrangement, so Sepolia has nothing to express and the mainnet value of zero - // already applies; this line records that as a decision rather than an omission. + // 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 diff --git a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md index 731d0351f1c..c8cbccedd62 100644 --- a/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md +++ b/l1-contracts/script/deploy/V6_UPGRADE_RUNBOOK.md @@ -134,19 +134,12 @@ loudly. A *stale* root does not: it is non-zero and deploys happily. Nothing gua ### Sequencer reward policy -v5 carried per-registry reward reductions for the auction and genesis-sale ATP registries inside -the rollup. AZIP-31 removed that: the rollup now calls a governance-set `ISequencerRewardCalculator` -once per epoch proof, and `sequencerRewardCalculator` is the address it starts with. - -**v6 launches with `address(0)` on both chains, which means every proposer earns the default -sequencer share — ATP positions included.** This is a deliberate choice, not an omission. No -production calculator exists yet; the two reference implementations -(`RegistryReductionCalculator`, `PremiumRewardCalculator`) are test fixtures under -`test/reward-calculators/` and are not built into `src`. Reinstating the reduction is a follow-up -governance payload that calls `setSequencerRewardCalculator`, which needs no redeploy. - -**If that follow-up slips, ATP holders are paid the full default in the interim.** Whoever signs -off on this upgrade should know that and agree to it. +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: