diff --git a/aggregation/src/light_aggregator/mod.rs b/aggregation/src/light_aggregator/mod.rs index d78d35022..3ef582708 100644 --- a/aggregation/src/light_aggregator/mod.rs +++ b/aggregation/src/light_aggregator/mod.rs @@ -81,7 +81,9 @@ use midnight_proofs::{ }, EvaluationDomain, }, - transcript::{CircuitTranscript, Hashable, Sampleable, Transcript}, + transcript::{ + CircuitTranscript, Hashable, Sampleable, Transcript, TranscriptHash, TranscriptInputBytes, + }, }; use rand::{CryptoRng, RngCore}; @@ -414,6 +416,11 @@ impl LightAggregator { C: Hashable, F: Sampleable + Hashable, u32: Hashable, + // Required by `plonk::prepare`, which this method delegates to. The + // bound is satisfied by both real transcript input types (`Vec` + // and `Vec`); propagating it here mirrors the other `prepare` + // callers in `proofs/tests/plonk_api.rs`. + ::Input: TranscriptInputBytes, { // Read the LHS of the acc from the transcript. let acc_lhs: Msm = { diff --git a/proofs/solidity-verifier/deployments/sepolia/moonlight-wrap/README.md b/proofs/solidity-verifier/deployments/sepolia/moonlight-wrap/README.md index 0a2c41b79..130a695cf 100644 --- a/proofs/solidity-verifier/deployments/sepolia/moonlight-wrap/README.md +++ b/proofs/solidity-verifier/deployments/sepolia/moonlight-wrap/README.md @@ -4,6 +4,64 @@ This directory contains the generated Moonlight wrap verifier contracts that were deployed to Sepolia, plus the exact runtime bytecode fetched back from the chain. +## Status: deployed code predates current codegen + +**The contracts at the addresses below are still the ones described here, but +the generator has since moved on. They are not reproducible from current +`main`.** Nothing in this directory has been regenerated -- the sources and +bytecode remain exactly what was deployed, because they are the record of what +is on chain. + +Re-rendering the same circuit at `e5300d4` produces a different verifier: + +| | Deployed | Current codegen | +| --- | ---: | ---: | +| `Halo2Verifier.sol` | 206,619 bytes | 212,419 bytes | +| Verifier runtime | 21,161 bytes | 21,203 bytes | +| `TRANSCRIPT_MPTR` | `0x80` | `0x1000` | + +The verifying key also differs, in its `quotient_program` section; the circuit +itself is unchanged (`acc_offset = 11`, 19 public inputs, `point_pair`). + +Fixes made after this deployment that it therefore does **not** carry: + +- **Memory layout rebase.** The deployed verifier bases its layout at `0x80`, + inside the `[0x80, 0x8e0)` window solc reserves for via-IR stack-to-memory + spill slots -- see AUDIT.md TA-5. It has not misbehaved, but the separation + rests on spill placement rather than on anything enforced. +- **Accumulator identity guard.** `load_acc_coord_shifted` used a bitwise `and` + against a radix base, making the guard false on every call; the identity + branch was dead and the canonicality barrier ineffective. Fail-closed, not a + forgery path. +- **Pairing result check.** `ec_pairing` folded the precompile result with a + bitwise `and`, accepting any odd return word rather than exactly `1`. +- **Constructor smoke test.** Probes used identity-only EIP-2537 vectors, which + a non-conformant precompile can satisfy without doing curve arithmetic. + +Redeploying is a deliberate on-chain action requiring a funded keystore and an +RPC endpoint; see "Recreate The Deployment" below. Until then this record stays +as-is and accurate. + +## Verifying the deployed bytecode + +The tracked source reproduces the on-chain runtime exactly, apart from the +immutable address slots that are substituted at deployment: + +```bash +solc --bin-runtime --optimize --optimize-runs 200 --via-ir \ + --evm-version cancun --no-cbor-metadata Halo2Verifier.sol +``` + +That yields 21,161 bytes -- matching `runtimeBytes` -- with codehash +`0x79432a36a98570db8c04b9c5cc23994477089eabd2501cfe0b0a78ae5f3c38f8`. + +It does **not** equal the recorded `runtimeCodeHash`, and should not: comparing +compiled output byte-for-byte against on-chain code shows exactly 40 differing +bytes, in two 20-byte runs at offsets `0x51` and `0x125`. Those are the two +placeholder slots for `address public immutable AUTHORIZED_VK`, filled in by the +constructor. Every other byte is identical. Verified at `e5300d4` with +`solc 0.8.30+commit.73712a01`. + ## Addresses | Contract | Address | diff --git a/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md b/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md index 4550d1607..6e669b3d3 100644 --- a/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md +++ b/proofs/solidity-verifier/docs/architecture/MEMORY_LAYOUT.md @@ -27,10 +27,35 @@ Solidity reserves the first four words of memory for compiler conventions: The generated verifier intentionally does not follow Solidity allocation by reading and bumping `mload(0x40)`. Instead, every generated absolute memory -region is planned at or above `0x80`. The streaming transcript buffer, the main -verifier return word, the split quotient return frame, the VK constructor -payload buffer, and low-memory precompile scratch all start from named -Rust-side layout constants rooted at `SOLIDITY_ALLOCATABLE_MEMORY_START`. +region is planned from named Rust-side layout constants. + +Those constants are **not** rooted at `0x80`. The verifier body is wrapped in +`assembly ("memory-safe")`, which is load-bearing -- without it the block does +not compile under `--via-ir` (stack too deep) -- but also factually untrue, +since the block writes memory it never obtained from the free-memory pointer. +The annotation is what enables solc's stack-to-memory mover, which reserves +spill slots upward from `0x80` and records the top in the runtime's +`mstore(0x40, ...)` prologue. Observed reservations range from `0x80` (none) to +`0x8e0`, varying with the circuit, the solc release, and the optimizer +schedule. + +Basing the layout at `0x80` therefore put solc's spill slots and the verifier's +own transcript buffer in the same bytes, separated only by live ranges that +nothing enforced -- a recompilation could silently place a live spill across a +verifier write and corrupt a challenge or pairing input. So the streaming +transcript buffer, the main verifier return word, the split quotient return +frame, low-memory precompile scratch, the accumulator/KZG pairing-batch hash +frame, and the final two-pair pairing frame are rooted at +`LOW_MEMORY_SCRATCH_START` (`0x1000`), above the largest observed reservation. +`VerifierMemoryLayout::validate()` rejects any generated region below that +base, and `compiled_memoryguard_does_not_overlap_generated_layout` compiles +each rendered variant -- including the accumulator-bearing ones -- and fails +the build if a future circuit or compiler pushes the reservation past it. + +The VK constructor payload buffer is the exception: it lives in +`Halo2VerifyingKey`, whose assembly carries no `memory-safe` annotation, so +solc reserves nothing there and it stays at +`SOLIDITY_ALLOCATABLE_MEMORY_START`. The code generator treats `[0x00..0x80)` as off limits for generated writes: `VerifierMemoryLayout::validate()` rejects any registered region inside that @@ -140,6 +165,8 @@ which changes the transcript-buffer bound. The verifier reserves: - unaligned starts or lengths; - any generated region inside Solidity-reserved memory `[0x00..0x80)`; +- any generated region below `LOW_MEMORY_SCRATCH_START` (`0x1000`), where a + live via-IR spill slot could share its bytes; - overlapping permanent regions; - overlapping scratch regions that are live in the same `MemoryPhase`; - PCS fixed-window overflows. @@ -245,7 +272,7 @@ The planner validates by lifetime, not just by address. | Phase | Region examples | Notes | | --- | --- | --- | -| `Transcript` | `[0, transcript_words * 0x20)` | Must stay below `VK_MPTR`. | +| `Transcript` | `[0x1000, 0x1000 + transcript_words * 0x20)` | Must stay below `VK_MPTR`. | | `ScalarInv` | `VK_MPTR - 0x100` frame | Historical modexp scratch near the VK payload. | | `LagrangeBatchInvert` | `batch_invert_scratch_mptr` | Reuses selector bytes before selector accumulators are live. | | `QuotientVm` | quotient temps and stack | Used before PCS final MSM. | @@ -253,8 +280,8 @@ The planner validates by lifetime, not just by address. | `PcsQComTrace` | optional q_com trace MSM | Aliases `pcs_scratch_mptr`; trace-only. | | `PcsFinalMsm` | final MSM input and selector accumulators | Selector accumulators and final MSM must not overlap in this phase. | | `AccumulatorMsm` | public accumulator MSM input | Length is derived from accumulator/VK shape. | -| `AccumulatorPairingBatch` | `[0x100, 0x320)` | Hash domain plus four G1 points for accumulator pairing batching. | -| `FinalPairing` | two-pair KZG pairing frame | Low-memory final precompile frame. | +| `AccumulatorPairingBatch` | `[0x1000, 0x1220)` | Hash domain plus four G1 points for accumulator pairing batching. | +| `FinalPairing` | `[0x1220, 0x1540)` two-pair KZG pairing frame | Final precompile frame, placed past the pairing-batch frame by construction. | ## Update Rules diff --git a/proofs/solidity-verifier/docs/architecture/MIGRATION.md b/proofs/solidity-verifier/docs/architecture/MIGRATION.md index 5e314d953..237132060 100644 --- a/proofs/solidity-verifier/docs/architecture/MIGRATION.md +++ b/proofs/solidity-verifier/docs/architecture/MIGRATION.md @@ -343,6 +343,10 @@ The `cargo test --lib` suite now stands at 7/7 green: `assembly ("memory-safe") { ... }` to silence the legacy stack-too-deep error path; with `--via-ir` solc 0.8.30 now compiles the full ~117 kB output cleanly. + Note the annotation is not merely cosmetic: it enables solc's + stack-to-memory mover, which reserves spill slots upward from + `0x80`. See AUDIT.md TA-5 and `docs/architecture/MEMORY_LAYOUT.md` + for why the generated layout is based at `0x1000` rather than `0x80`. * `tests/poseidon_fixture.rs` — new integration test (gated behind `feature = "evm"` and currently `#[ignore]`d, see below) that: 1. Configures `SRS_DIR` to point at diff --git a/proofs/solidity-verifier/docs/audit/AUDIT.md b/proofs/solidity-verifier/docs/audit/AUDIT.md index b85c509aa..aba140bf0 100644 --- a/proofs/solidity-verifier/docs/audit/AUDIT.md +++ b/proofs/solidity-verifier/docs/audit/AUDIT.md @@ -2314,10 +2314,33 @@ memory-safety rules. If future edits add Solidity code after the block, or if the compiler reasons across the block in an unexpected way, this becomes a miscompilation risk. +**Status: addressed.** The risk was not hypothetical. `solc 0.8.30` with the +pinned flags emits `mstore(0x40, 0x08e0)` for the moonlight-wrap render, i.e. +it reserved `[0x80, 0x8e0)` for via-IR stack-to-memory spill slots -- directly +on top of a generated layout that started at `0x80`. Reservations observed +across renders: `0x80` (ivc-keccak, none), `0xe0` (rsa), `0x3c0` (poseidon), +`0x8e0` (moonlight-wrap). At least one spill (`mstore(0x300, mload(0x6a00))`, +the `y` challenge, re-read ~600 IR lines later) sits in that window. + +The recommendation below to remove the annotation was tested and does not work: +the block then fails to compile with `Cannot swap Variable usr$f_4 ... too deep +in the stack by 1 slots`, and solc itself suggests re-adding the annotation. +The annotation is load-bearing. + +The fix instead moves the generated layout above the reservation +(`LOW_MEMORY_SCRATCH_START = 0x1000`), making the two regions disjoint in space +so their liveness no longer matters, and adds +`compiled_memoryguard_does_not_overlap_generated_layout`, which compiles each +rendered variant and fails if the reservation ever grows past that base. Note +this removes the *consequence*, not the false annotation itself; making the +annotation honest would require runtime `mload(0x40)`-based re-basing at the +cost of an `ADD` per memory access. + Recommendation: -- Prefer removing `"memory-safe"` from the terminal verifier block unless there - is a compiler-specific proof that this pattern is accepted. +- ~~Prefer removing `"memory-safe"` from the terminal verifier block unless + there is a compiler-specific proof that this pattern is accepted.~~ + Superseded: removal does not compile. See status note above. - Pin the exact compiler and EVM version. The Renegade audit's recommendation to use fixed pragmas rather than floating `^0.8.x` is especially relevant for generated verifier code. diff --git a/proofs/solidity-verifier/docs/reference/QUOTIENT_EVALUATOR_9KB_BYTECODE.md b/proofs/solidity-verifier/docs/reference/QUOTIENT_EVALUATOR_9KB_BYTECODE.md index 63e06a6b2..64e5c6230 100644 --- a/proofs/solidity-verifier/docs/reference/QUOTIENT_EVALUATOR_9KB_BYTECODE.md +++ b/proofs/solidity-verifier/docs/reference/QUOTIENT_EVALUATOR_9KB_BYTECODE.md @@ -104,8 +104,16 @@ normal ABI call. The largest win was the compact quotient VM. Instead of rendering most identities as Yul source, the generator lowers them to -a small bytecode language in `src/lowering/quotient/mod.rs`. The runtime consumer -is `templates/partials/quotient_numerator/QuotientNumeratorBlock.yul`. +a small bytecode language in `src/lowering/quotient_numerator/vm/mod.rs`. The +runtime consumer is +`templates/partials/quotient_numerator/QuotientNumeratorBlock.yul`. + +Every emitted program is certified before it can be pinned into a verifying key: +`src/lowering/quotient_numerator/vm/certify.rs` re-executes the finalized +bytecode with the independent interpreter in +`src/lowering/quotient_numerator/vm/reference.rs` and compares each identity +against direct evaluation of the expression tree it was lowered from, and +against a second build with the limb superinstructions disabled. The VK payload carries: diff --git a/proofs/solidity-verifier/fixtures/ivc/Halo2QuotientEvaluator.sol b/proofs/solidity-verifier/fixtures/ivc/Halo2QuotientEvaluator.sol new file mode 100644 index 000000000..c2bc6699d --- /dev/null +++ b/proofs/solidity-verifier/fixtures/ivc/Halo2QuotientEvaluator.sol @@ -0,0 +1,1593 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.24; + +/// @title Split Halo2 quotient numerator evaluator. +/// @notice Reconstructs the scalar side of the linearization query for a generated verifier. +/// @dev This is the split-out implementation of the expensive +/// `partially_evaluate_identities` / `compute_linearization_commitment` side +/// from the Midfall Rust verifier: +/// - `midfall/proofs/src/plonk/mod.rs::partially_evaluate_identities` +/// - `midfall/proofs/src/plonk/linearization/verifier.rs::compute_linearization_commitment` +/// - `midfall/proofs/src/plonk/{permutation,logup,trash}.rs` +/// @dev The main verifier has already parsed calldata, checked proof scalar +/// ranges, sampled Fiat-Shamir challenges, loaded the VK payload, and computed +/// local Lagrange/public-input values before making the staticcall. +/// +/// Instead of receiving structured Solidity arguments, the evaluator receives +/// the verifier's memory frame as raw calldata: +/// +/// calldata[0..QUOTIENT_FRAME_LEN) +/// == memory[QUOTIENT_FRAME_BASE..QUOTIENT_FRAME_BASE+QUOTIENT_FRAME_LEN) +/// +/// The fallback copies that frame back into the same generated memory +/// addresses. All constants below are therefore memory addresses inside that +/// copied frame, not ABI offsets. +/// +/// Output is a compact fixed frame consumed by Halo2Verifier: +/// +/// word 0: QUOTIENT_MAGIC, a generated version/magic guard +/// word 1: linearization_expected_eval +/// word 2..: simple-selector accumulator scalars +/// +/// This contract reconstructs the Rust verifier's y-batched identity numerator +/// nu_y(x) and returns the linearization expected scalar -nu_y(x). It does not +/// evaluate or trust a quotient scalar h(x). +/// +/// The quotient limb commitments are handled by Halo2Verifier on the commitment +/// side as (1 - x^n) * sum_i x_split^i * Q_i. That is why this scalar side is +/// -nu_y(x), not h(x) = nu_y(x) / (x^n - 1). +/// +/// See docs/QUOTIENT_NUMERATOR_EVALUATOR.md for the full Rust/Solidity mapping. +contract Halo2QuotientEvaluator { + // BLS12-381 scalar field modulus. All arithmetic in this contract is over + // Fr and uses addmod/mulmod with this modulus. + uint256 internal constant FR_MODULUS = + 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; + + // Start of the copied verifier-key payload in memory. The VK payload also + // carries the compact quotient VM constant/program tables used by the + // included numerator block. + uint256 internal constant VK_MPTR = 0x3680; + + // Fiat-Shamir challenge slots. Halo2Verifier sampled these in transcript + // order before the external call. The evaluator only reads them. + uint256 internal constant CHALLENGE_MPTR = 0x7900; + uint256 internal constant THETA_MPTR = 0x7900; + uint256 internal constant BETA_MPTR = 0x7920; + uint256 internal constant GAMMA_MPTR = 0x7940; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x7960; + uint256 internal constant Y_MPTR = 0x7980; + uint256 internal constant X_MPTR = 0x79a0; + uint256 internal constant X1_MPTR = 0x79c0; + uint256 internal constant X2_MPTR = 0x79e0; + uint256 internal constant X3_MPTR = 0x7a00; + uint256 internal constant X4_MPTR = 0x7a20; + + // Common polynomial values at x. Halo2Verifier computes these once after + // sampling x and places them in the frame so the numerator block can share + // the exact Rust verifier inputs. + uint256 internal constant X_N_MPTR = 0x7c40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x7c60; + uint256 internal constant L_LAST_MPTR = 0x7c80; + uint256 internal constant L_BLIND_MPTR = 0x7ca0; + uint256 internal constant L_0_MPTR = 0x7cc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x7ce0; + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x7d00; + + // Proof evaluation table. Values are already decoded as canonical Fr words + // by Halo2Verifier. The generated numerator code indexes this table by the + // same query order as the Rust verifier. + uint256 internal constant REVERSED_EVALS_MPTR = 0x9480; + + // Scratch/output region for simple-selector linearization accumulators. + // The numerator block writes one bucket per simple selector, then the + // fallback copies those buckets into the compact return frame. + uint256 internal constant SELECTOR_ACC_MPTR = 0xb140; + // Callee-local scratch for trace hooks. Trace-enabled verifier builds call + // this evaluator with CALL so quotient identity logs can be compared with + // the native Rust trace. Production verifier builds keep using STATICCALL + // and render this evaluator without trace hooks. + uint256 internal constant TRACE_U256_MPTR = 0x1000; + uint256 internal constant QUOTIENT_OUTPUT_MPTR = 0x1000; + + // External-call frame metadata. The main verifier calls this contract with + // exactly QUOTIENT_FRAME_LEN bytes starting at + // QUOTIENT_FRAME_BASE, then checks the return length and QUOTIENT_MAGIC. + uint256 internal constant QUOTIENT_FRAME_BASE = 0x3680; + uint256 internal constant QUOTIENT_FRAME_LEN = 0x6ac0; + uint256 internal constant QUOTIENT_OUTPUT_LEN = 0x0180; + uint256 internal constant QUOTIENT_MAGIC = 0x00000000000000000000000000000000000000000000000051554556414c0001; + + /// @notice Evaluate the generated quotient numerator block for one verifier memory frame. + /// @dev Calldata is exactly the raw frame, not ABI-encoded arguments. Returns `QUOTIENT_MAGIC`, the linearization expected eval, and selector buckets. + /// @dev This fallback also uses generated absolute memory addresses and + /// returns directly from assembly. Its compact return frame starts at + /// `0x80`, preserving Solidity's reserved memory words. + fallback() external { + assembly ("memory-safe") { + // Reject malformed calls. This contract is not a general-purpose + // ABI endpoint; accepting partial or shifted frames would make the + // generated memory addresses point at the wrong data. + if iszero(eq(calldatasize(), QUOTIENT_FRAME_LEN)) { revert(0, 0) } + + // Rehydrate the verifier memory image. From this point onward the + // generated Yul can use the same MPTR constants as the monolithic + // verifier path. + calldatacopy(QUOTIENT_FRAME_BASE, 0, QUOTIENT_FRAME_LEN) + + let r := FR_MODULUS + + // This included block is the main body of the evaluator. It: + // 1. evaluates gate/permutation/lookup/trash identities in the + // same order as Rust `partially_evaluate_identities`; + // 2. y-batches fully evaluated identities into + // quotient_eval_numer; + // 3. y-batches simple-selector identities into + // SELECTOR_ACC_MPTR buckets; + // 4. writes -quotient_eval_numer to QUOTIENT_EVAL_MPTR. + // + // Depending on codegen settings, some identities are native Yul + // callbacks and the rest are executed by the compact q_program VM + // stored in the copied VK payload. + // + // The upstream Rust comments call out that simple multiplicative + // selectors do not appear as normal proof eval scalars. The Yul + // block mirrors that rule by accumulating those identities into + // SELECTOR_ACC_MPTR buckets for later multiplication by fixed + // selector commitments, while fully evaluated identities contribute + // to the negated expected scalar. // Optional quotient helper functions. Each one is rendered only + // when the Rust lowering pass recognized the corresponding + // expression shape in this generated verifier. They are pure Fr + // helpers and share the same FR_MODULUS as the surrounding + // numerator block. + // VK-specialized identity helper for Poseidon S-box terms. + // + // Rust source shape: + // circuits/src/hash/poseidon/poseidon_chip.rs::sbox + // full_round_gate / partial_round_gate + // circuits/src/hash/poseidon/round_skips.rs::RoundId + // + // The Rust verifier only sees this as an Expression tree from + // `vk.cs.gates`; the generator emits q_pow5 after recognizing five + // equal multiplicative factors. It is a codegen shortcut for x^5, + // not a separate verifier rule. + function q_pow5(x) -> z { + let q_r := FR_MODULUS + let x2 := mulmod(x, x, q_r) + z := mulmod(x, mulmod(x2, x2, q_r), q_r) + } // =============================================================== + // Batched identity numerator / linearization target. + // + // This block does not evaluate the quotient polynomial h(x), and + // the proof does not provide an h(x) scalar to trust. Instead it: + // + // 1. Reconstructs the y-batched constraint numerator nu_y(x) + // from the alleged polynomial evaluations read after the + // transcript sampled x. + // 2. Stores -nu_y(x) as the expected opening scalar for the + // linearized commitment. + // + // The commitment side is built in the next block from the quotient + // limb commitments as (1 - x^n) * Σ_i x_split^i * Q_i, plus any + // simple-selector commitments. The PCS check later binds that + // linearized commitment to this expected scalar at x. + // + // Rust source-of-truth: + // - verifier.rs reads quotient commitments, samples x, then + // reads/computes all evaluations used below. + // - mod.rs::partially_evaluate_identities returns identities in + // gate, permutation, lookup, trash order. + // - linearization/verifier.rs::compute_linearization_commitment + // reverse-folds those identities by powers of y, sends + // simple-selector identities to selector commitment scalars, + // and subtracts fully-evaluated identities into expected_eval. + // + // This template is shared by the monolithic and external quotient + // paths. In the external path, Halo2QuotientEvaluator first copies + // the verifier memory frame into the same generated addresses. + // + // Runtime inputs expected to exist before this block starts: + // - `r` is the BLS12-381 scalar-field modulus. + // - Y_MPTR holds the quotient batching challenge y. + // - X_MPTR, L_*_MPTR, INSTANCE_EVAL_MPTR, and + // REVERSED_EVALS_MPTR hold values parsed or derived by the + // main verifier after the transcript sampled x. + // - VK_MPTR holds the pinned VK payload; in compact mode that + // payload includes the quotient constant table and bytecode. + // + // Runtime outputs written by this block: + // - QUOTIENT_EVAL_MPTR receives the scalar expected opening for + // the linearized commitment, namely -nu_y(x). + // - SELECTOR_ACC_MPTR[0..num_simple_selectors) receives one + // linearization scalar per generated simple selector. + // + // Line-by-line reading conventions used below: + // + // * Every runtime value is one canonical Fr element stored in a + // 256-bit EVM memory word. The small integer operands decoded + // from q_program are never field values; they are pointers, + // constant-table slots, selector indexes, offsets, or counts. + // + // * `mload(ptr)` is the only way the VM turns a small pointer + // operand into a real 255-bit field element. The value loaded + // from memory is then combined with `addmod(..., r)` or + // `mulmod(..., r)`, so every arithmetic line is reduced modulo + // the BLS12-381 scalar-field order. + // + // * `q_top` is the cached top of the VM operand stack. When an + // opcode needs to push while `q_top` is already live, the old + // value is written to `q_sp` and `q_sp` is advanced by one + // word. Binary `ADD`/`MUL` move `q_sp` back by one word and + // combine that spilled value with `q_top`. + // + // * Identity boundaries are explicit. Expression opcodes leave + // one value in `q_top`; `FOLD_MAIN` or `FOLD_SELECTOR` consumes + // it and advances the global y-batch position. Native callback + // opcodes are only emitted at empty-stack boundaries and run + // generated Yul that performs the same fold side effects. + // + // * The generated Solidity source intentionally emits comments + // before opcode cases. Those comments are documentation only: + // they do not affect bytecode, but they make rendered verifier + // assembly readable without jumping back to Rust codegen. + // =============================================================== + { + // Compact quotient-program mode. + // + // The largest identity expressions are not all emitted as + // unrolled Yul. Instead, most arithmetic is encoded as a small + // q_program bytecode stored in the VK payload. This block + // interprets that program, while selected heavy identities may + // still be emitted as native callbacks for gas. + // + // Compact mode is a code-size trade: short bytecode operands + // name already-planned memory slots, and the interpreter turns + // those names into Fr arithmetic. The opcode stream is fully + // generated and pinned by the VK/runtime codehash; no proof + // calldata can alter control flow. + // Load the quotient batching challenge used by every fold. + let y := mload(Y_MPTR) + + // q_const_mptr points to Fr constants used by the VM. + // q_program_mptr points to the bytecode stream. + // Constants are stored as consecutive 32-byte Fr words. + let q_const_mptr := 0x3a60 + // Program bytes are also stored in the VK payload, packed into + // 32-byte words by PackedProgramCodec. + let q_program_mptr := 0x50a0 + // Running Horner accumulator for fully evaluated identities. + // After all identities, this is nu_y(x) for the `None` + // identity group. + // Initialize A = 0 before scanning the identity stream. + mstore(0xb280, 0) + // Simple selectors are grouped into separate linearization + // buckets. They start at zero for every proof. + // q_sel_zero_off walks selector bucket byte offsets. + for { let q_sel_zero_off := 0 } lt(q_sel_zero_off, 0x0140) { q_sel_zero_off := add(q_sel_zero_off, 0x20) } { + // B_s = 0 for each simple selector bucket. + mstore(add(SELECTOR_ACC_MPTR, q_sel_zero_off), 0) + } + // Codegen knows the selector identity positions. Precompute + // the y^k powers needed for selector gap and tail updates, + // avoiding a runtime y^-1 modexp and per-identity selector + // scale maintenance. + { + // q_y_power holds y^i at the current loop index. + let q_y_power := 1 + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0xb2c0, 1) + // Start at i=1 because y^0 = 1 is written above. + for { let q_y_power_i := 1 } lt(q_y_power_i, 49) { q_y_power_i := add(q_y_power_i, 1) } { + // Advance from y^(i-1) to y^i modulo Fr. + q_y_power := mulmod(q_y_power, y, r) + // Store y^i at selector_power_mptr + 32*i. + mstore(add(0xb2c0, shl(5, q_y_power_i)), q_y_power) + } + } + + // Direct inline prefix. These identities are generated as Yul + // before entering the VM. They use the same fold snippets as + // VM/native identities, so they occupy the same y-batch order. + { + let var0 := 0x1 + let f_3 := mload(0x9ac0) + let f_4 := mload(0x99c0) + let a_0 := mload(0x94a0) + let var1 := mulmod(f_4, a_0, r) + let var2 := addmod(f_3, var1, r) + let f_5 := mload(0x99e0) + let a_1 := mload(0x94c0) + let var3 := mulmod(f_5, a_1, r) + let var4 := addmod(var2, var3, r) + let f_6 := mload(0x9a00) + let a_2 := mload(0x94e0) + let var5 := mulmod(f_6, a_2, r) + let var6 := addmod(var4, var5, r) + let f_7 := mload(0x9a20) + let a_3 := mload(0x9500) + let var7 := mulmod(f_7, a_3, r) + let var8 := addmod(var6, var7, r) + let f_8 := mload(0x9a40) + let a_4 := mload(0x9520) + let var9 := mulmod(f_8, a_4, r) + let var10 := addmod(var8, var9, r) + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var11 := mulmod(f_0, a_0_next_1, r) + let var12 := addmod(var10, var11, r) + let f_1 := mload(0x9a80) + let var13 := mulmod(f_1, a_0, r) + let var14 := mulmod(var13, a_1, r) + let var15 := addmod(var12, var14, r) + let f_2 := mload(0x9aa0) + let var16 := mulmod(f_2, a_0, r) + let var17 := mulmod(var16, a_2, r) + let var18 := addmod(var15, var17, r) + let var19 := mulmod(var0, var18, r) + mstore(0xb8e0, var19) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_1 := mload(0x94c0) + let a_2 := mload(0x94e0) + let var1 := addmod(a_1, a_2, r) + let a_3 := mload(0x9500) + let var2 := addmod(0, sub(r, a_3), r) + let var3 := addmod(var1, var2, r) + let a_4 := mload(0x9520) + let var4 := addmod(0, sub(r, a_4), r) + let var5 := addmod(var3, var4, r) + let var6 := mulmod(var0, var5, r) + mstore(0xb8e0, var6) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let f_4 := mload(0x99c0) + let var1 := addmod(a_0, f_4, r) + let a_0_next_1 := mload(0x9540) + let var2 := addmod(0, sub(r, a_0_next_1), r) + let var3 := addmod(var1, var2, r) + let var4 := mulmod(var0, var3, r) + mstore(0xb8e0, var4) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_1 := mload(0x94c0) + let f_5 := mload(0x99e0) + let var1 := addmod(a_1, f_5, r) + let a_1_next_1 := mload(0x9560) + let var2 := addmod(0, sub(r, a_1_next_1), r) + let var3 := addmod(var1, var2, r) + let var4 := mulmod(var0, var3, r) + mstore(0xb8e0, var4) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + + // VM registers: + // q_pc current bytecode pointer + // q_end end of bytecode stream + // q_sp memory stack pointer for non-top stack values + // q_top cached top-of-stack value + // q_has_top whether q_top currently holds a stack value + // + // The cached top reduces memory traffic in the interpreter. + // q_sp's registered range must cover the interpreted operand + // stack plus any native callback scratch that reuses this base + // pointer. In particular, the native permutation callback + // writes a structured scratch table at program.stack_mptr. + // q_pc starts at the first encoded instruction. + let q_pc := q_program_mptr + // q_end is an exclusive byte pointer for the VM loop. + let q_end := add(q_program_mptr, 0x11cf) + // q_sp starts at the first free stack word. + let q_sp := 0xb8e0 + // q_top is meaningless until q_has_top is set. + let q_top := 0 + // q_has_top = 0 means the VM stack is empty. + let q_has_top := 0 + + // q_program opcode summary: + // 0x01/0x09 push const 0x02/0x05 push memory + // 0x03/0x04 push token ptr 0x06 add, 0x07 mul, 0x08 neg + // 0x0a fold main identity 0x0b fold selector identity + // 0x0c..0x11 add/mul const or memory into top + // 0x12..0x16 fused add-mul runs + // 0x17/0x18 reserved + // 0x19 native permutation 0x1b native heavy identity + // 0x1c LIN7 0x1d BILIN7_ROW + // 0x1e BILIN7_PAIRWISE 0x1f native lookup + // 0x20 POW5 0x21 MODARITH7 + // 0x22 AFFINE_SUM + // + // The default IVC verifier uses one physical encoding for the + // logical VM: compact byte-oriented opcodes with variable-width + // operands, dynamic runs, and limb-aware cases. + + // Byte-oriented encoding: opcodes are one byte followed by + // variable-width operand bytes. + for { } lt(q_pc, q_end) { } { + // The bytecode table is byte-addressed, but EVM memory + // loads whole words. `byte(0, mload(q_pc))` extracts the + // opcode at the current byte cursor; each case advances + // q_pc by exactly its operand width. + let q_op := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + + switch q_op + // VM 0x05 PUSH_MEM_U16 (bytes): next two bytes are a short memory pointer. + case 0x05 { + // Operand layout: u16 absolute memory pointer. The + // memory planner keeps the hot quotient frame below + // 64 KiB when this compact form is emitted. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + if q_has_top { + mstore(q_sp, q_top) + q_sp := add(q_sp, 0x20) + } + q_top := mload(q_ptr) + q_has_top := 1 + } + // VM 0x06 ADD: pop one spilled stack word and add it to q_top. + case 0x06 { + // The safety validator guarantees a spilled operand + // exists before ADD. q_top is the right operand. + q_sp := sub(q_sp, 0x20) + q_top := addmod(mload(q_sp), q_top, r) + } + // VM 0x08 NEG: replace q_top with its Fr negation. + case 0x08 { + // addmod(0, r - x, r) maps zero back to zero and every + // nonzero scalar to its canonical additive inverse. + q_top := addmod(0, sub(r, q_top), r) + } + // VM 0x0d MUL_CONST_U8: multiply q_top by a small constant-table slot. + case 0x0d { + // One-byte constant-index multiply, used by short + // affine chains after an initial PUSH. + let qconst := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + q_top := mulmod(q_top, mload(add(q_const_mptr, shl(5, qconst))), r) + } + // VM 0x10 ADD_MEM_U16: add a short memory load into q_top. + case 0x10 { + // Operand layout: u16 pointer. The pointed word is an + // already range-checked Fr scalar in verifier memory. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + q_top := addmod(q_top, mload(q_ptr), r) + } + // VM 0x11 MUL_MEM_U16: multiply q_top by a short memory load. + case 0x11 { + // In-place multiply by a planned memory word. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + q_top := mulmod(q_top, mload(q_ptr), r) + } + // Limb-aware opcodes are opt-in compact forms for + // structurally recognized non-SHA foreign-field shapes. + // Coefficients are indexes into q_const_mptr, which is + // generated from VK/program data, never from proof + // calldata. + // + // Rust source shape: + // proofs/src/plonk/mod.rs::partially_evaluate_identities + // circuits/src/field/foreign/util.rs::{sum_exprs,pair_wise_prod} + // circuits/src/field/foreign/params.rs::{base_powers,double_base_powers} + // + // "Foreign field" means the circuit represents elements + // modulo another modulus m as 7 limbs in base + // 2^LOG2_BASE. The verifier does not switch fields; it + // evaluates the lowered identity over BLS12-381 Fr, using + // Fr coefficients equal to base^i mod m or base^(i+j) mod m. + // VM 0x21 MODARITH7: byte-only fused affine 7-limb foreign-field/ECC identity. + case 0x21 { + // MODARITH7: + // maybe_cond * ( + // c + // + sum LIN7 blocks + // + sum BILIN7_ROW blocks + // + sum BILIN7_PAIRWISE blocks + // + sum coeff[k] * mload(ptr[k]) + // + sum coeff[k] * mload(lhs[k]) * mload(rhs[k]) + // ) + // It is a dispatch/operand-load optimization only; + // all coefficients still come from the generated + // quotient constant table. + // + // Flags: + // bit 0: multiply the final affine sum by a memory + // condition word. + // bit 1: seed q_acc from a constant-table word + // before reading the counted term blocks. + let q_flags := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + let q_cond_ptr := 0 + if and(q_flags, 0x01) { + // Optional condition pointer. When present, the + // whole identity is gated by mload(q_cond_ptr). + q_cond_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + } + + let q_acc := 0 + if and(q_flags, 0x02) { + // Optional constant seed for affine identities + // with a standalone constant term. + let qconst := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + q_acc := mload(add(q_const_mptr, shl(5, qconst))) + } + + // Five one-byte counters describe the blocks that + // follow. Each block has a fixed-width internal layout, + // so q_pc can advance without per-term tags. + let q_counts_word := mload(q_pc) + let q_lin_count := byte(0, q_counts_word) + let q_row_count := byte(1, q_counts_word) + let q_pairwise_count := byte(2, q_counts_word) + let q_mem_count := byte(3, q_counts_word) + let q_product_count := byte(4, q_counts_word) + q_pc := add(q_pc, 5) + + if q_has_top { + mstore(q_sp, q_top) + q_sp := add(q_sp, 0x20) + } + + // LIN7 blocks: q_acc += sum_i c_i * limb_i. + for { let q_lin_block := 0 } lt(q_lin_block, q_lin_count) { q_lin_block := add(q_lin_block, 1) } { + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_ptr := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod(mload(add(q_const_mptr, shl(5, qconst))), mload(q_ptr), r), + r + ) + } + } + + // BILIN7_ROW blocks: q_acc += lhs * sum_i c_i * rhs_i. + for { let q_row_block := 0 } lt(q_row_block, q_row_count) { q_row_block := add(q_row_block, 1) } { + let q_lhs := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + let q_lhs_value := mload(q_lhs) + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_rhs := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod( + mulmod(q_lhs_value, mload(q_rhs), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + } + + // BILIN7_PAIRWISE blocks: q_acc += weighted 7-by-7 + // product convolution. + for { let q_pair_block := 0 } lt(q_pair_block, q_pairwise_count) { q_pair_block := add(q_pair_block, 1) } { + let q_pair_word := mload(q_pc) + let q_lhs_base := shr(240, q_pair_word) + let q_rhs_base := and(shr(224, q_pair_word), 0xffff) + q_pc := add(q_pc, 0x04) + let q_coeff_pc := q_pc + q_pc := add(q_pc, 13) + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_lhs_value := mload(add(q_lhs_base, shl(5, q_i))) + for { let q_j := 0 } lt(q_j, 7) { q_j := add(q_j, 1) } { + let qconst := byte(0, mload(add(q_coeff_pc, add(q_i, q_j)))) + q_acc := addmod( + q_acc, + mulmod( + mulmod(q_lhs_value, mload(add(q_rhs_base, shl(5, q_j))), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + } + } + + // Extra linear memory terms outside the 7-limb shapes. + for { let q_mem_block := 0 } lt(q_mem_block, q_mem_count) { q_mem_block := add(q_mem_block, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_ptr := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod(mload(add(q_const_mptr, shl(5, qconst))), mload(q_ptr), r), + r + ) + } + + // Extra binary product terms outside the 7-limb shapes. + for { let q_product_block := 0 } lt(q_product_block, q_product_count) { q_product_block := add(q_product_block, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_lhs := and(shr(232, q_word), 0xffff) + let q_rhs := and(shr(216, q_word), 0xffff) + q_pc := add(q_pc, 5) + q_acc := addmod( + q_acc, + mulmod( + mulmod(mload(q_lhs), mload(q_rhs), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + + if and(q_flags, 0x01) { + // Apply the optional gate condition last so every + // subterm shares the same selector/condition. + q_acc := mulmod(mload(q_cond_ptr), q_acc, r) + } + // MODARITH7 pushes its fused identity value. + q_top := q_acc + q_has_top := 1 + } + // Native permutation callback. It evaluates the + // permutation identities from permutation.rs at this exact + // VM position, preserving the Rust identity order while + // avoiding a large interpreted product loop. + // VM 0x19 NATIVE_PERMUTATION: marker for the generated permutation callback. + case 0x19 { + // Native callbacks are identity-boundary opcodes. They + // must not inherit any partially evaluated VM stack + // state from the previous expression. + q_top := 0 + q_has_top := 0 + // The generated loop below uses program.stack_mptr as + // its scratch-table base, not as a conventional VM + // stack. The Rust memory planner must reserve enough + // words for structured_permutation_scratch_words(meta) + // whenever this opcode can appear. + q_sp := 0xb8e0 + // The generated lines below call the same fold snippets + // used by interpreted expressions, so trace IDs and + // y-batch positions remain contiguous. + { + let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 + let q_perm_vals := 0xb8e0 + let q_perm_sigmas := 0xbb20 + let q_perm_z_cur := 0xbd60 + let q_perm_z_next := 0xbe20 + let q_perm_z_last := 0xbee0 + let q_perm_delta_base_ptr := 0xbf80 + let q_perm_num_cols := 18 + let q_perm_num_sets := 6 + let q_perm_chunk_len := 3 + let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 + mstore(add(q_perm_vals, 0x0), mload(0x99a0)) + { + for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { + let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) + let q_perm_val_load_src_off := q_perm_val_load_dst_off + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x94a0, q_perm_val_load_src_off))) + } + } + mstore(add(q_perm_vals, 0xc0), mload(0x9480)) + mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) + { + for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 9) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { + let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) + let q_perm_val_load_src_off := q_perm_val_load_dst_off + mstore(add(add(q_perm_vals, 0x100), q_perm_val_load_dst_off), mload(add(0x95a0, q_perm_val_load_src_off))) + } + } + mstore(add(q_perm_vals, 0x220), mload(0x9980)) + { + for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 18) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { + let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) + let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x9bc0, q_perm_sigma_load_src_off))) + } + } + { + for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 6) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { + let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) + let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x9e00, q_perm_z_cur_load_src_off))) + } + } + { + for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 6) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { + let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) + let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x9e20, q_perm_z_next_load_src_off))) + } + } + { + for { let q_perm_z_last_load_i := 0 } lt(q_perm_z_last_load_i, 5) { q_perm_z_last_load_i := add(q_perm_z_last_load_i, 1) } { + let q_perm_z_last_load_dst_off := shl(5, q_perm_z_last_load_i) + let q_perm_z_last_load_src_off := mul(q_perm_z_last_load_i, 0x60) + mstore(add(add(q_perm_z_last, 0x0), q_perm_z_last_load_dst_off), mload(add(0x9e40, q_perm_z_last_load_src_off))) + } + } + let q_perm_eval := 0 + q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + let q_perm_zn := mload(add(q_perm_z_cur, 0xa0)) + q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + for { let q_perm_i := 1 } lt(q_perm_i, 6) { q_perm_i := add(q_perm_i, 1) } { + let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) + let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) + q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + } + mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) + for { let q_perm_set := 0 } lt(q_perm_set, 6) { q_perm_set := add(q_perm_set, 1) } { + let q_perm_start := mul(q_perm_set, q_perm_chunk_len) + let q_perm_end := add(q_perm_start, q_perm_chunk_len) + if gt(q_perm_end, q_perm_num_cols) { q_perm_end := q_perm_num_cols } + let q_perm_left := mload(add(q_perm_z_next, shl(5, q_perm_set))) + let q_perm_right := mload(add(q_perm_z_cur, shl(5, q_perm_set))) + let q_perm_delta_pow := mload(q_perm_delta_base_ptr) + for { let q_perm_j := q_perm_start } lt(q_perm_j, q_perm_end) { q_perm_j := add(q_perm_j, 1) } { + let q_perm_off := shl(5, q_perm_j) + let q_perm_v := mload(add(q_perm_vals, q_perm_off)) + let q_perm_s := mload(add(q_perm_sigmas, q_perm_off)) + q_perm_left := mulmod(q_perm_left, addmod(addmod(q_perm_v, mulmod(mload(BETA_MPTR), q_perm_s, r), r), mload(GAMMA_MPTR), r), r) + q_perm_right := mulmod(q_perm_right, addmod(addmod(q_perm_v, q_perm_delta_pow, r), mload(GAMMA_MPTR), r), r) + q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) + } + q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) + } + } + } + // Native lookup callback. This whole-family opcode + // evaluates the LogUp boundary, helper-chunk, and + // accumulator identities at this VM position, preserving + // the Rust y-batch order while avoiding many interpreted + // product-loop opcodes. + // VM 0x1f NATIVE_LOOKUP: marker for the generated LogUp lookup callback. + case 0x1f { + // Reset VM stack state before entering structured + // lookup Yul. Lookup callbacks own their scratch + // layout and perform all needed folds internally. + q_top := 0 + q_has_top := 0 + // The generated loop below uses program.stack_mptr as + // f+beta/prefix/suffix scratch rather than as a + // conventional VM stack. The Rust memory planner must + // reserve structured_lookup_scratch_words(meta). + q_sp := 0xb8e0 + // Generated LogUp code follows the same y-batch order + // as the Rust identity stream. + { + let q_lookup_f := 0xb8e0 + let q_lookup_prefix := 0xb960 + let q_lookup_suffix := 0xb9e0 + let q_lookup_l0 := mload(L_0_MPTR) + let q_lookup_llast := mload(L_LAST_MPTR) + let q_lookup_lblind := mload(L_BLIND_MPTR) + let q_lookup_lsum := addmod(q_lookup_l0, q_lookup_llast, r) + let q_lookup_active := addmod(1, sub(r, addmod(q_lookup_llast, q_lookup_lblind, r)), r) + let q_lookup_beta := mload(BETA_MPTR) + let q_lookup_theta := mload(THETA_MPTR) + { + { + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa060), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let f_10 := mload(0x9ae0) + let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) + let var1 := mulmod(var0, q_lookup_theta, r) + for { let q_lookup_shared_i := 0 } lt(q_lookup_shared_i, 4) { q_lookup_shared_i := add(q_lookup_shared_i, 1) } { + let q_lookup_shared_off := shl(5, q_lookup_shared_i) + let q_lookup_shared_tail := mload(add(0x94c0, q_lookup_shared_off)) + let q_lookup_shared_compressed := addmod(var1, q_lookup_shared_tail, r) + mstore(add(q_lookup_f, q_lookup_shared_off), addmod(q_lookup_shared_compressed, q_lookup_beta, r)) + } + let q_lookup_product := 1 + for { let q_lookup_prod_i := 0 } lt(q_lookup_prod_i, 4) { q_lookup_prod_i := add(q_lookup_prod_i, 1) } { + q_lookup_product := mulmod(q_lookup_product, mload(add(q_lookup_f, shl(5, q_lookup_prod_i))), r) + } + mstore(q_lookup_prefix, 1) + for { let q_lookup_pref_i := 1 } lt(q_lookup_pref_i, 4) { q_lookup_pref_i := add(q_lookup_pref_i, 1) } { + let q_lookup_pref_prev := sub(q_lookup_pref_i, 1) + mstore(add(q_lookup_prefix, shl(5, q_lookup_pref_i)), mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_pref_prev))), mload(add(q_lookup_f, shl(5, q_lookup_pref_prev))), r)) + } + mstore(add(q_lookup_suffix, 0x60), 1) + for { let q_lookup_suf_i := sub(4, 1) } gt(q_lookup_suf_i, 0) { q_lookup_suf_i := sub(q_lookup_suf_i, 1) } { + let q_lookup_suf_prev := sub(q_lookup_suf_i, 1) + mstore(add(q_lookup_suffix, shl(5, q_lookup_suf_prev)), mulmod(mload(add(q_lookup_suffix, shl(5, q_lookup_suf_i))), mload(add(q_lookup_f, shl(5, q_lookup_suf_i))), r)) + } + let q_lookup_sum := 0 + for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 4) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { + q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) + } + let q_lookup_eval := addmod(mulmod(mload(0xa040), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let q_lookup_sum_h := mload(0xa040) + let f_17 := mload(0x9b60) + let f_11 := mload(0x9b00) + let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) + let f_12 := mload(0x9b20) + let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) + let q_lookup_s_sum_h := mulmod(f_17, q_lookup_sum_h, r) + let q_lookup_diff := addmod(mload(0xa080), sub(r, addmod(mload(0xa060), q_lookup_s_sum_h, r)), r) + let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa020), r) + let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + } + { + { + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa0e0), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let a_14 := mload(0x9980) + let var0 := addmod(mulmod(0, q_lookup_theta, r), a_14, r) + let a_0 := mload(0x94a0) + let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_0, r) + let a_1 := mload(0x94c0) + let var2 := addmod(mulmod(var1, q_lookup_theta, r), a_1, r) + let a_2 := mload(0x94e0) + let var3 := addmod(mulmod(var2, q_lookup_theta, r), a_2, r) + let a_3 := mload(0x9500) + let var4 := addmod(mulmod(var3, q_lookup_theta, r), a_3, r) + let a_4 := mload(0x9520) + let var5 := addmod(mulmod(var4, q_lookup_theta, r), a_4, r) + let a_5 := mload(0x95a0) + let var6 := addmod(mulmod(var5, q_lookup_theta, r), a_5, r) + let a_6 := mload(0x95c0) + let var7 := addmod(mulmod(var6, q_lookup_theta, r), a_6, r) + let a_7 := mload(0x95e0) + let var8 := addmod(mulmod(var7, q_lookup_theta, r), a_7, r) + let a_8 := mload(0x9600) + let var9 := addmod(mulmod(var8, q_lookup_theta, r), a_8, r) + let a_9 := mload(0x9620) + let var10 := addmod(mulmod(var9, q_lookup_theta, r), a_9, r) + let a_10 := mload(0x9640) + let var11 := addmod(mulmod(var10, q_lookup_theta, r), a_10, r) + let a_11 := mload(0x9660) + let var12 := addmod(mulmod(var11, q_lookup_theta, r), a_11, r) + let a_12 := mload(0x9680) + let var13 := addmod(mulmod(var12, q_lookup_theta, r), a_12, r) + let a_13 := mload(0x96a0) + let var14 := addmod(mulmod(var13, q_lookup_theta, r), a_13, r) + let f_13 := mload(0x9b40) + let var15 := addmod(mulmod(var14, q_lookup_theta, r), f_13, r) + let q_lookup_eval := addmod(mulmod(mload(0xa0c0), addmod(var15, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let q_lookup_sum_h := mload(0xa0c0) + let var0 := 0x1 + let f_26 := mload(0x9ba0) + let var1 := addmod(0, sub(r, f_26), r) + let var2 := addmod(var0, var1, r) + let a_14 := mload(0x9980) + let var3 := mulmod(var2, a_14, r) + let var4 := addmod(mulmod(0, q_lookup_theta, r), var3, r) + let a_0 := mload(0x94a0) + let var5 := mulmod(var2, a_0, r) + let var6 := addmod(mulmod(var4, q_lookup_theta, r), var5, r) + let a_1 := mload(0x94c0) + let var7 := mulmod(var2, a_1, r) + let var8 := addmod(mulmod(var6, q_lookup_theta, r), var7, r) + let a_2 := mload(0x94e0) + let var9 := mulmod(var2, a_2, r) + let var10 := addmod(mulmod(var8, q_lookup_theta, r), var9, r) + let a_3 := mload(0x9500) + let var11 := mulmod(var2, a_3, r) + let var12 := addmod(mulmod(var10, q_lookup_theta, r), var11, r) + let a_4 := mload(0x9520) + let var13 := mulmod(var2, a_4, r) + let var14 := addmod(mulmod(var12, q_lookup_theta, r), var13, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var2, a_5, r) + let var16 := addmod(mulmod(var14, q_lookup_theta, r), var15, r) + let a_6 := mload(0x95c0) + let var17 := mulmod(var2, a_6, r) + let var18 := addmod(mulmod(var16, q_lookup_theta, r), var17, r) + let a_7 := mload(0x95e0) + let var19 := mulmod(var2, a_7, r) + let var20 := addmod(mulmod(var18, q_lookup_theta, r), var19, r) + let a_8 := mload(0x9600) + let var21 := mulmod(var2, a_8, r) + let var22 := addmod(mulmod(var20, q_lookup_theta, r), var21, r) + let a_9 := mload(0x9620) + let var23 := mulmod(var2, a_9, r) + let var24 := addmod(mulmod(var22, q_lookup_theta, r), var23, r) + let a_10 := mload(0x9640) + let var25 := mulmod(var2, a_10, r) + let var26 := addmod(mulmod(var24, q_lookup_theta, r), var25, r) + let a_11 := mload(0x9660) + let var27 := mulmod(var2, a_11, r) + let var28 := addmod(mulmod(var26, q_lookup_theta, r), var27, r) + let a_12 := mload(0x9680) + let var29 := mulmod(var2, a_12, r) + let var30 := addmod(mulmod(var28, q_lookup_theta, r), var29, r) + let a_13 := mload(0x96a0) + let var31 := mulmod(var2, a_13, r) + let var32 := addmod(mulmod(var30, q_lookup_theta, r), var31, r) + let f_13 := mload(0x9b40) + let var33 := mulmod(var2, f_13, r) + let var34 := addmod(mulmod(var32, q_lookup_theta, r), var33, r) + let q_lookup_s_sum_h := mulmod(var0, q_lookup_sum_h, r) + let q_lookup_diff := addmod(mload(0xa100), sub(r, addmod(mload(0xa0e0), q_lookup_s_sum_h, r)), r) + let q_lookup_t_beta := addmod(var34, q_lookup_beta, r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa0a0), r) + let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + } + } + } + // Native callbacks are generated only for the heaviest + // recognized Midfall gate identities. All other gate and + // non-native identity arithmetic remains in + // the compact q_program VM above, preserving the Rust + // `partially_evaluate_identities` order. + // VM 0x1b NATIVE_IDENTITY: marker for generated heavy-gate callbacks. + case 0x1b { + // Operand layout: u16 native callback index. The + // manifest validates that callback indexes appear in + // generated order and target existing switch cases. + let q_native_idx := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + // Heavy identities are whole expressions, so clear the + // interpreter stack before dispatching. + q_top := 0 + q_has_top := 0 + q_sp := 0xb8e0 + // Native identity sub-cases are generated from selected heavy gate identities. + switch q_native_idx + case 0 { + { + let var0 := 0x1 + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var1 := addmod(0, sub(r, a_0_next_1), r) + let var2 := addmod(f_0, var1, r) + let var3 := 0x1b8114c381b922fd5d6d241210e2d8a68ad5744053ba9e776118de4107b51ace + let a_0 := mload(0x94a0) + let var4 := mulmod(a_0, a_0, r) + let a_3 := mload(0x9500) + let var5 := mulmod(var4, a_3, r) + let var6 := mulmod(var3, var5, r) + let var7 := addmod(var2, var6, r) + let var8 := 0x3df32e4cc4cb2ed20e5d21899cf5331775990ccaec4c09b4e3717213fcc0d763 + let a_1 := mload(0x94c0) + let var9 := mulmod(a_1, a_1, r) + let a_4 := mload(0x9520) + let var10 := mulmod(var9, a_4, r) + let var11 := mulmod(var8, var10, r) + let var12 := addmod(var7, var11, r) + let var13 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 + let a_2 := mload(0x94e0) + let var14 := mulmod(a_2, a_2, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var14, a_5, r) + let var16 := mulmod(var13, var15, r) + let var17 := addmod(var12, var16, r) + let var18 := mulmod(var0, var17, r) + mstore(0xb8e0, var18) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 1 { + { + let var0 := 0x1 + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) + let var1 := addmod(0, sub(r, a_1_next_1), r) + let var2 := addmod(f_1, var1, r) + let var3 := 0x404d21073985d14e432a4ad76d3fae06ca74314b950fe7b1d7f501cd31a8b374 + let a_0 := mload(0x94a0) + let var4 := mulmod(a_0, a_0, r) + let a_3 := mload(0x9500) + let var5 := mulmod(var4, a_3, r) + let var6 := mulmod(var3, var5, r) + let var7 := addmod(var2, var6, r) + let var8 := 0xb2cc8704264c6bd81bc620e9e524d4b73e9b2317679422ff7fa1603955649f1 + let a_1 := mload(0x94c0) + let var9 := mulmod(a_1, a_1, r) + let a_4 := mload(0x9520) + let var10 := mulmod(var9, a_4, r) + let var11 := mulmod(var8, var10, r) + let var12 := addmod(var7, var11, r) + let var13 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f + let a_2 := mload(0x94e0) + let var14 := mulmod(a_2, a_2, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var14, a_5, r) + let var16 := mulmod(var13, var15, r) + let var17 := addmod(var12, var16, r) + let var18 := mulmod(var0, var17, r) + mstore(0xb8e0, var18) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 2 { + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let a_0_next_1 := mload(0x9540) + let var1 := mulmod(a_0, a_0_next_1, r) + let var2 := 0x100000000000000 + let a_1_next_1 := mload(0x9560) + let var3 := mulmod(a_0, a_1_next_1, r) + let var4 := mulmod(var2, var3, r) + let var5 := addmod(var1, var4, r) + let var6 := 0x10000000000000000000000000000 + let a_2_next_1 := mload(0x9580) + let var7 := mulmod(a_0, a_2_next_1, r) + let var8 := mulmod(var6, var7, r) + let var9 := addmod(var5, var8, r) + let a_1 := mload(0x94c0) + let var10 := mulmod(a_1, a_0_next_1, r) + let var11 := mulmod(var2, var10, r) + let var12 := addmod(var9, var11, r) + let var13 := mulmod(a_1, a_1_next_1, r) + let var14 := mulmod(var6, var13, r) + let var15 := addmod(var12, var14, r) + let var16 := 0x3212e00cde6d2002b119d800000347fcb8 + let a_6_next_1 := mload(0x9720) + let var17 := mulmod(a_1, a_6_next_1, r) + let var18 := mulmod(var16, var17, r) + let var19 := addmod(var15, var18, r) + let a_2 := mload(0x94e0) + let var20 := mulmod(a_2, a_0_next_1, r) + let var21 := mulmod(var6, var20, r) + let var22 := addmod(var19, var21, r) + let a_5_next_1 := mload(0x9700) + let var23 := mulmod(a_2, a_5_next_1, r) + let var24 := mulmod(var16, var23, r) + let var25 := addmod(var22, var24, r) + let var26 := 0x297784894e27525bc342b7fde37dba9366 + let var27 := mulmod(a_2, a_6_next_1, r) + let var28 := mulmod(var26, var27, r) + let var29 := addmod(var25, var28, r) + let a_3 := mload(0x9500) + let a_4_next_1 := mload(0x96e0) + let var30 := mulmod(a_3, a_4_next_1, r) + let var31 := mulmod(var16, var30, r) + let var32 := addmod(var29, var31, r) + let var33 := mulmod(a_3, a_5_next_1, r) + let var34 := mulmod(var26, var33, r) + let var35 := addmod(var32, var34, r) + let var36 := 0x340f2ebe380a0f5eff4360543988a61dc2 + let var37 := mulmod(a_3, a_6_next_1, r) + let var38 := mulmod(var36, var37, r) + let var39 := addmod(var35, var38, r) + let a_4 := mload(0x9520) + let a_3_next_1 := mload(0x96c0) + let var40 := mulmod(a_4, a_3_next_1, r) + let var41 := mulmod(var16, var40, r) + let var42 := addmod(var39, var41, r) + let var43 := mulmod(a_4, a_4_next_1, r) + let var44 := mulmod(var26, var43, r) + let var45 := addmod(var42, var44, r) + let var46 := mulmod(a_4, a_5_next_1, r) + let var47 := mulmod(var36, var46, r) + let var48 := addmod(var45, var47, r) + let var49 := 0x13af65741744bd7bb2c6872df2b800320 + let var50 := mulmod(a_4, a_6_next_1, r) + let var51 := mulmod(var49, var50, r) + let var52 := addmod(var48, var51, r) + let a_5 := mload(0x95a0) + let var53 := mulmod(a_5, a_2_next_1, r) + let var54 := mulmod(var16, var53, r) + let var55 := addmod(var52, var54, r) + let var56 := mulmod(a_5, a_3_next_1, r) + let var57 := mulmod(var26, var56, r) + let var58 := addmod(var55, var57, r) + let var59 := mulmod(a_5, a_4_next_1, r) + let var60 := mulmod(var36, var59, r) + let var61 := addmod(var58, var60, r) + let var62 := mulmod(a_5, a_5_next_1, r) + let var63 := mulmod(var49, var62, r) + let var64 := addmod(var61, var63, r) + let var65 := 0x2cb9b546d20373eaf85e8f53db883cb548 + let var66 := mulmod(a_5, a_6_next_1, r) + let var67 := mulmod(var65, var66, r) + let var68 := addmod(var64, var67, r) + let a_6 := mload(0x95c0) + let var69 := mulmod(a_6, a_1_next_1, r) + let var70 := mulmod(var16, var69, r) + let var71 := addmod(var68, var70, r) + let var72 := mulmod(a_6, a_2_next_1, r) + let var73 := mulmod(var26, var72, r) + let var74 := addmod(var71, var73, r) + let var75 := mulmod(a_6, a_3_next_1, r) + let var76 := mulmod(var36, var75, r) + let var77 := addmod(var74, var76, r) + let var78 := mulmod(a_6, a_4_next_1, r) + let var79 := mulmod(var49, var78, r) + let var80 := addmod(var77, var79, r) + let var81 := mulmod(a_6, a_5_next_1, r) + let var82 := mulmod(var65, var81, r) + let var83 := addmod(var80, var82, r) + let var84 := 0xc8557e86f90d0d89eed6eb5349a0f8820 + let var85 := mulmod(a_6, a_6_next_1, r) + let var86 := mulmod(var84, var85, r) + let var87 := addmod(var83, var86, r) + let var88 := mulmod(var2, a_1, r) + let var89 := addmod(a_0, var88, r) + let var90 := mulmod(var6, a_2, r) + let var91 := addmod(var89, var90, r) + let var92 := addmod(var87, var91, r) + let var93 := mulmod(var2, a_1_next_1, r) + let var94 := addmod(a_0_next_1, var93, r) + let var95 := mulmod(var6, a_2_next_1, r) + let var96 := addmod(var94, var95, r) + let var97 := addmod(var92, var96, r) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) + let var98 := mulmod(var2, a_8, r) + let var99 := addmod(a_7, var98, r) + let a_9 := mload(0x9620) + let var100 := mulmod(var6, a_9, r) + let var101 := addmod(var99, var100, r) + let var102 := addmod(0, sub(r, var101), r) + let var103 := addmod(var97, var102, r) + let a_7_next_1 := mload(0x9740) + let var104 := 0x241eabfffeb153ffffb9feffffffffaaab + let var105 := mulmod(a_7_next_1, var104, r) + let var106 := addmod(0, sub(r, var105), r) + let var107 := addmod(var103, var106, r) + let var108 := addmod(0, sub(r, var16), r) + let var109 := addmod(var107, var108, r) + let a_8_next_1 := mload(0x9760) + let var110 := 0x73eda753299d7d483339d80809a1d80553b9202d7ffe85d4800008bb20000001 + let var111 := addmod(a_8_next_1, var110, r) + let var112 := 0x4000000000000000000000000000000000 + let var113 := mulmod(var111, var112, r) + let var114 := addmod(0, sub(r, var113), r) + let var115 := addmod(var109, var114, r) + let var116 := mulmod(var0, var115, r) + mstore(0xb8e0, var116) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x80) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 3 { + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let var1 := 0x10000000000000000000000000000 + let var2 := addmod(a_0, var1, r) + let var3 := 0x100000000000000 + let a_1 := mload(0x94c0) + let var4 := addmod(a_1, var1, r) + let var5 := mulmod(var3, var4, r) + let var6 := addmod(var2, var5, r) + let a_2 := mload(0x94e0) + let var7 := addmod(a_2, var1, r) + let var8 := mulmod(var1, var7, r) + let var9 := addmod(var6, var8, r) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) + let var10 := mulmod(var3, a_8, r) + let var11 := addmod(a_7, var10, r) + let a_9 := mload(0x9620) + let var12 := mulmod(var1, a_9, r) + let var13 := addmod(var11, var12, r) + let var14 := addmod(0, sub(r, var13), r) + let var15 := addmod(var9, var14, r) + let var16 := addmod(0, sub(r, var1), r) + let var17 := addmod(var15, var16, r) + let a_7_next_1 := mload(0x9740) + let var18 := 0x241eabfffeb153ffffb9feffffffffaaab + let var19 := mulmod(a_7_next_1, var18, r) + let var20 := addmod(0, sub(r, var19), r) + let var21 := addmod(var17, var20, r) + let var22 := 0xd9d44a30b019261257667fde3844a8cd6 + let var23 := addmod(0, sub(r, var22), r) + let var24 := addmod(var21, var23, r) + let a_8_next_1 := mload(0x9760) + let var25 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5b6e855000003ab00002 + let var26 := addmod(a_8_next_1, var25, r) + let var27 := 0x4000000000000000000000000000000000 + let var28 := mulmod(var26, var27, r) + let var29 := addmod(0, sub(r, var28), r) + let var30 := addmod(var24, var29, r) + let var31 := mulmod(var0, var30, r) + mstore(0xb8e0, var31) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xa0) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + default { revert(0, 0) } + } + // VM 0x0b FOLD_SELECTOR: consume q_top into one simple-selector bucket. + case 0x0b { + // Operand layout packed into three bytes: + // high byte: selector bucket index; + // low u16 : y-power gap since this selector's + // previous contribution. + let q_selector_payload := shr(232, mload(q_pc)) + q_pc := add(q_pc, 3) + let q_sel_idx := shr(16, q_selector_payload) + let q_sel_gap := and(q_selector_payload, 0xffff) + let q_eval := q_top + q_has_top := 0 + // Simple-selector identity: keep the same y-batch + // position as main identities, then advance only this + // selector bucket by its codegen-known gap. + // + // The global fully-evaluated accumulator is still + // multiplied by y so later main identities land at the + // same y powers as Rust's reverse fold. + mstore(0xb280, mulmod(mload(0xb280), y, r)) + let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) + let q_sel_acc := mload(q_target_ptr) + if q_sel_gap { + // Selector buckets are sparse in the global + // identity stream. Precomputed y^gap advances only + // this selector's local accumulator. + q_sel_acc := mulmod(q_sel_acc, mload(add(0xb2c0, shl(5, q_sel_gap))), r) + } + mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) + } + // Invalid generated bytecode should fail closed. 0x1a intentionally lands here. + default { + revert(0, 0) + } + } + // The VK-pinned bytecode must end exactly at q_end and every + // identity must have been consumed by a fold/native callback. + // This catches malformed generator output whose final opcode + // over-reads operands or leaves a partial expression live. + if iszero(eq(q_pc, q_end)) { revert(0, 0) } + if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0xb8e0)) { revert(0, 0) } + + // Structured post-VM suffix. The current default uses this for + // regular trash constraints: it is smaller than fully unrolled + // Yul and cheaper than interpreting every trash operation. + // + // These generated blocks run after q_pc reaches q_end, but + // they still participate in the same identity order and write + // into the same numerator / selector accumulators. + { + let q_trash_tau := mload(TRASH_CHALLENGE_MPTR) + { + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var0 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 + let var1 := mulmod(a_0_next_1, var0, r) + let var2 := addmod(f_0, var1, r) + let var3 := 0x590ba402032e82eb1f660ef09796c5686345a5054ed96dae8e2d233633788771 + let a_0 := mload(0x94a0) + let var4 := mulmod(var3, a_0, r) + let var5 := addmod(var2, var4, r) + let var6 := 0x52f789e4afc3801f7411102ee2f47cc5954a744e71cac98e75ea962a55a0a76f + let a_1 := mload(0x94c0) + let var7 := mulmod(var6, a_1, r) + let var8 := addmod(var5, var7, r) + let var9 := 0x3509dd2fe3aac0080783557fec090fb1cb4b2b0901253c55282024331d1fe1a8 + let a_2 := mload(0x94e0) + let var10 := q_pow5(a_2) + let var11 := mulmod(var9, var10, r) + let var12 := addmod(var8, var11, r) + let var13 := 0x333f8046ece5579cbd6872449c57f2703dfc8864cfadc06d587ff104a0d0c1f2 + let a_3 := mload(0x9500) + let var14 := q_pow5(a_3) + let var15 := mulmod(var13, var14, r) + let var16 := addmod(var12, var15, r) + let var17 := 0x412c98232b6ab8a47aa76ee814ef7ec6261987c9802f2cfc490e007951a60ca5 + let a_4 := mload(0x9520) + let var18 := q_pow5(a_4) + let var19 := mulmod(var17, var18, r) + let var20 := addmod(var16, var19, r) + let var21 := 0x53fded36d490ba6b05a5d10fd99ffe5456baec6a6a8753199d5ebdc33c99790e + let a_5 := mload(0x95a0) + let var22 := q_pow5(a_5) + let var23 := mulmod(var21, var22, r) + let var24 := addmod(var20, var23, r) + let var25 := 0x6ccb1c7d87f3c12a2bde4e68ac7f1e8b03481ba15d7f88f9a7f9b8310dd6d34 + let a_6 := mload(0x95c0) + let var26 := q_pow5(a_6) + let var27 := mulmod(var25, var26, r) + let var28 := addmod(var24, var27, r) + let var29 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 + let a_7 := mload(0x95e0) + let var30 := q_pow5(a_7) + let var31 := mulmod(var29, var30, r) + let var32 := addmod(var28, var31, r) + let var33 := addmod(mulmod(0, q_trash_tau, r), var32, r) + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) + let var34 := mulmod(a_1_next_1, var0, r) + let var35 := addmod(f_1, var34, r) + let var36 := 0x5b1fc262a28cbb8bf75d9b1a6edaa74591ec24cd9a209512213cec3a3c0f1a5d + let var37 := mulmod(var36, a_0, r) + let var38 := addmod(var35, var37, r) + let var39 := 0x4d0ea7f9c3fda06d9535b0fdafd8338bd47c2200b284fa71a325ff41ac358028 + let var40 := mulmod(var39, a_1, r) + let var41 := addmod(var38, var40, r) + let var42 := 0x26cc223e16f47c20e17cc6069605fa5a8af05ea4f6eb36029a641d23b818eb10 + let var43 := mulmod(var42, var10, r) + let var44 := addmod(var41, var43, r) + let var45 := 0x31e823a45e567484c1544e310c0fa5cd66547a8f0dde659ac61698c30e838d25 + let var46 := mulmod(var45, var14, r) + let var47 := addmod(var44, var46, r) + let var48 := 0x275a20361ea91992193920270d3e2d1f6361880ac0a439c64bef815d4469ba85 + let var49 := mulmod(var48, var18, r) + let var50 := addmod(var47, var49, r) + let var51 := 0x5f3a15bab4ce4097b1edc3a25002694b92395ce355a8a12fe557459d9633f701 + let var52 := mulmod(var51, var22, r) + let var53 := addmod(var50, var52, r) + let var54 := 0x301cf56f9b4577112cc4241cddf6484aaadedbf1bbd0f2351adf2e41c2fb2ecd + let var55 := mulmod(var54, var26, r) + let var56 := addmod(var53, var55, r) + let var57 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f + let var58 := mulmod(var57, var30, r) + let var59 := addmod(var56, var58, r) + let var60 := addmod(mulmod(var33, q_trash_tau, r), var59, r) + let f_2 := mload(0x9aa0) + let var61 := mulmod(a_3, var0, r) + let var62 := addmod(f_2, var61, r) + let var63 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 + let var64 := mulmod(var63, a_0, r) + let var65 := addmod(var62, var64, r) + let var66 := 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a + let var67 := mulmod(var66, a_1, r) + let var68 := addmod(var65, var67, r) + let var69 := 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db + let var70 := mulmod(var69, var10, r) + let var71 := addmod(var68, var70, r) + let var72 := addmod(mulmod(var60, q_trash_tau, r), var71, r) + let f_3 := mload(0x9ac0) + let var73 := mulmod(a_4, var0, r) + let var74 := addmod(f_3, var73, r) + let var75 := 0x222e83e70453dfee19b402e9fa8dfe2c4987b034d0be3ceb478b3022e97934c1 + let var76 := mulmod(var75, a_0, r) + let var77 := addmod(var74, var76, r) + let var78 := 0x26c2cc87f95726b28f33ca03409a460ec987cfe12adae32769e3565865d07191 + let var79 := mulmod(var78, a_1, r) + let var80 := addmod(var77, var79, r) + let var81 := 0x4382d0938a760120dd6cef8f3b90a0c38abae475e3d21e39365472b76d780272 + let var82 := mulmod(var81, var10, r) + let var83 := addmod(var80, var82, r) + let var84 := mulmod(var69, var14, r) + let var85 := addmod(var83, var84, r) + let var86 := addmod(mulmod(var72, q_trash_tau, r), var85, r) + let f_4 := mload(0x99c0) + let var87 := mulmod(a_5, var0, r) + let var88 := addmod(f_4, var87, r) + let var89 := 0x726df1506749848155630b86ae25a82b281ecd050fe3a52d85a181fa87202e4b + let var90 := mulmod(var89, a_0, r) + let var91 := addmod(var88, var90, r) + let var92 := 0x24822e1af9aa2887c912c87eb0f20bd332330e7e55cd784de67cb407a9f05520 + let var93 := mulmod(var92, a_1, r) + let var94 := addmod(var91, var93, r) + let var95 := 0x4e5280109d8f96b8bfb543a6b1af25fb56a9db616af85a90eedc558e3eb1ea29 + let var96 := mulmod(var95, var10, r) + let var97 := addmod(var94, var96, r) + let var98 := mulmod(var81, var14, r) + let var99 := addmod(var97, var98, r) + let var100 := mulmod(var69, var18, r) + let var101 := addmod(var99, var100, r) + let var102 := addmod(mulmod(var86, q_trash_tau, r), var101, r) + let f_5 := mload(0x99e0) + let var103 := mulmod(a_6, var0, r) + let var104 := addmod(f_5, var103, r) + let var105 := 0x2f5908b169c6cf1bd26dcf0f9e5105481f5164f3ece0582bf3098312167751a7 + let var106 := mulmod(var105, a_0, r) + let var107 := addmod(var104, var106, r) + let var108 := 0x23a6684b942d726a22e4d5b8d8ff83aeaa773f62600184efe5d033d7c7c6e827 + let var109 := mulmod(var108, a_1, r) + let var110 := addmod(var107, var109, r) + let var111 := 0x1981b4b33d6a9dab957b351d981d3323e65da39493af5bc01f7e8ffe17f98d4e + let var112 := mulmod(var111, var10, r) + let var113 := addmod(var110, var112, r) + let var114 := mulmod(var95, var14, r) + let var115 := addmod(var113, var114, r) + let var116 := mulmod(var81, var18, r) + let var117 := addmod(var115, var116, r) + let var118 := mulmod(var69, var22, r) + let var119 := addmod(var117, var118, r) + let var120 := addmod(mulmod(var102, q_trash_tau, r), var119, r) + let f_6 := mload(0x9a00) + let var121 := mulmod(a_7, var0, r) + let var122 := addmod(f_6, var121, r) + let var123 := 0x6d05a41959f539a7fc9ec0972ea1e3dbb6fc67dd51daf3414f7fbbb091c7274a + let var124 := mulmod(var123, a_0, r) + let var125 := addmod(var122, var124, r) + let var126 := 0x27e7119226c42a6d19c1541904b99ae40685511ed2e078964b74594d38340849 + let var127 := mulmod(var126, a_1, r) + let var128 := addmod(var125, var127, r) + let var129 := 0xd94c46a8456352aa44d7a885ab59e3a36664e6fb25e826f8a4cd79822f0533 + let var130 := mulmod(var129, var10, r) + let var131 := addmod(var128, var130, r) + let var132 := mulmod(var111, var14, r) + let var133 := addmod(var131, var132, r) + let var134 := mulmod(var95, var18, r) + let var135 := addmod(var133, var134, r) + let var136 := mulmod(var81, var22, r) + let var137 := addmod(var135, var136, r) + let var138 := mulmod(var69, var26, r) + let var139 := addmod(var137, var138, r) + let var140 := addmod(mulmod(var120, q_trash_tau, r), var139, r) + let f_7 := mload(0x9a20) + let a_2_next_1 := mload(0x9580) + let var141 := mulmod(a_2_next_1, var0, r) + let var142 := addmod(f_7, var141, r) + let var143 := 0x70d8f2a733a64d650faccc9b1c2a766a9544bb3ff1a11ee73cb43947ef386633 + let var144 := mulmod(var143, a_0, r) + let var145 := addmod(var142, var144, r) + let var146 := 0x40fa389feb2522bb934881ac9ed749aee2296502af592418c6b5675c0f560261 + let var147 := mulmod(var146, a_1, r) + let var148 := addmod(var145, var147, r) + let var149 := 0x1f61345b652161410c5e29f51e301ae56342af824bc110649393d2b911c50d3e + let var150 := mulmod(var149, var10, r) + let var151 := addmod(var148, var150, r) + let var152 := mulmod(var129, var14, r) + let var153 := addmod(var151, var152, r) + let var154 := mulmod(var111, var18, r) + let var155 := addmod(var153, var154, r) + let var156 := mulmod(var95, var22, r) + let var157 := addmod(var155, var156, r) + let var158 := mulmod(var81, var26, r) + let var159 := addmod(var157, var158, r) + let var160 := mulmod(var69, var30, r) + let var161 := addmod(var159, var160, r) + let var162 := addmod(mulmod(var140, q_trash_tau, r), var161, r) + let f_19 := mload(0x9b80) + let q_trash_one_minus_selector := addmod(1, sub(r, f_19), r) + let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0xa120), r) + let q_trash_eval := addmod(var162, sub(r, q_trash_scaled), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_trash_eval, r)) + } + } + // Finish selector buckets by applying the codegen-known tail + // from each selector's last identity to the end of the global + // y-batch. + // + // After this step, every selector bucket is aligned with the + // final global y position and can be multiplied by its fixed + // selector commitment in the linearized MSM. + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0600)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x05e0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0580)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x04c0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x80) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0460)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xa0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0400)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xc0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x03a0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xe0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0340)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0100) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x02e0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0120) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0280)), r)) + } + + // Fully evaluated identities are the constant-polynomial side + // of the linearization query. Rust subtracts that grouped + // scalar into expected_eval, so Solidity stores -nu_y(x). + let linearization_expected_eval := addmod(0, sub(r, mload(0xb280)), r) + mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) + pop(y) + } + + // Return the compact output frame. Halo2Verifier checks the magic, + // stores word 1 as the linearization expected eval, then expands + // selector buckets into the fused final PCS MSM. + mstore(QUOTIENT_OUTPUT_MPTR, QUOTIENT_MAGIC) + mstore(add(QUOTIENT_OUTPUT_MPTR, 0x20), mload(QUOTIENT_EVAL_MPTR)) + // Copy selector buckets from the generated absolute memory region + // into the compact external-call return frame. + for { let q_i := 0 } lt(q_i, 10) { q_i := add(q_i, 1) } { + mstore(add(QUOTIENT_OUTPUT_MPTR, add(0x40, shl(5, q_i))), mload(add(SELECTOR_ACC_MPTR, shl(5, q_i)))) + } + return(QUOTIENT_OUTPUT_MPTR, QUOTIENT_OUTPUT_LEN) + } + } +} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/ivc/Halo2Verifier.sol b/proofs/solidity-verifier/fixtures/ivc/Halo2Verifier.sol new file mode 100644 index 000000000..6f66ad7c7 --- /dev/null +++ b/proofs/solidity-verifier/fixtures/ivc/Halo2Verifier.sol @@ -0,0 +1,2282 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.24; + +/// @title Halo2 BLS12-381 KZG verifier. +/// @notice Circuit-specialized verifier for Midfall/midnight-proofs Halo2 +/// proofs rendered by this repository's Rust generator. +/// @dev This contract ports the verifier flow from +/// `midfall/proofs/src/plonk/verifier.rs`, the Keccak transcript comments from +/// `midfall/proofs/src/transcript/implementors.rs`, and the KZG multi-open +/// comments from `midfall/proofs/src/poly/kzg/mod.rs`. +/// @dev It is not a generic verifier. The proof layout, VK payload, quotient +/// identity program, memory layout, and optional quotient evaluator are all +/// generated for one `VerifyingKey>`. +/// +/// Halo2 KZG verifier for the BLS12-381 curve, midnight-proofs flavour. +/// +/// Differences vs the original BN254 / halo2 v0.4 template: +// +/// - BLS12-381 base field Fp is 381 bits and does not fit in a uint256. +/// Each Fp coord is encoded EIP-2537 padded (16 zero bytes + 48 bytes). +/// A G1 point is 128 bytes (4 words); a G2 point is 256 bytes (8). +/// - Calldata carries G1 commitments in uncompressed EIP-2537 padded +/// form (4 words = 128 bytes per point: x_hi, x_lo, y_hi, y_lo). The +/// proof bytes produced by midnight-proofs prover are repacked off +/// chain (compressed -> uncompressed) before being passed to +/// `verifyProof`. The verifier hashes the uncompressed 128-byte form into +/// the transcript verbatim, matching `Hashable for G1Projective::to_input`; +/// see `common_uncompressed_g1`. +/// - Transcript `common` absorbs raw inputs in order. `squeeze` computes one +/// Keccak digest, resets the transcript buffer to that digest, then samples +/// by interpreting the digest as a big-endian integer modulo r. +/// - Scalar inversion uses modexp(scalar, r-2, r). +/// - Constructors run deployment-time smoke tests for MCOPY and the EIP-2537 +/// precompiles using identity inputs. Compile with Solidity >=0.8.24 and +/// deploy only on chains/forks that support MCOPY and EIP-2537. +contract Halo2Verifier { + + /// @notice Verifying-key contract address authorized for this verifier. + /// @dev The runtime length and codehash are pinned by generated constants and checked at construction time. + address public immutable AUTHORIZED_VK; + // Expected VK runtime metadata. The deployed VK runtime is + // INVALID || payload, hence EXPECTED_VK_LENGTH is one byte longer than + // EXPECTED_VK_PAYLOAD_LENGTH. + uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 17024; + uint256 internal constant EXPECTED_VK_LENGTH = 17025; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x67bac137fa7e479c25b63324812752e4b6e13d9841d5bf83c322170bf91c0f88; + bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); + /// @notice Quotient evaluator contract authorized for split quotient reconstruction. + /// @dev The evaluator returns the linearization expected scalar and selector buckets; its runtime may be pinned by generated constants. + address public immutable AUTHORIZED_QUOTIENT; + // Expected split evaluator runtime metadata. It is checked at deployment + // and again immediately before each external quotient reconstruction. + uint256 internal constant EXPECTED_QUOTIENT_LENGTH = 9552; + uint256 internal constant EXPECTED_QUOTIENT_CODEHASH_WORD = 0x6a7ec82a9b0683783c459f4b43e924f8ceba256731bf2b9782ab5747e801ea2c; + bytes32 internal constant EXPECTED_QUOTIENT_CODEHASH = bytes32(EXPECTED_QUOTIENT_CODEHASH_WORD); + + // Solidity ABI calldata cursors. The generated verifier accepts exactly + // verifyProof(bytes proof, uint256[] instances), then parses the `proof` + // bytes itself in the same order as the Rust verifier transcript. + uint256 internal constant PROOF_LEN_CPTR = 0x44; + uint256 internal constant PROOF_CPTR = 0x64; + uint256 internal constant NUM_INSTANCE_CPTR = 0x1ec4; + uint256 internal constant INSTANCE_CPTR = 0x1ee4; + // First general-purpose memory words reserved by the generated verifier. + // RETURN_MPTR is a single word set to 1 on success. + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; + + // ---------------------------------------------------------------------- + // Verifying-key memory map. The VK header lives at VK_MPTR, followed + // by the quotient VM payload and commitments. After the full VK + // runtime comes the challenge slots (challenge_mptr..) and the + // per-stage scratch (theta_mptr..). + // ---------------------------------------------------------------------- + uint256 internal constant VK_MPTR = 0x3680; + uint256 internal constant VK_DIGEST_MPTR = 0x3680; + uint256 internal constant NUM_INSTANCES_MPTR = 0x36a0; + uint256 internal constant K_MPTR = 0x36c0; + uint256 internal constant N_INV_MPTR = 0x36e0; + uint256 internal constant OMEGA_MPTR = 0x3700; + uint256 internal constant OMEGA_INV_MPTR = 0x3720; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x3740; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x3760; + uint256 internal constant ACC_OFFSET_MPTR = 0x3780; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x37a0; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x37c0; + uint256 internal constant G1_BASE_MPTR = 0x37e0; + uint256 internal constant G2_BASE_MPTR = 0x3860; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x3960; + + uint256 internal constant CHALLENGE_MPTR = 0x7900; + + // Challenge layout. Squeeze order in midnight-proofs: + // user_phase challenges (variable count) + // theta -> beta, gamma -> trash_challenge -> y -> x -> + // x1, x2 -> x3 -> x4 + uint256 internal constant THETA_MPTR = 0x7900; + uint256 internal constant BETA_MPTR = 0x7920; + uint256 internal constant GAMMA_MPTR = 0x7940; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x7960; + uint256 internal constant Y_MPTR = 0x7980; + uint256 internal constant X_MPTR = 0x79a0; + uint256 internal constant X1_MPTR = 0x79c0; + uint256 internal constant X2_MPTR = 0x79e0; + uint256 internal constant X3_MPTR = 0x7a00; + uint256 internal constant X4_MPTR = 0x7a20; + + // Batch-open commitments live in 4-word EIP-2537 padded slots. + uint256 internal constant F_COM_MPTR = 0x7a40; + uint256 internal constant PI_MPTR = 0x7ac0; + + // Accumulator (KZG IVC). + uint256 internal constant ACC_LHS_MPTR = 0x7b40; + uint256 internal constant ACC_RHS_MPTR = 0x7bc0; + + // Lagrange / linearization scratch. + uint256 internal constant X_N_MPTR = 0x7c40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x7c60; + uint256 internal constant L_LAST_MPTR = 0x7c80; + uint256 internal constant L_BLIND_MPTR = 0x7ca0; + uint256 internal constant L_0_MPTR = 0x7cc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x7ce0; + // Legacy name: this is not h(x). It stores the expected opening + // scalar for the linearized commitment, i.e. the negated y-batched + // identity numerator reconstructed from the alleged evals at x. + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x7d00; + uint256 internal constant QUOTIENT_MPTR = 0x7d20; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x7dc0; + uint256 internal constant V_MPTR = 0x7de0; + uint256 internal constant FINAL_COM_MPTR = 0x7e00; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x7e80; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x7f00; // 4 words + + // Multi-prepare scratch (sized at codegen time). + uint256 internal constant ROT_POINTS_MPTR = 0x7f80; + uint256 internal constant X1_POWERS_MPTR = 0x8300; + // Q_COM materialization is currently fused into the final MSM scratch, + // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero + // reserved capacity until a future emitter starts writing Q_COM_MPTR. + uint256 internal constant Q_COM_MPTR = 0x8b20; + uint256 internal constant Q_EVAL_SET_MPTR = 0x8b20; + + // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals + // block of the proof; we keep it as a memory slot for symmetry. + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x9220; + + // Reserved 4-word slot for the G1 identity (point at infinity) in + // EIP-2537 padded form. EVM memory is zero-initialised, and we + // never write to this region, so the four `mload`s below produce + // 0,0,0,0 which is exactly the identity encoding the EIP-2537 + // ec_add / ec_mul precompiles accept. + uint256 internal constant G1_IDENTITY_MPTR = 0x9320; + + // Decoded polynomial-eval buffer (Optimisation H3). The off-chain + // Solidity proof shim rewrites proof scalars into canonical BE words, + // so `calldataload` gives the field element directly. The transcript- + // side `evaluations` loop range-checks and spills that value here so + // downstream eval references (gate evaluator + PCS q_eval Horner) + // become 3-gas `mload(...)` instead of calldata reads. + uint256 internal constant REVERSED_EVALS_MPTR = 0x9480; + uint256 internal constant SELECTOR_ACC_MPTR = 0xb140; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0xb140; + uint256 internal constant TRACE_U256_MPTR = 0xe340; + + // ---------------------------------------------------------------------- + // Per-category bases for EIP-2537 padded G1 commitments. The proof + // calldata carries 128-byte uncompressed/padded G1s after the off-chain + // proof shim repacks midnight-proofs' native compressed stream; this + // region stores the 4-word slots used by PCS / quotient-fold sections. + // + // Cumulative offsets (in words from `comms_mptr_base`): + // ADVICE_COMMS_MPTR_BASE + 0 + // LOOKUP_M_COMMS_MPTR_BASE + 4*total_advices + // PERM_Z_COMMS_MPTR_BASE + 4*total_advices + 4*num_lookups + // LOOKUP_HELPER_COMMS_MPTR_BASE + ... + 4*num_permutation_zs + // LOOKUP_Z_COMMS_MPTR_BASE + ... + 4*lookup_helper_chunks_total + // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups + // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans + // ---------------------------------------------------------------------- + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0xa140; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0xa8c0; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0xa9c0; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0xacc0; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0xadc0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0xaec0; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0xaf40; + + // BLS12-381 scalar-field modulus, used for transcript challenges and all + // Halo2 verifier arithmetic. + uint256 internal constant FR_MODULUS = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; + + // BLS12-381 Fp modulus minus one, split like an EIP-2537 coordinate: + // high word = 16 zero bytes || top 16 coordinate bytes, low word = + // bottom 32 coordinate bytes. + uint256 internal constant BLS_P_HI = 0x000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd7; + uint256 internal constant BLS_P_MINUS_ONE_LO = 0x64774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa; + // Packed public-accumulator sentinels for the shifted coordinate codec. + // The `_WITH_ID_FLAG` variant is used only for the first x-coordinate word. + uint256 internal constant BLS_P_MINUS_ONE_PACKED_0 = 0x00000000f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa; + uint256 internal constant BLS_P_MINUS_ONE_PACKED_0_WITH_ID_FLAG = 0x00000000f38512bf6730d2a0f6b0f6241eabfffeb153ffffbafeffffffffaaaa; + uint256 internal constant BLS_P_MINUS_ONE_PACKED_1 = 0x0000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84; + + /// @notice Smoke-check the Cancun/EIP-2537 runtime features required by the verifier. + /// @dev Exercises MCOPY and identity EIP-2537 inputs to catch incompatible chain/fork configurations at deployment. + function require_eip2537_precompiles() private view { + assembly ("memory-safe") { + // Scratch is reused for every runtime-prerequisite probe. + let scratch := 0x1000 + + // MCOPY must be available because the verifier uses it for + // proof-time point/scratch staging. Execute the opcode here so a + // non-Cancun fork fails during deployment instead of later proofs. + mstore(scratch, 0x1234) + mcopy(add(scratch, 0x20), scratch, 0x20) + if iszero(eq(mload(add(scratch, 0x20)), 0x1234)) { revert(0, 0) } + + // Start the EIP-2537 probes with the identity encoding for G1/G2: + // all-zero padded words. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + + // G1ADD(identity, identity) -> identity, 128-byte return. + // This catches chains where the precompile is missing or returns a + // non-standard success shape. + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if or(or(mload(scratch), mload(add(scratch, 0x20))), or(mload(add(scratch, 0x40)), mload(add(scratch, 0x60)))) { + revert(0, 0) + } + + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + + // Worst-case generated G1MSM with all identity/zero terms -> + // identity, 128-byte return. This exercises the largest MSM input + // length rendered by this verifier instead of only a one-pair + // smoke call. + let msm_scratch := 0xb140 + for { let off := 0 } lt(off, 0x30c0) { off := add(off, 0x20) } { + mstore(add(msm_scratch, off), 0) + } + // The production verifier uses G1MSM both for commitments and as + // the subgroup validator for absorbed proof points. + if iszero(staticcall(gas(), 0x0c, msm_scratch, 0x30c0, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if or(or(mload(scratch), mload(add(scratch, 0x20))), or(mload(add(scratch, 0x40)), mload(add(scratch, 0x60)))) { + revert(0, 0) + } + + // PAIRING_CHECK([(identity_g1, identity_g2), (identity_g1, identity_g2)]) + // -> true, 32-byte return. This matches the runtime two-pair KZG + // pairing input size and catches absent pairing precompiles, + // short return data, and obviously incompatible semantics. + if iszero(staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x20)) { revert(0, 0) } + if iszero(eq(mload(scratch), 1)) { revert(0, 0) } + } + } + + + /// @notice Create a verifier pinned to a verifying key and quotient evaluator. + /// @dev Checks MCOPY/EIP-2537 availability and verifies both dependency runtimes before storing their addresses. + /// @param authorizedVk Address of the generated `Halo2VerifyingKey` runtime. + /// @param authorizedQuotient Address of the generated `Halo2QuotientEvaluator` runtime. + constructor(address authorizedVk, address authorizedQuotient) { + // Verifier correctness depends on chain support for MCOPY and the + // BLS12-381 precompiles; fail deployment before pinning dependencies. + require_eip2537_precompiles(); + // Pin the generated VK runtime exactly. The verifier later repeats the + // codehash/length check before copying the VK payload for a proof. + require( + authorizedVk.code.length == EXPECTED_VK_LENGTH + && authorizedVk.codehash == EXPECTED_VK_CODEHASH, + "invalid vk" + ); + // The split evaluator contains generated verifier logic, not a generic + // library. Pin it with the same strictness as the verifying key. + require( + authorizedQuotient.code.length == EXPECTED_QUOTIENT_LENGTH + && authorizedQuotient.codehash == EXPECTED_QUOTIENT_CODEHASH, + "invalid quotient" + ); + // Store the already-validated dependency addresses for proof-time + // memory loading and quotient reconstruction. + AUTHORIZED_VK = authorizedVk; + AUTHORIZED_QUOTIENT = authorizedQuotient; + } + + /// @notice Verify a Halo2/Midfall proof for the generated verifying key. + /// @dev This checks only that `proof` verifies for the supplied public + /// `instances` under this pinned VK/protocol. Application contracts must + /// bind the meaning of those instances separately: state roots, program + /// identifiers, expected IVC outputs, chain/domain separation, and any + /// protocol-specific authorization are outside this raw verifier ABI. + /// @dev Production renders are success-or-revert: accepted proofs return + /// `true`, while malformed calldata, invalid proof material, failed + /// precompiles, or mismatched pinned dependency code revert. Trace and gas + /// renders keep the same failure policy. + /// @dev The generated verifier uses absolute Yul memory addresses instead + /// of Solidity's free-memory pointer, but generated scratch starts at + /// `0x80` so Solidity's reserved memory prefix is preserved. The main + /// assembly block remains terminal: accepted proofs return from assembly + /// and all rejected inputs revert. Do not inline this body into Solidity + /// code that continues executing after verification without reviewing the + /// memory strategy; see `docs/MEMORY_LAYOUT.md`. + /// @param proof Solidity-facing proof bytes, with G1 elements repacked into EIP-2537 padded uncompressed form. + /// @param instances Public instance scalars encoded as canonical BLS12-381 scalar-field words. + /// @return Always `true` for accepted proofs; invalid proofs revert instead of returning `false`. + function verifyProof( + bytes calldata proof, + uint256[] calldata instances + ) external view returns (bool) { + // Cheap ABI-shape guard before any generated memory work: + // - proof head must point at the bytes payload; + // - instances head must point at the generated instance array. + // + // The verifier below is a hand-rolled calldata parser. Failing here + // keeps malformed dynamic-argument layouts from being interpreted as a + // valid Midfall proof stream. + assembly ("memory-safe") { + if iszero(and(eq(calldataload(0x04), 0x40), eq(calldataload(0x24), sub(NUM_INSTANCE_CPTR, 0x04)))) { + revert(0, 0) + } + } + // Non-embedded renders pin the VK by address and codehash. The Yul + // loader rechecks the runtime before every proof and copies the + // INVALID-prefixed payload into VK_MPTR. + address vk = AUTHORIZED_VK; + // Split quotient renders delegate the scalar-side identity numerator + // reconstruction to a separately deployed generated evaluator. + address quotientEvaluator = AUTHORIZED_QUOTIENT; + assembly ("memory-safe") { + // This block owns the call-frame memory and remains terminal. + // Generated scratch starts at TRANSCRIPT_MPTR (0x80), preserving + // Solidity's reserved scratch, free-memory-pointer, and zero-slot + // words. See docs/MEMORY_LAYOUT.md. + // =============================================================== + // Helpers: modexp, transcript, EIP-2537 calls + // =============================================================== + + // Inverse of a Fr scalar via modexp(x, r-2, r). The verifier + // calls this only after transcript absorption is complete, so it + // reuses the dead transcript buffer just below VK_MPTR instead of + // a fixed post-VK address that can collide with live PCS scratch + // when the VK payload becomes smaller. + function scalar_inv(x) -> inv { + // Zero has no multiplicative inverse in Fr; callers rely on a + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } + if iszero(x) { revert(0, 0) } + let p := 0x3580 + // EIP-198 modexp frame: + // [base_len, exp_len, mod_len, base, exponent, modulus] + mstore(add(p, 0x00), 0x20) // base len + mstore(add(p, 0x20), 0x20) // exp len + mstore(add(p, 0x40), 0x20) // mod len + mstore(add(p, 0x60), x) + mstore(add(p, 0x80), sub(FR_MODULUS, 2)) + mstore(add(p, 0xa0), FR_MODULUS) + if iszero(staticcall(gas(), 0x05, p, 0xc0, p, 0x20)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x20)) { revert(0, 0) } + inv := mload(p) + } + + // ---------- Streaming Keccak256 transcript helpers ---------- + // + // The transcript buffer lives at + // memory[TRANSCRIPT_MPTR..buf_len). On verifier entry it starts + // empty. Each common(input) appends raw bytes. squeeze_*(buf_len) + // computes one Keccak digest, reseeds the buffer with that + // 32-byte digest, and samples a Fq element as + // uint256(digest_be) mod r. + + function transcript_init() -> buf_len { + // Empty transcript buffer starts exactly at TRANSCRIPT_MPTR. + buf_len := TRANSCRIPT_MPTR + } + + // Append one 32-byte big-endian field/transcript word at the + // current end of the transcript buffer. + function common_word(buf_len, word) -> ret { + mstore(buf_len, word) + ret := add(buf_len, 32) + } + + // Absorb a BLS12-381 G1 point in EIP-2537 padded + // uncompressed form (4 calldata words = 128 bytes: + // x_hi || x_lo || y_hi || y_lo, each coord = 16 zero + // pad bytes + 48 big-endian field bytes) into the + // transcript buffer at `buf_len`. + // + // Matches the patched `Hashable for + // midnight_curves::G1Projective::to_input` in + // midnight-proofs, which now emits the same 128-byte form + // (`midfall/proofs/src/transcript/implementors.rs`). The + // previous emitter hashed the 48-byte ZCash compressed + // encoding instead and ran a 384-bit `lex(y) > lex(p − y)` + // ladder + identity flag fixup to derive the sign bit on + // the fly; switching to the uncompressed form drops that + // ladder entirely. + // + // Canonicality: reject non-zero bytes in the top 16 bytes + // of each `_hi` calldata word and reject coordinates + // outside Fp. Normalizing those bytes before hashing would + // make multiple calldata encodings share one transcript. + // + // This helper does not run an independent curve/subgroup + // check. Instead, ProtocolPlan::validate rejects generated + // plans where an absorbed proof commitment would not later be + // consumed by an EIP-2537 G1MSM or pairing path, and those + // precompiles perform the curve/subgroup validation. + // + // The point's uncompressed form remains in calldata; the + // call site is responsible for `calldatacopy`-ing it into + // memory afterwards if it needs the on-curve coordinates. + function common_uncompressed_g1(buf_len, cptr) -> ret { + let x_hi_word := calldataload(cptr) + let x_lo := calldataload(add(cptr, 0x20)) + let y_hi_word := calldataload(add(cptr, 0x40)) + let y_lo := calldataload(add(cptr, 0x60)) + if shr(128, x_hi_word) { revert(0, 0) } + if shr(128, y_hi_word) { revert(0, 0) } + + let x_hi := and(x_hi_word, 0xffffffffffffffffffffffffffffffff) + let y_hi := and(y_hi_word, 0xffffffffffffffffffffffffffffffff) + if iszero(or(lt(x_hi, BLS_P_HI), and(eq(x_hi, BLS_P_HI), iszero(gt(x_lo, BLS_P_MINUS_ONE_LO))))) { + revert(0, 0) + } + if iszero(or(lt(y_hi, BLS_P_HI), and(eq(y_hi, BLS_P_HI), iszero(gt(y_lo, BLS_P_MINUS_ONE_LO))))) { + revert(0, 0) + } + + // Memcpy the 4 calldata words (128 bytes) verbatim + // into the keccak buffer. + calldatacopy(buf_len, cptr, 0x80) + ret := add(buf_len, 0x80) + } + + // One Keccak finalization + reseed. Returns the new buffer + // cursor (= TRANSCRIPT_MPTR + 32) and stores the squeezed Fq at + // `mptr`. + function squeeze_to(buf_len, mptr) -> ret { + let h0 := keccak256(TRANSCRIPT_MPTR, sub(buf_len, TRANSCRIPT_MPTR)) + // Reseed: write the 32-byte digest at start of buffer. + mstore(TRANSCRIPT_MPTR, h0) + let r := FR_MODULUS + // Sample Fq as uint256(keccak_digest_be) mod r. + mstore(mptr, mod(h0, r)) + ret := add(TRANSCRIPT_MPTR, 32) + } + + // ---------- EC primitives (EIP-2537 wrappers) ---------- + // + // These mirror the BN254 helpers but operate on 4-word G1 + // points. They use planned memory windows above Solidity's + // reserved prefix; the streaming transcript buffer is no longer + // needed once all challenges are squeezed. + + // Invert a contiguous run of Fr words in-place using Montgomery's + // batch inversion trick: + // 1. write prefix products to scratch; + // 2. invert the total product once with modexp; + // 3. walk backward to recover each individual inverse. + // + // The function returns a boolean instead of reverting so callers + // can combine it with other `success` plumbing until a section + // boundary decides whether to fail closed. + function batch_invert(success, mptr_start, mptr_end, scratch_mptr, r) -> ret { + ret := success + if iszero(ret) { leave } + // Memory ranges must be forward and word-aligned by + // construction; a reversed range is always a codegen error. + if lt(mptr_end, mptr_start) { + ret := 0 + leave + } + + let count_bytes := sub(mptr_end, mptr_start) + // Empty batch is valid and leaves memory untouched. + if iszero(count_bytes) { leave } + + // Fast path for a single denominator: avoid prefix scratch and + // just run one modexp inverse in place. + if eq(count_bytes, 0x20) { + let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } + if iszero(x) { + ret := 0 + leave + } + + let single_scratch := scratch_mptr + mstore(add(single_scratch, 0x00), 0x20) + mstore(add(single_scratch, 0x20), 0x20) + mstore(add(single_scratch, 0x40), 0x20) + mstore(add(single_scratch, 0x60), x) + mstore(add(single_scratch, 0x80), sub(r, 2)) + mstore(add(single_scratch, 0xa0), r) + ret := staticcall(gas(), 0x05, single_scratch, 0xc0, single_scratch, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + if ret { mstore(mptr_start, mload(single_scratch)) } + leave + } + + // Forward pass: scratch stores prefix products up to, but not + // including, the final element. `gp` becomes the total product. + let gp_mptr := scratch_mptr + let gp := mload(mptr_start) + let mptr := add(mptr_start, 0x20) + for {} lt(mptr, sub(mptr_end, 0x20)) {} { + gp := mulmod(gp, mload(mptr), r) + mstore(gp_mptr, gp) + mptr := add(mptr, 0x20) + gp_mptr := add(gp_mptr, 0x20) + } + gp := mulmod(gp, mload(mptr), r) + // A zero total product means at least one denominator was + // zero, so no batch inverse exists. + if iszero(gp) { + ret := 0 + leave + } + + // Invert the total product once. + mstore(add(gp_mptr, 0x00), 0x20) + mstore(add(gp_mptr, 0x20), 0x20) + mstore(add(gp_mptr, 0x40), 0x20) + mstore(add(gp_mptr, 0x60), gp) + mstore(add(gp_mptr, 0x80), sub(r, 2)) + mstore(add(gp_mptr, 0xa0), r) + ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } + let all_inv := mload(gp_mptr) + + // Backward pass: derive each inverse from the inverted total + // product and the saved prefix products. + let first_mptr := mptr_start + let second_mptr := add(first_mptr, 0x20) + gp_mptr := sub(gp_mptr, 0x20) + for {} lt(second_mptr, mptr) {} { + let inv := mulmod(all_inv, mload(gp_mptr), r) + all_inv := mulmod(all_inv, mload(mptr), r) + mstore(mptr, inv) + mptr := sub(mptr, 0x20) + gp_mptr := sub(gp_mptr, 0x20) + } + let inv_first := mulmod(all_inv, mload(second_mptr), r) + let inv_second := mulmod(all_inv, mload(first_mptr), r) + mstore(first_mptr, inv_first) + mstore(second_mptr, inv_second) + } + + // Final EIP-2537 pairing wrapper. `lhs_mptr` and `rhs_mptr` are + // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. + function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { + ret := success + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } + // Lay out two (G1, G2) pairs at scratch..scratch+0x300: + // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] + // Cancun MCOPY (3 + 3·words gas) replaces what used to + // be a 4-step mstore chain for each G1 (~60 gas) and an + // 8-iter mstore loop for each G2 (~240 gas). Net saving + // here is ~500 gas per ec_pairing call. + let scratch := 0x0320 + mcopy(scratch, lhs_mptr, 0x80) + mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) + mcopy(add(scratch, 0x180), rhs_mptr, 0x80) + mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) + ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) + if iszero(ret) { revert(0, 0) } + ret := 1 + } + + // ---------- IVC accumulator public-input decoding ---------- + // + // `AssignedForeignPoint` exposes each base-field coordinate + // through `AssignedField::as_public_input`: seven radix-2^56 limbs of + // (coord - 1) are packed four-at-a-time into native field elements. + // The x coordinate's first packed word carries the identity flag by + // adding one raw radix base. Rebuild EIP-2537 padded + // (x_hi, x_lo, y_hi, y_lo) words from that encoding. + // + // Public-input layout for one coordinate: + // word 0: limb_0 | limb_1 << bits | ... up to limbs_per_word + // word 1: next limbs, if any + // + // The limbs are little-endian in the represented integer even + // though calldata words are loaded as big 256-bit values. The loop + // below extracts each limb by shifting inside the packed word and + // reconstructs the full coordinate into the two-word EIP-2537 + // representation expected by the BLS12-381 precompiles. + function load_acc_coord_shifted(src, bits, n, base, limbs_per_word, first_adjust) -> hi, lo { + // Mask for one radix limb, e.g. 2^56 - 1 for the current + // BLS12-381 self-emulation parameters. + let mask := sub(base, 1) + for { let i := 0 } lt(i, n) { i := add(i, 1) } { + // Limb words are little-endian packed inside each Fr + // public input. `first_adjust` removes the identity flag + // base from the first x word when present. + let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { + packed := sub(packed, first_adjust) + } + // Select limb i from its packed field word. The mod/div + // pair maps a limb index to an intra-word limb slot and + // the calldata word containing it. + let limb := and(shr(mul(mod(i, limbs_per_word), bits), packed), mask) + + let shift := mul(i, bits) + // Split the reconstructed 384-bit coordinate into the + // EIP-2537 high/low words expected by the precompiles. + if lt(shift, 256) { + lo := add(lo, shl(shift, limb)) + if gt(add(shift, bits), 256) { + // A limb can straddle the 256-bit low/high split. + // Move the overflow bits into hi. + hi := add(hi, shr(sub(256, shift), limb)) + } + } + if iszero(lt(shift, 256)) { + // Once shift >= 256 the whole limb belongs to hi. + hi := add(hi, shl(sub(shift, 256), limb)) + } + } + } + + // The shifted coordinate codec represents zero as p-1 before the + // final +1 below, so keep this sentinel explicit. + function is_bls_p_minus_one(hi, lo) -> yes { + yes := and(eq(hi, BLS_P_HI), eq(lo, BLS_P_MINUS_ONE_LO)) + } + + // Canonical encoded accumulator identity: + // x = p-1 plus the identity flag in the first packed word, + // y = p-1 with no identity flag. + // It decodes to the EIP-2537 point-at-infinity slot (all zeros). + // + // This fast path is deliberately stricter than "decodes to zero": + // the point at infinity has exactly one accepted public-input + // encoding. Non-canonical zero-like encodings are rejected later. + function is_acc_encoded_identity(src) -> yes { + yes := and( + and( + eq(calldataload(src), BLS_P_MINUS_ONE_PACKED_0_WITH_ID_FLAG), + eq(calldataload(add(src, 0x20)), BLS_P_MINUS_ONE_PACKED_1) + ), + and( + eq(calldataload(add(src, 0x40)), BLS_P_MINUS_ONE_PACKED_0), + eq(calldataload(add(src, 0x60)), BLS_P_MINUS_ONE_PACKED_1) + ) + ) + } + + // Reject unused high bits in the packed public-input words. This + // makes each accumulator point encoding canonical before it reaches + // the precompile-based curve/subgroup validation. + function check_acc_coord_packing(src, bits, n, limbs_per_word) -> ok { + ok := 1 + // Number of packed native-field public-input words occupied by + // one coordinate. + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + for { let word_idx := 0 } lt(word_idx, coord_words) { word_idx := add(word_idx, 1) } { + // The final word may contain fewer than limbs_per_word + // limbs. Any unused high bits must be zero, otherwise the + // same coordinate would have multiple calldata encodings. + let remaining := sub(n, mul(word_idx, limbs_per_word)) + let limbs_in_word := limbs_per_word + if lt(remaining, limbs_per_word) { + limbs_in_word := remaining + } + let used_bits := mul(limbs_in_word, bits) + if lt(used_bits, 256) { + // shl(used_bits, 1) == 2^used_bits. The packed word + // must be strictly less than that bound. + ok := and(ok, lt(calldataload(add(src, mul(word_idx, 0x20))), shl(used_bits, 1))) + } + } + } + + // Decode one shifted coordinate. `allow_id` is true only for x, + // because the identity flag lives in x's first packed word. + function load_acc_coord(src, allow_id, bits, n, base, limbs_per_word) -> ok, hi, lo, is_id { + ok := check_acc_coord_packing(src, bits, n, limbs_per_word) + if and(allow_id, iszero(lt(calldataload(src), base))) { + // Probe the x identity flag by removing one radix base and + // checking whether the adjusted coordinate is p-1. + // + // `calldataload(src) >= base` is a cheap prefilter: only x + // can carry this flag, and adding one radix base must make + // the first packed word at least base. + let adj_hi, adj_lo := load_acc_coord_shifted(src, bits, n, base, limbs_per_word, base) + is_id := is_bls_p_minus_one(adj_hi, adj_lo) + } + + // Decode again with the identity adjustment applied only when + // the canonical identity flag was actually detected. + hi, lo := load_acc_coord_shifted(src, bits, n, base, limbs_per_word, mul(is_id, base)) + ok := and( + ok, + // Coordinate must be in the BLS12-381 base field, i.e. + // <= p - 1 in split hi/lo form. + or(lt(hi, BLS_P_HI), and(eq(hi, BLS_P_HI), iszero(gt(lo, BLS_P_MINUS_ONE_LO)))) + ) + + let was_p_minus_one := is_bls_p_minus_one(hi, lo) + if was_p_minus_one { + // Shifted encoding maps p-1 back to zero. + hi := 0 + lo := 0 + } + if iszero(was_p_minus_one) { + // All other coordinates are encoded as coord - 1, so add + // one back with carry into the high word. + let next_lo := add(lo, 1) + hi := add(hi, lt(next_lo, lo)) + lo := next_lo + } + + // EIP-2537 pads each 48-byte Fp coordinate to 64 bytes, + // so the high word must fit in its low 128 bits. + // This also catches impossible reconstructions above 384 bits. + ok := and(ok, lt(hi, shl(128, 1))) + } + + // Decode a public accumulator point into an EIP-2537 4-word G1 + // slot. Non-identity points are curve/subgroup checked later by + // routing them through G1MSM. + function load_acc_point(dst, src, bits, n, base) -> ok, is_id { + // Prefer the canonical all-coordinate identity encoding before + // attempting coordinate-level shifted decoding. This accepts + // the point at infinity only in the exact form generated by the + // circuit's public-input codec. + is_id := is_acc_encoded_identity(src) + if is_id { + ok := 1 + // EIP-2537 encodes G1 identity as four zero words: + // x_hi = x_lo = y_hi = y_lo = 0. + mstore(dst, 0) + mstore(add(dst, 0x20), 0) + mstore(add(dst, 0x40), 0) + mstore(add(dst, 0x60), 0) + } + if iszero(is_id) { + // x occupies coord_words packed public-input words; y + // starts immediately after x. + let limbs_per_word := 4 + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + // Only x may carry the identity flag. y must decode as a + // normal shifted coordinate. + let x_ok, x_hi, x_lo, x_is_id := load_acc_coord(src, 1, bits, n, base, limbs_per_word) + let y_ok, y_hi, y_lo, y_id := load_acc_coord( + add(src, mul(coord_words, 0x20)), + 0, + bits, + n, + base, + limbs_per_word + ) + // y_id is always zero because allow_id was false, but the + // tuple shape is shared with x decoding. + pop(y_id) + ok := and(x_ok, y_ok) + is_id := x_is_id + + if is_id { + // If x carried the identity flag, both decoded + // coordinates must be zero after shifting. Any other y + // value would be a malformed infinity encoding. + ok := and(ok, iszero(or(or(x_hi, x_lo), or(y_hi, y_lo)))) + mstore(dst, 0) + mstore(add(dst, 0x20), 0) + mstore(add(dst, 0x40), 0) + mstore(add(dst, 0x60), 0) + } + if iszero(is_id) { + // The coordinate codec maps encoded p-1 to decoded + // zero. EIP-2537 reserves affine (0,0) for the point + // at infinity, so a decoded infinity is only valid + // when the canonical accumulator identity encoding + // was used above. + let decoded_zero := iszero(or(or(x_hi, x_lo), or(y_hi, y_lo))) + ok := and(ok, iszero(decoded_zero)) + // Store the affine point in the exact precompile input + // layout: x_hi, x_lo, y_hi, y_lo. + mstore(dst, x_hi) + mstore(add(dst, 0x20), x_lo) + mstore(add(dst, 0x40), y_hi) + mstore(add(dst, 0x60), y_lo) + } + } + } + // Validate and prepare the public accumulator equation before the + // main transcript starts. This fails malformed public inputs early + // and writes ACC_LHS_MPTR / ACC_RHS_MPTR for final pairing batching. + // + // The accumulator public input represents an equality of two G1 + // commitments used by the recursive KZG accumulator. This helper: + // 1. decodes carried public G1 points from shifted limbs; + // 2. forces every decoded point through EIP-2537 G1MSM so the + // precompile validates curve/subgroup membership; + // 3. folds the RHS carried point and fixed-base scalar tail into + // ACC_RHS_MPTR, leaving ACC_LHS_MPTR / ACC_RHS_MPTR ready for + // randomized batching in FinalPairing.yul. + function validate_public_accumulator(success, r) -> out { + out := success + let bits := 56 + let n := 7 + // The BLS12-381 self-emulation currently exposes Fp + // coordinates as 7 radix-2^56 limbs. + let limb_base := shl(bits, 1) + let limbs_per_word := 4 + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + // acc_offset is generated from the VK/protocol shape and + // points into the ABI `instances` array. + let acc_instance_ptr := add(INSTANCE_CPTR, 0x80) + + // LHS layout: point limbs (x,y), then either an explicit + // scalar word or an implicit unit scalar for already-collapsed + // point-pair public inputs. + // The scalar pointer is computed unconditionally; the rendered + // branch below decides whether to read it or use scalar 1. + let lhs_scalar_ptr := add(acc_instance_ptr, mul(mul(2, coord_words), 0x20)) + let lhs_ok, lhs_is_id := load_acc_point(ACC_LHS_MPTR, acc_instance_ptr, bits, n, limb_base) + out := and(out, lhs_ok) + // Shared scratch for one-pair LHS validation and the later + // variable-length RHS MSM. + let acc_scratch := 0xb140 + { + // Carried-scalar layout: the circuit exposes the scalar + // that multiplies the carried LHS point. + let lhs_scalar := calldataload(lhs_scalar_ptr) + // Canonicality is enforced here rather than relying on the + // later instance-absorption loop: G1MSM reduces scalars + // mod r implicitly, so s and s+r would be indistinguishable + // inside this helper. + out := and(out, lt(lhs_scalar, r)) + // Identity status is useful for decoding checks above, but + // validation still goes through G1MSM for all points. + pop(lhs_is_id) + // Always route the decoded carried point through G1MSM, + // even for identity points and zero/one scalars. The + // precompile is the on-curve/subgroup validator for this + // public-input point; skipping it would let a malformed + // non-identity point hide behind scalar 0. + mcopy(acc_scratch, ACC_LHS_MPTR, 0x80) + mstore(add(acc_scratch, 0x80), lhs_scalar) + if out { + // Single-pair MSM output overwrites ACC_LHS_MPTR with + // lhs_scalar * decoded_lhs. If lhs_scalar is one, this + // is also a curve/subgroup validation round-trip. + out := staticcall(gas(), 0x0c, acc_scratch, 0xa0, ACC_LHS_MPTR, 0x80) + out := and(out, eq(returndatasize(), 0x80)) + } + } + // RHS layout for this generated verifier is fully collapsed: + // point limbs (x,y), scalar. There is no fixed-base scalar + // tail; fixed-base contributions were already folded into + // ACC_RHS by the circuit/native accumulator construction. + let rhs_instance_ptr := add(lhs_scalar_ptr, 0x20) + // RHS scalar, when present, immediately follows the RHS point + // limbs. The fixed-base scalar tail starts after it. + let rhs_scalar_ptr := add(rhs_instance_ptr, mul(mul(2, coord_words), 0x20)) + let rhs_ok, rhs_is_id := load_acc_point(ACC_RHS_MPTR, rhs_instance_ptr, bits, n, limb_base) + out := and(out, rhs_ok) + // acc_pair_ptr appends (G1, scalar) pairs into acc_scratch for + // one final RHS MSM. + let acc_pair_ptr := acc_scratch + { + // Explicit carried RHS scalar. + let rhs_scalar := calldataload(rhs_scalar_ptr) + out := and(out, lt(rhs_scalar, r)) + pop(rhs_is_id) + // Keep the carried RHS point in the MSM input even when + // it is encoded as identity or has scalar 0/1, so EIP-2537 + // validates every decoded public accumulator point before + // it can affect, or be erased from, the pairing batch. + mcopy(acc_pair_ptr, ACC_RHS_MPTR, 0x80) + mstore(add(acc_pair_ptr, 0x80), rhs_scalar) + // Move to the next (G1, scalar) pair slot. + acc_pair_ptr := add(acc_pair_ptr, 0xa0) + } + // Total byte length of the appended RHS MSM input pairs. This + // is at least one pair because the carried RHS point is always + // appended; keep the guard for synthetic render configurations. + let acc_msm_len := sub(acc_pair_ptr, acc_scratch) + if acc_msm_len { + // Fold the carried RHS point and any generated fixed-base + // tail into ACC_RHS_MPTR. The later final pairing block + // randomizes this equation together with the KZG pairing. + if out { + // Output overwrites ACC_RHS_MPTR with: + // rhs_scalar * carried_rhs + // + sum_i fixed_scalar_i * fixed_base_i + // + // The precompile also validates every nonzero fixed + // base embedded by codegen and the carried RHS point. + out := staticcall( + gas(), + 0x0c, + acc_scratch, + acc_msm_len, + ACC_RHS_MPTR, + 0x80 + ) + out := and(out, eq(returndatasize(), 0x80)) + } + } + // The caller checks `out` and reverts before transcript work if + // any decode, canonicality, or precompile validation failed. + } + + + + let r := FR_MODULUS + let success := true + + + + // =============================================================== + // VK loading: either bake in the embedded VK bytes or fetch + // them from the linked AUTHORIZED_VK contract. + // + // This is the first verifier phase after helper definitions. Its + // job is to make the generated VK payload available at VK_MPTR in + // one canonical memory layout, regardless of whether this render + // embeds the VK directly or links a separate Halo2VerifyingKey + // contract. + // + // Later template partials treat VK_MPTR as already populated with: + // - header words: vk_digest, domain data, accumulator metadata; + // - BLS12-381 base points used by the final pairing; + // - compact quotient VM constants/program bytes, when enabled; + // - fixed and permutation commitments in 4-word G1 slots. + // =============================================================== + { + // Re-check the pinned VK dependency on every proof. The + // constructor check catches normal deployment mistakes, while + // this fresh check hardens forks or same-transaction edge + // cases where code at the authorized address could differ + // from the runtime originally pinned by this verifier. + // + // EXPECTED_VK_LENGTH includes the leading INVALID byte in the + // Halo2VerifyingKey runtime. EXPECTED_VK_CODEHASH_WORD is the + // full runtime hash, not only the payload hash. + if iszero(and( + eq(extcodesize(vk), EXPECTED_VK_LENGTH), + eq(extcodehash(vk), EXPECTED_VK_CODEHASH_WORD) + )) { revert(0, 0) } + // Runtime byte 0 is INVALID so direct calls cannot execute the + // payload. Copy from byte 1 into VK_MPTR to reconstruct the + // exact payload layout used by the embedded branch. + extcodecopy(vk, VK_MPTR, 0x01, EXPECTED_VK_PAYLOAD_LENGTH) + + // Cross-check loaded VK header words against the verifier + // constants used by later parser, domain, and accumulator + // paths. Codehash pinning protects the external VK address; + // these checks catch generator drift before calldata parsing + // chooses a stale schema. + success := and(success, eq(mload(NUM_INSTANCES_MPTR), 14)) + success := and(success, eq(mload(K_MPTR), 20)) + success := and(success, eq(mload(HAS_ACCUMULATOR_MPTR), 1)) + success := and(success, eq(mload(ACC_OFFSET_MPTR), 4)) + success := and(success, eq(mload(NUM_ACC_LIMBS_MPTR), 7)) + success := and(success, eq(mload(NUM_ACC_LIMB_BITS_MPTR), 56)) + if iszero(success) { revert(0, 0) } + // + // The checks below validate the dynamic ABI envelope before the + // transcript parser starts walking raw calldata: + // - proof bytes length equals the generated proof layout; + // - instance array length equals the generated public input + // count; + // - total calldata length has no missing or trailing words. + // + // `success` is folded through `and` for consistency with later + // sections, then immediately enforced at the end of this block. + // A failure here means the verifier is not looking at the proof + // shape it was generated to parse. + success := and(success, eq(0x1e60, calldataload(PROOF_LEN_CPTR))) + success := and(success, eq(14, calldataload(NUM_INSTANCE_CPTR))) + // Calldata must contain exactly the ABI selector, proof bytes, + // instance-array length, and generated number of instance + // words. Any trailing bytes fail closed. + success := and( + success, + eq(calldatasize(), add(INSTANCE_CPTR, 0x01c0)) + ) + // Stop before any transcript absorption if the ABI/proof shape + // is not exactly the generated one. + if iszero(success) { revert(0, 0) } + } + // Fail malformed accumulator public inputs before transcript, + // quotient, PCS, and final pairing work. The late accumulator block + // only batches these already-validated G1 outputs into the final + // pairing equation. + // + // Accumulator validation decodes shifted public-input limbs into + // EIP-2537 G1 slots, checks canonical encodings, and routes points + // through G1MSM for curve/subgroup validation. Doing it here means + // invalid accumulator public inputs cannot influence transcript + // challenge derivation or waste gas in later quotient/PCS work. + // validate_public_accumulator returns a boolean to share the same + // success-plumbing style as other helper calls; this boundary is + // where the verifier converts failure to a revert. + success := validate_public_accumulator(success, r) + if iszero(success) { revert(0, 0) } + + // =============================================================== + // Transcript: VK digest + instances + proof. + // + // This block is the Solidity mirror of the native Midfall verifier + // transcript schedule. It does three jobs at once: + // + // 1. Absorb public data and proof bytes into the streaming + // Keccak transcript in exactly the native order. + // 2. Decode/range-check proof scalars and canonical G1 calldata. + // 3. Copy proof commitments/evaluations into planned memory + // slots consumed by Lagrange, quotient, PCS, and pairing + // blocks later in the verifier. + // + // `buf_len` is a write cursor into the transcript buffer. The + // helper functions append bytes and return the new cursor; squeeze + // helpers hash memory[TRANSCRIPT_MPTR..buf_len), reseed the buffer + // with the digest, and write the sampled Fr challenge to memory. + // =============================================================== + let buf_len := transcript_init() + // VK_DIGEST_MPTR holds the digest as a BE 32-byte word (the + // VK contract stores it via `mstore`, which matches the + // Keccak Fq transcript input). + // + // This digest commits to the verifier key / constraint system + // before any proof material is read. + buf_len := common_word(buf_len, mload(VK_DIGEST_MPTR)) + + // Absorb committed_pi = G1Affine::identity() when the + // `committed-instances` feature is on in midnight-proofs. + // Under the patched `Hashable::to_input` (see + // `midfall/proofs/src/transcript/implementors.rs`), the + // identity hashes as 128 zero bytes (EIP-2537 (0,0) + // convention), NOT the 48-byte ZCash compressed form + // 0xc0||47*0x00 that the previous emitter produced. + // Native verifier absorbs this BEFORE the instance count. + { + // 128 zero bytes: zero out 4 consecutive 32-byte words + // at buf_len. + // This is a raw transcript absorb, not a memory slot kept for + // later elliptic-curve operations. + mstore(buf_len, 0) + mstore(add(buf_len, 0x20), 0) + mstore(add(buf_len, 0x40), 0) + mstore(add(buf_len, 0x60), 0) + buf_len := add(buf_len, 0x80) + } + + { + // Native verifier absorbs a length scalar before instance + // values; Keccak Fq transcript input is canonical BE. + // The ABI length was already checked against this generated + // constant in VkLoading.yul. + buf_len := common_word(buf_len, 14) + + let instance_cptr := INSTANCE_CPTR + for { let instance_cptr_end := add(instance_cptr, 0x01c0) } + lt(instance_cptr, instance_cptr_end) + { instance_cptr := add(instance_cptr, 0x20) } { + let inst_be := calldataload(instance_cptr) + // Public inputs are BLS12-381 scalar-field elements. They + // must be canonical before transcript absorption; accepting + // non-canonical encodings would admit transcript aliases. + success := and(success, lt(inst_be, r)) + // Instances are passed BE in calldata, matching the + // Keccak Fq transcript input. + buf_len := common_word(buf_len, inst_be) + } + if iszero(success) { revert(0, 0) } + } + + // =============================================================== + // Per-user-phase reads + challenge squeezes. + // + // Each proof G1 is already EIP-2537 padded in calldata. The + // verifier validates and absorbs that 128-byte form, then copies + // it into the corresponding per-category MPTR. The PCS / + // quotient-fold blocks below dereference those MPTRs. + // + // All G1 reads follow the same pattern: + // - common_uncompressed_g1 canonicalizes/range-checks the two Fp + // coordinates and appends the exact 128 calldata bytes; + // - calldatacopy stores the same 4-word G1 slot in planned + // memory for later EIP-2537 precompile calls; + // - proof_cptr advances by one G1 byte length. + // =============================================================== + // proof_cptr walks the raw proof bytes inside the ABI `bytes` + // payload. Every successful read advances it exactly once, and the + // final equality check below proves the parser consumed the whole + // generated proof layout. + let proof_cptr := PROOF_CPTR + // advice_walk mirrors proof commitment order into the contiguous + // G1 commitment memory region used by PCS and quotient folding. + let advice_walk := ADVICE_COMMS_MPTR_BASE + // ---- User phase 1 ---- + // Advice commitments for this phase are absorbed before the phase's + // challenge squeezes. The number of commitments and challenges is + // generated from the protocol plan. + for { let end := add(proof_cptr, 0x0780) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + // Store the commitment at its phase-ordered advice slot. + calldatacopy(advice_walk, proof_cptr, 0x80) + advice_walk := add(advice_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- theta ---- + // From this point onward the transcript alternates between + // squeezed challenges and proof commitments exactly as + // midnight-proofs does in `plonk/verifier.rs`. + // theta batches lookup input expressions. + buf_len := squeeze_to(buf_len, THETA_MPTR) + // ---- multiplicities (one G1 per lookup) ---- + // Lookup multiplicity commitments are absorbed after theta and + // copied into their own contiguous G1 region. + let lookup_m_walk := LOOKUP_M_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0100) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_m_walk, proof_cptr, 0x80) + lookup_m_walk := add(lookup_m_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- beta, gamma ---- + // beta and gamma are the permutation/lookup randomizers. They are + // squeezed after lookup multiplicities and before permutation + // product commitments, matching the native verifier schedule. + buf_len := squeeze_to(buf_len, BETA_MPTR) + buf_len := squeeze_to(buf_len, GAMMA_MPTR) + // ---- permutation Z products ---- + // Permutation product commitments are used by the permutation + // identities in the quotient numerator and later by PCS openings. + let perm_z_walk := PERM_Z_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0300) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(perm_z_walk, proof_cptr, 0x80) + perm_z_walk := add(perm_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // ---- lookup helpers + accumulators (per-lookup) ---- + // Each lookup contributes zero or more helper commitments followed + // by its lookup accumulator Z commitment. The generated layout keeps + // helper commitments and accumulator commitments in separate memory + // regions because the quotient/PCS schedules address them + // differently. + let lookup_helper_walk := LOOKUP_HELPER_COMMS_MPTR_BASE + let lookup_z_walk := LOOKUP_Z_COMMS_MPTR_BASE + // lookup 0: 1 helper(s) + 1 acc + // Helper commitments for lookup 0. + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_helper_walk, proof_cptr, 0x80) + lookup_helper_walk := add(lookup_helper_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // Accumulator commitment for lookup 0. This is + // always one G1 when the lookup section is present. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_z_walk, proof_cptr, 0x80) + lookup_z_walk := add(lookup_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + // lookup 1: 1 helper(s) + 1 acc + // Helper commitments for lookup 1. + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_helper_walk, proof_cptr, 0x80) + lookup_helper_walk := add(lookup_helper_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // Accumulator commitment for lookup 1. This is + // always one G1 when the lookup section is present. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_z_walk, proof_cptr, 0x80) + lookup_z_walk := add(lookup_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // ---- trash_challenge ---- + // Midnight squeezes this challenge unconditionally, even when the + // circuit has no trash arguments. + // Keeping this squeeze unconditional preserves transcript + // compatibility across circuits with and without trash columns. + buf_len := squeeze_to(buf_len, TRASH_CHALLENGE_MPTR) + // ---- trashcans ---- + // Trashcan commitments are optional, but when present they are + // absorbed before y so the quotient batching challenge binds them. + let trashcan_walk := TRASHCAN_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(trashcan_walk, proof_cptr, 0x80) + trashcan_walk := add(trashcan_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- y ---- + // y batches all quotient identities. Quotient commitments are read + // only after y is sampled, matching the Rust verifier flow. + buf_len := squeeze_to(buf_len, Y_MPTR) + + // ---- quotient commitment(s) ---- + // Each uncompressed quotient commitment is calldatacopied directly to + // QUOTIENT_LIMB_COMMS_MPTR_BASE; the Horner fold below reads + // them back from memory. common_uncompressed_g1 absorbs the + // 128-byte calldata form into the transcript verbatim. + // + // Multi-limb quotient mode reads several Q_i commitments; single-H + // mode renders this loop with one limb. + let quotient_walk := QUOTIENT_LIMB_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0200) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(quotient_walk, proof_cptr, 0x80) + quotient_walk := add(quotient_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- x ---- + // x is the main evaluation point. Values read after this point are + // alleged polynomial evaluations at x or derived PCS openings. + buf_len := squeeze_to(buf_len, X_MPTR) + + // ---- evaluations ---- + // Optimisation H3: the off-chain Solidity proof shim rewrites + // proof scalars into BE calldata words. Spill each decoded eval + // into REVERSED_EVALS_MPTR in the same iteration we range-check + // it, so downstream references can use cheap mload. + // + // The Rust verifier conceptually reads evaluations in query order. + // The lowering plan arranges REVERSED_EVALS_MPTR in the order used + // by the quotient VM/direct evaluator, hence the generated name. + { + let eval_buf := REVERSED_EVALS_MPTR + for { let end := add(proof_cptr, 0x0cc0) } + lt(proof_cptr, end) + {} { + let eval := calldataload(proof_cptr) + // Proof evaluation scalars must be canonical Fr elements + // before they are absorbed or made available to quotient + // reconstruction. + if iszero(lt(eval, r)) { revert(0, 0) } + // Spill for quotient numerator and PCS codegen. + mstore(eval_buf, eval) + eval_buf := add(eval_buf, 0x20) + // Absorb the exact BE field word used by the native + // Keccak transcript. + buf_len := common_word(buf_len, eval) + proof_cptr := add(proof_cptr, 0x20) + } + } + + // ---- x1, x2 ---- + // x1 and x2 batch the KZG multi-opening reduction. They are + // squeezed after all polynomial evaluations are absorbed. + buf_len := squeeze_to(buf_len, X1_MPTR) + buf_len := squeeze_to(buf_len, X2_MPTR) + + // ---- f_com (1 uncompressed G1) ---- + // f_com is the commitment to the batched polynomial used by the PCS + // multi-open protocol. It is both transcript material and later + // pairing/MSM input. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(F_COM_MPTR, proof_cptr, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // ---- x3 ---- + // x3 is the PCS evaluation point for f_com. + buf_len := squeeze_to(buf_len, X3_MPTR) + // truncated-challenges mirrors midnight-proofs + // proofs/src/poly/kzg/mod.rs: + // - x3 is the f_com evaluation point and is truncated + // immediately after squeeze. + // - x1 and x4 remain full squeezed Fr words, but later PCS + // batching stores truncate(x1^i) and truncate(x4^i) while + // keeping the internal power accumulators full precision. + // This direct x3 mask is therefore one part of the PCS truncation + // rule, not the only truncated value used by the verifier. + mstore(X3_MPTR, and(mload(X3_MPTR), 0xffffffffffffffffffffffffffffffff)) + + // ---- q_evals (one Fq per point set) ---- + // q_evals are not spilled into REVERSED_EVALS_MPTR because the PCS + // emitter reads them as a contiguous calldata range from the saved + // Q_EVAL_CPTR_MPTR cursor. + // + // Each q_eval is the claimed evaluation for one prepared point set + // in the KZG multi-open reduction. They are still transcript + // material and must be range-checked as Fr scalars. + mstore(Q_EVAL_CPTR_MPTR, proof_cptr) + for { let end := add(proof_cptr, 0xa0) } + lt(proof_cptr, end) + {} { + let eval := calldataload(proof_cptr) + // Canonical Fr check before transcript absorption. + if iszero(lt(eval, r)) { revert(0, 0) } + buf_len := common_word(buf_len, eval) + proof_cptr := add(proof_cptr, 0x20) + } + + // ---- x4 ---- + // x4 is the final PCS batching challenge, sampled after q_evals + // and before the opening proof point pi. + buf_len := squeeze_to(buf_len, X4_MPTR) + + // ---- pi (1 uncompressed G1) ---- + // pi is the KZG opening proof commitment. It is the last proof + // object absorbed into the transcript and later becomes one side of + // the final pairing check. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(PI_MPTR, proof_cptr, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // The hand-rolled proof parser must consume exactly the ABI + // `proof` bytes before the `instances` length word. This is + // redundant with the generated proof length today, but makes + // future proof-layout drift fail closed. + // + // NUM_INSTANCE_CPTR is the calldata word immediately after the + // dynamic proof bytes payload. If proof_cptr lands anywhere else, + // some section was under-read or over-read. + if iszero(eq(proof_cptr, NUM_INSTANCE_CPTR)) { revert(0, 0) } + + // `success` carries deferred canonicality failures from public + // instance reads. G1/proof scalar helpers revert immediately. + if iszero(success) { revert(0, 0) } + + // =============================================================== + // Lagrange & instance-evaluation block (pure Fr arithmetic). + // =============================================================== + { + let k := 20 + let x := mload(X_MPTR) + // Compute x^n by repeated squaring, with n = 2^k. + let x_n := x + for { let idx := 0 } lt(idx, k) { idx := add(idx, 1) } { + x_n := mulmod(x_n, x_n, r) + } + + let omega := mload(OMEGA_MPTR) + + // First pass writes denominators (x - omega_i) for every + // Lagrange value needed below, then appends x^n - 1. The + // batch inversion pass turns all of them into inverses in one + // modexp call. + let mptr := X_N_MPTR + let mptr_end := add(mptr, 0x0300) + for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } + lt(mptr, mptr_end) + { mptr := add(mptr, 0x20) } { + mstore(mptr, addmod(x, sub(r, pow_of_omega), r)) + pow_of_omega := mulmod(pow_of_omega, omega, r) + } + let x_n_minus_1 := addmod(x_n, sub(r, 1), r) + mstore(mptr_end, x_n_minus_1) + success := batch_invert(success, X_N_MPTR, add(mptr_end, 0x20), BATCH_INV_SCRATCH_MPTR, r) + + // Convert inverted denominators into Lagrange evaluations: + // L_i(x) = (x^n - 1) * n^-1 * omega_i / (x - omega_i). + mptr := X_N_MPTR + let l_i_common := mulmod(x_n_minus_1, mload(N_INV_MPTR), r) + for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } + lt(mptr, mptr_end) + { mptr := add(mptr, 0x20) } { + mstore(mptr, mulmod(l_i_common, mulmod(mload(mptr), pow_of_omega, r), r)) + pow_of_omega := mulmod(pow_of_omega, omega, r) + } + + // l_blind is the sum of the negative-rotation Lagrange terms + // used by the midnight-proofs blinding identity. + let l_blind := mload(add(X_N_MPTR, 0x20)) + let l_i_cptr := add(X_N_MPTR, 0x40) + for { let l_i_cptr_end := add(X_N_MPTR, 0x0140) } + lt(l_i_cptr, l_i_cptr_end) + { l_i_cptr := add(l_i_cptr, 0x20) } { + l_blind := addmod(l_blind, mload(l_i_cptr), r) + } + + // Public instance polynomial evaluation at x. Instance words + // have already been range-checked and absorbed in transcript + // order; this loop only forms the linear combination. + let instance_eval := 0 + for { + let instance_cptr := INSTANCE_CPTR + let instance_cptr_end := add(instance_cptr, 0x01c0) + } + lt(instance_cptr, instance_cptr_end) + { instance_cptr := add(instance_cptr, 0x20) + l_i_cptr := add(l_i_cptr, 0x20) } { + instance_eval := addmod(instance_eval, mulmod(mload(l_i_cptr), calldataload(instance_cptr), r), r) + } + + // Persist the derived values into named memory slots consumed + // by quotient reconstruction and PCS preparation. + let x_n_minus_1_inv := mload(mptr_end) + let l_last := mload(X_N_MPTR) + let l_0 := mload(add(X_N_MPTR, 0x0140)) + + mstore(X_N_MPTR, x_n) + mstore(X_N_MINUS_1_INV_MPTR, x_n_minus_1_inv) + mstore(L_LAST_MPTR, l_last) + mstore(L_BLIND_MPTR, l_blind) + mstore(L_0_MPTR, l_0) + mstore(INSTANCE_EVAL_MPTR, instance_eval) + } + + if iszero(success) { revert(0, 0) } + + + // =============================================================== + // External batched identity numerator reconstruction. + // + // The quotient evaluator receives the verifier memory image from + // QUOTIENT_FRAME_BASE..+QUOTIENT_FRAME_LEN, reconstructs the same + // y-batched numerator, and returns: + // word 0: magic/version + // word 1: linearization expected eval + // word 2..: simple-selector accumulators + // =============================================================== + { + let q_out := QUOTIENT_RETURN_MPTR + // The quotient evaluator is as correctness-critical as the VK: + // it reconstructs the y-batched identity numerator and + // selector buckets. Re-check the pinned runtime before every + // external call, mirroring the VK freshness guard above. + if iszero(and( + eq(extcodesize(quotientEvaluator), EXPECTED_QUOTIENT_LENGTH), + eq(extcodehash(quotientEvaluator), EXPECTED_QUOTIENT_CODEHASH_WORD) + )) { revert(0, 0) } + if iszero(staticcall(gas(), quotientEvaluator, 0x3680, 0x6ac0, q_out, 0x0180)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x0180)) { revert(0, 0) } + if iszero(eq(mload(q_out), 0x00000000000000000000000000000000000000000000000051554556414c0001)) { revert(0, 0) } + // Word 1 is the negated y-batched identity numerator, stored + // in the same memory slot used by the monolithic path. + mstore(QUOTIENT_EVAL_MPTR, mload(add(q_out, 0x20))) + // Remaining return words are selector linearization buckets. + // Copy them back into the canonical selector accumulator region + // so the PCS code path is identical for split and monolithic + // quotient renders. + for { let q_i := 0 } lt(q_i, 10) { q_i := add(q_i, 1) } { + mstore(add(SELECTOR_ACC_MPTR, shl(5, q_i)), mload(add(q_out, add(0x40, shl(5, q_i))))) + } + } + + // =============================================================== + // Prepare linearization scalars for the final PCS MSM. + // + // The linearized commitment is + // (1 - x^n) * Σ_i x_split^i * Q_i + // + Σ_j sel_acc_j * S_j_com, + // where x_split = x^(n-1). Instead of materializing that point + // with a standalone G1MSM here, PCS block 5 expands the + // linearized commitment into its quotient and selector + // pairs inside the already-fused final MSM. + // + // QUOTIENT_MPTR is no longer a G1 point in this path. Its first + // two words carry: + // word 0: x_split + // word 1: one_minus_x_n + // =============================================================== + { + let x := mload(X_MPTR) + let k := 20 + // Compute both x^n and x^(n-1) with the same squaring walk: + // x_pow_2i tracks x^(2^i), while x_pow_2i_minus1 tracks + // x^(2^i - 1). + let x_pow_2i := x + let x_pow_2i_minus1 := 1 + for { let idx := 0 } lt(idx, k) { idx := add(idx, 1) } { + x_pow_2i_minus1 := mulmod( + mulmod(x_pow_2i_minus1, x_pow_2i_minus1, r), + x, + r + ) + x_pow_2i := mulmod(x_pow_2i, x_pow_2i, r) + } + let x_split := x_pow_2i_minus1 + let one_minus_x_n := addmod(1, sub(r, x_pow_2i), r) + + // PCS block 5 interprets this 2-word payload as scalar + // metadata, not as a materialized G1 point. + mstore(QUOTIENT_MPTR, x_split) + mstore(add(QUOTIENT_MPTR, 0x20), one_minus_x_n) + } + + // =============================================================== + // PCS computation (multi-prepare emitter from Step 5). + // + // The Rust lowering stage has already expanded the KZG multi-open + // equation into a sequence of generated Yul sub-blocks. Those + // blocks populate: + // - F_EVAL_MPTR / V_MPTR scalar batching values; + // - FINAL_COM_MPTR for the fused commitment MSM; + // - PAIRING_LHS_MPTR and PAIRING_RHS_MPTR for the final pairing. + // =============================================================== + { + // Generated PCS sub-block 1. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // 4 distinct rotation(s) + let x := mload(X_MPTR) + let omega := mload(OMEGA_MPTR) + let omega_inv := mload(OMEGA_INV_MPTR) + let x_pow_of_omega := x + mstore(add(ROT_POINTS_MPTR, 0x40), x_pow_of_omega) + x_pow_of_omega := mulmod(x_pow_of_omega, omega, r) + mstore(add(ROT_POINTS_MPTR, 0x60), x_pow_of_omega) + x_pow_of_omega := x + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + mstore(add(ROT_POINTS_MPTR, 0x20), x_pow_of_omega) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + mstore(add(ROT_POINTS_MPTR, 0x0), x_pow_of_omega) + } + // Generated PCS sub-block 2. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // pre-compute 43 x1 power(s) + let x1 := mload(X1_MPTR) + mstore(X1_POWERS_MPTR, 1) + let acc := 1 + let p := X1_POWERS_MPTR + for { let i := 0 } lt(i, 0x2a) { i := add(i, 1) } { + p := add(p, 0x20) + acc := mulmod(acc, x1, r) + mstore(p, and(acc, 0xffffffffffffffffffffffffffffffff)) + } + } + // Generated PCS sub-block 3. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[0]: 43 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x9980) + mstore(0xb2a0, 0x9480) + mstore(0xb2c0, 0xa020) + mstore(0xb2e0, 0xa040) + mstore(0xb300, 0xa0a0) + mstore(0xb320, 0xa0c0) + mstore(0xb340, 0xa120) + mstore(0xb360, 0x99a0) + mstore(0xb380, 0x99c0) + mstore(0xb3a0, 0x99e0) + mstore(0xb3c0, 0x9a00) + mstore(0xb3e0, 0x9a20) + mstore(0xb400, 0x9a40) + mstore(0xb420, 0x9a60) + mstore(0xb440, 0x9a80) + mstore(0xb460, 0x9aa0) + mstore(0xb480, 0x9ac0) + mstore(0xb4a0, 0x9ae0) + mstore(0xb4c0, 0x9b00) + mstore(0xb4e0, 0x9b20) + mstore(0xb500, 0x9b40) + mstore(0xb520, 0x9b60) + mstore(0xb540, 0x9b80) + mstore(0xb560, 0x9ba0) + mstore(0xb580, 0x9bc0) + mstore(0xb5a0, 0x9be0) + mstore(0xb5c0, 0x9c00) + mstore(0xb5e0, 0x9c20) + mstore(0xb600, 0x9c40) + mstore(0xb620, 0x9c60) + mstore(0xb640, 0x9c80) + mstore(0xb660, 0x9ca0) + mstore(0xb680, 0x9cc0) + mstore(0xb6a0, 0x9ce0) + mstore(0xb6c0, 0x9d00) + mstore(0xb6e0, 0x9d20) + mstore(0xb700, 0x9d40) + mstore(0xb720, 0x9d60) + mstore(0xb740, 0x9d80) + mstore(0xb760, 0x9da0) + mstore(0xb780, 0x9dc0) + mstore(0xb7a0, 0x9de0) + mstore(0xb7c0, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x9980) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x20) + for { let i := 1 } lt(i, 0x2b) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x20) + } + mstore(add(Q_EVAL_SET_MPTR, 0x0), q_eval_set_0) + } + // Generated PCS sub-block 4. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[1]: 3 commitment(s) + let q_eval_set_0 := mload(0x9660) + let q_eval_set_1 := mload(0x9920) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x9680), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9940), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x96a0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9960), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + mstore(add(Q_EVAL_SET_MPTR, 0x20), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x40), q_eval_set_1) + } + // Generated PCS sub-block 5. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[2]: 3 commitment(s) + let q_eval_set_0 := mload(0x9fe0) + let q_eval_set_1 := mload(0xa000) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa060), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa080), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa0e0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa100), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + mstore(add(Q_EVAL_SET_MPTR, 0x60), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x80), q_eval_set_1) + } + // Generated PCS sub-block 6. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[3]: 11 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x94a0) + mstore(0xb2a0, 0x9540) + mstore(0xb2c0, 0x97c0) + mstore(0xb2e0, 0x94c0) + mstore(0xb300, 0x9560) + mstore(0xb320, 0x97e0) + mstore(0xb340, 0x94e0) + mstore(0xb360, 0x9580) + mstore(0xb380, 0x9800) + mstore(0xb3a0, 0x9500) + mstore(0xb3c0, 0x96c0) + mstore(0xb3e0, 0x9820) + mstore(0xb400, 0x9520) + mstore(0xb420, 0x96e0) + mstore(0xb440, 0x9840) + mstore(0xb460, 0x95a0) + mstore(0xb480, 0x9700) + mstore(0xb4a0, 0x9860) + mstore(0xb4c0, 0x95c0) + mstore(0xb4e0, 0x9720) + mstore(0xb500, 0x9880) + mstore(0xb520, 0x95e0) + mstore(0xb540, 0x9740) + mstore(0xb560, 0x98a0) + mstore(0xb580, 0x9600) + mstore(0xb5a0, 0x9760) + mstore(0xb5c0, 0x98c0) + mstore(0xb5e0, 0x9620) + mstore(0xb600, 0x9780) + mstore(0xb620, 0x98e0) + mstore(0xb640, 0x9640) + mstore(0xb660, 0x97a0) + mstore(0xb680, 0x9900) + let q_eval_set_0 := mload(0x94a0) + let q_eval_set_1 := mload(0x9540) + let q_eval_set_2 := mload(0x97c0) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x60) + for { let i := 1 } lt(i, 0xb) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(mload(add(eval_p, 0x20))), pow, r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(mload(add(eval_p, 0x40))), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x60) + } + mstore(add(Q_EVAL_SET_MPTR, 0xa0), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0xc0), q_eval_set_1) + mstore(add(Q_EVAL_SET_MPTR, 0xe0), q_eval_set_2) + } + // Generated PCS sub-block 7. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[4]: 5 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x9e00) + mstore(0xb2a0, 0x9e20) + mstore(0xb2c0, 0x9e40) + mstore(0xb2e0, 0x9e60) + mstore(0xb300, 0x9e80) + mstore(0xb320, 0x9ea0) + mstore(0xb340, 0x9ec0) + mstore(0xb360, 0x9ee0) + mstore(0xb380, 0x9f00) + mstore(0xb3a0, 0x9f20) + mstore(0xb3c0, 0x9f40) + mstore(0xb3e0, 0x9f60) + mstore(0xb400, 0x9f80) + mstore(0xb420, 0x9fa0) + mstore(0xb440, 0x9fc0) + let q_eval_set_0 := mload(0x9e00) + let q_eval_set_1 := mload(0x9e20) + let q_eval_set_2 := mload(0x9e40) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x60) + for { let i := 1 } lt(i, 0x5) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(mload(add(eval_p, 0x20))), pow, r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(mload(add(eval_p, 0x40))), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x60) + } + mstore(add(Q_EVAL_SET_MPTR, 0x100), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x120), q_eval_set_1) + mstore(add(Q_EVAL_SET_MPTR, 0x140), q_eval_set_2) + } + // Generated PCS sub-block 8. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // f_eval via Horner over 5 reversed set(s) + let x2 := mload(X2_MPTR) + let x3 := mload(X3_MPTR) + let f_eval := 0 + let Q_EVAL_CPTR := mload(Q_EVAL_CPTR_MPTR) + let rot_pt_0 := mload(add(ROT_POINTS_MPTR, 0x0)) + let rot_pt_1 := mload(add(ROT_POINTS_MPTR, 0x20)) + let rot_pt_2 := mload(add(ROT_POINTS_MPTR, 0x40)) + let rot_pt_3 := mload(add(ROT_POINTS_MPTR, 0x60)) + // --- set 4 (cardinality 3) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let dx_2 := addmod(x3, sub(r, rot_pt_0), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_0), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_0), r), r) + let lbasis_2 := 1 + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_0, sub(r, rot_pt_2), r), r) + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_0, sub(r, rot_pt_3), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, dx_2, r) + let bp_3 := mulmod(bp_2, lbasis_0, r) + let bp_4 := mulmod(bp_3, lbasis_1, r) + let bp_5 := mulmod(bp_4, lbasis_2, r) + let bq := scalar_inv(bp_5) + let lbasis_inv_2 := mulmod(bq, bp_4, r) + bq := mulmod(bq, lbasis_2, r) + let lbasis_inv_1 := mulmod(bq, bp_3, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_2 := mulmod(bq, bp_1, r) + bq := mulmod(bq, dx_2, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + den_inv := mulmod(den_inv, dx_inv_2, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x80)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x100)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x120)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + let term_2 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x140)), dx_inv_2, r), lbasis_inv_2, r) + eval := addmod(eval, sub(r, term_2), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 3 (cardinality 3) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let dx_2 := addmod(x3, sub(r, rot_pt_1), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_1), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_1), r), r) + let lbasis_2 := 1 + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_1, sub(r, rot_pt_2), r), r) + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_1, sub(r, rot_pt_3), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, dx_2, r) + let bp_3 := mulmod(bp_2, lbasis_0, r) + let bp_4 := mulmod(bp_3, lbasis_1, r) + let bp_5 := mulmod(bp_4, lbasis_2, r) + let bq := scalar_inv(bp_5) + let lbasis_inv_2 := mulmod(bq, bp_4, r) + bq := mulmod(bq, lbasis_2, r) + let lbasis_inv_1 := mulmod(bq, bp_3, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_2 := mulmod(bq, bp_1, r) + bq := mulmod(bq, dx_2, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + den_inv := mulmod(den_inv, dx_inv_2, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xa0)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xc0)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + let term_2 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xe0)), dx_inv_2, r), lbasis_inv_2, r) + eval := addmod(eval, sub(r, term_2), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 2 (cardinality 2) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, lbasis_0, r) + let bp_3 := mulmod(bp_2, lbasis_1, r) + let bq := scalar_inv(bp_3) + let lbasis_inv_1 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_1, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x60)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x80)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 1 (cardinality 2) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_1), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_1), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_1, sub(r, rot_pt_2), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, lbasis_0, r) + let bp_3 := mulmod(bp_2, lbasis_1, r) + let bq := scalar_inv(bp_3) + let lbasis_inv_1 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_1, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x20)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x40)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 0 (cardinality 1) --- + { + let dx0 := addmod(x3, sub(r, rot_pt_2), r) + let dx0_inv := scalar_inv(dx0) + let eval := mulmod(addmod(calldataload(add(Q_EVAL_CPTR, 0x0)), sub(r, mload(add(Q_EVAL_SET_MPTR, 0x0))), r), dx0_inv, r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + mstore(F_EVAL_MPTR, f_eval) + } + // Generated PCS sub-block 9. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // build final_com and v (KZG single-opening proof, fused MSM) + // final MSM input length from circuit/VK shape: 78 term(s) + let x4 := mload(X4_MPTR) + let lin_x_split := mload(QUOTIENT_MPTR) + let lin_one_minus_x_n := mload(add(QUOTIENT_MPTR, 0x20)) + let Q_EVAL_CPTR := mload(Q_EVAL_CPTR_MPTR) + let x4_pow_full := 1 + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_1 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_2 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_3 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_4 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_5 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + let v := calldataload(Q_EVAL_CPTR) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), x4_pow_1, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), x4_pow_3, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x80)), x4_pow_4, r), r) + v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_5, r), r) + mcopy(0xb280, 0xa840, 0x80) + mstore(0xb300, 1) + mcopy(0xb320, 0xa8c0, 0x80) + mstore(0xb3a0, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0xb3c0, 0xacc0, 0x80) + mstore(0xb440, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0xb460, 0xa940, 0x80) + mstore(0xb4e0, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0xb500, 0xad40, 0x80) + mstore(0xb580, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0xb5a0, 0xaec0, 0x80) + mstore(0xb620, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0xb640, 0x6700, 0x80) + mstore(0xb6c0, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0xb6e0, 0x6480, 0x80) + mstore(0xb760, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0xb780, 0x6500, 0x80) + mstore(0xb800, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0xb820, 0x6580, 0x80) + mstore(0xb8a0, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0xb8c0, 0x6600, 0x80) + mstore(0xb940, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0xb960, 0x6680, 0x80) + mstore(0xb9e0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0xba00, 0x6280, 0x80) + mstore(0xba80, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0xbaa0, 0x6300, 0x80) + mstore(0xbb20, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0xbb40, 0x6380, 0x80) + mstore(0xbbc0, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0xbbe0, 0x6400, 0x80) + mstore(0xbc60, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0xbc80, 0x6780, 0x80) + mstore(0xbd00, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0xbd20, 0x6800, 0x80) + mstore(0xbda0, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0xbdc0, 0x6880, 0x80) + mstore(0xbe40, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0xbe60, 0x6900, 0x80) + mstore(0xbee0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0xbf00, 0x6b00, 0x80) + mstore(0xbf80, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0xbfa0, 0x6c00, 0x80) + mstore(0xc020, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0xc040, 0x6f80, 0x80) + mstore(0xc0c0, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0xc0e0, 0x7000, 0x80) + mstore(0xc160, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0xc180, 0x7080, 0x80) + mstore(0xc200, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0xc220, 0x7100, 0x80) + mstore(0xc2a0, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0xc2c0, 0x7180, 0x80) + mstore(0xc340, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0xc360, 0x7200, 0x80) + mstore(0xc3e0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0xc400, 0x7280, 0x80) + mstore(0xc480, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0xc4a0, 0x7300, 0x80) + mstore(0xc520, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0xc540, 0x7380, 0x80) + mstore(0xc5c0, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0xc5e0, 0x7400, 0x80) + mstore(0xc660, mload(add(X1_POWERS_MPTR, 0x400))) + mcopy(0xc680, 0x7480, 0x80) + mstore(0xc700, mload(add(X1_POWERS_MPTR, 0x420))) + mcopy(0xc720, 0x7500, 0x80) + mstore(0xc7a0, mload(add(X1_POWERS_MPTR, 0x440))) + mcopy(0xc7c0, 0x7580, 0x80) + mstore(0xc840, mload(add(X1_POWERS_MPTR, 0x460))) + mcopy(0xc860, 0x7600, 0x80) + mstore(0xc8e0, mload(add(X1_POWERS_MPTR, 0x480))) + mcopy(0xc900, 0x7680, 0x80) + mstore(0xc980, mload(add(X1_POWERS_MPTR, 0x4a0))) + mcopy(0xc9a0, 0x7700, 0x80) + mstore(0xca20, mload(add(X1_POWERS_MPTR, 0x4c0))) + mcopy(0xca40, 0x7780, 0x80) + mstore(0xcac0, mload(add(X1_POWERS_MPTR, 0x4e0))) + mcopy(0xcae0, 0x7800, 0x80) + mstore(0xcb60, mload(add(X1_POWERS_MPTR, 0x500))) + mcopy(0xcb80, 0x7880, 0x80) + mstore(0xcc00, mload(add(X1_POWERS_MPTR, 0x520))) + let lin_query_scalar_41 := mload(add(X1_POWERS_MPTR, 0x540)) + let lin_cur_scalar_41 := mulmod(lin_query_scalar_41, lin_one_minus_x_n, r) + mcopy(0xcc20, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0xcca0, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xccc0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0xcd40, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xcd60, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0xcde0, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xce00, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0xce80, lin_cur_scalar_41) + mcopy(0xcea0, 0x6980, 0x80) + mstore(0xcf20, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0xcf40, 0x6a00, 0x80) + mstore(0xcfc0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0xcfe0, 0x6a80, 0x80) + mstore(0xd060, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0xd080, 0x6b80, 0x80) + mstore(0xd100, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0xd120, 0x6c80, 0x80) + mstore(0xd1a0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) + mcopy(0xd1c0, 0x6d00, 0x80) + mstore(0xd240, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) + mcopy(0xd260, 0x6d80, 0x80) + mstore(0xd2e0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) + mcopy(0xd300, 0x6e00, 0x80) + mstore(0xd380, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) + mcopy(0xd3a0, 0x6e80, 0x80) + mstore(0xd420, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) + mcopy(0xd440, 0x6f00, 0x80) + mstore(0xd4c0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) + mcopy(0xd4e0, 0xa6c0, 0x80) + mstore(0xd560, x4_pow_1) + mcopy(0xd580, 0xa740, 0x80) + mstore(0xd600, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0xd620, 0xa7c0, 0x80) + mstore(0xd6a0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0xd6c0, 0xac40, 0x80) + mstore(0xd740, x4_pow_2) + mcopy(0xd760, 0xadc0, 0x80) + mstore(0xd7e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0xd800, 0xae40, 0x80) + mstore(0xd880, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) + mcopy(0xd8a0, 0xa140, 0x80) + mstore(0xd920, x4_pow_3) + mcopy(0xd940, 0xa1c0, 0x80) + mstore(0xd9c0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) + mcopy(0xd9e0, 0xa240, 0x80) + mstore(0xda60, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_3, r)) + mcopy(0xda80, 0xa2c0, 0x80) + mstore(0xdb00, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_3, r)) + mcopy(0xdb20, 0xa340, 0x80) + mstore(0xdba0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_3, r)) + mcopy(0xdbc0, 0xa3c0, 0x80) + mstore(0xdc40, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_3, r)) + mcopy(0xdc60, 0xa440, 0x80) + mstore(0xdce0, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_3, r)) + mcopy(0xdd00, 0xa4c0, 0x80) + mstore(0xdd80, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_3, r)) + mcopy(0xdda0, 0xa540, 0x80) + mstore(0xde20, mulmod(mload(add(X1_POWERS_MPTR, 0x100)), x4_pow_3, r)) + mcopy(0xde40, 0xa5c0, 0x80) + mstore(0xdec0, mulmod(mload(add(X1_POWERS_MPTR, 0x120)), x4_pow_3, r)) + mcopy(0xdee0, 0xa640, 0x80) + mstore(0xdf60, mulmod(mload(add(X1_POWERS_MPTR, 0x140)), x4_pow_3, r)) + mcopy(0xdf80, 0xa9c0, 0x80) + mstore(0xe000, x4_pow_4) + mcopy(0xe020, 0xaa40, 0x80) + mstore(0xe0a0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_4, r)) + mcopy(0xe0c0, 0xaac0, 0x80) + mstore(0xe140, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_4, r)) + mcopy(0xe160, 0xab40, 0x80) + mstore(0xe1e0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_4, r)) + mcopy(0xe200, 0xabc0, 0x80) + mstore(0xe280, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_4, r)) + mcopy(0xe2a0, F_COM_MPTR, 0x80) + mstore(0xe320, x4_pow_5) + if success { + success := staticcall(gas(), 0x0c, 0xb280, 0x30c0, FINAL_COM_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mstore(V_MPTR, v) + } + // Generated PCS sub-block 10. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // Scale z*pi - vG before the final pairing check + // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) + mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) + if success { + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(0x1080, FINAL_COM_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) + if success { + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + if success { + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) + } + } + + // Batch the prevalidated public IVC accumulator pairing equation + // into the final KZG pairing. + // + // We do not simply multiply the two pairing equations together: + // two bad equations could cancel. Instead, after all four G1 + // pairing inputs are fixed, derive a verifier-local randomizer + // alpha and check: + // + // e(kzg_rhs + alpha * acc_rhs, G2_BASE) + // * e(kzg_lhs + alpha * acc_lhs, NEG_S_G2_BASE) == 1 + // + // If either original equation is bad, this combined equation + // holds for at most one alpha in Fr. + { + let batch_ptr := 0x0100 + + // Domain || KZG rhs/lhs || accumulator rhs/lhs. + mstore(batch_ptr, 0x70616972696e672d62617463682d6163632d6b7a670000000000000000) + mcopy(add(batch_ptr, 0x20), PAIRING_RHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0xa0), PAIRING_LHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0x0120), ACC_RHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0x01a0), ACC_LHS_MPTR, 0x80) + // alpha is Fiat-Shamir over the fully materialized pairing + // inputs. Replace the negligible zero draw with one so the + // accumulator equation cannot be accidentally dropped. + let acc_pair_alpha := mod(keccak256(batch_ptr, 0x0220), r) + if iszero(acc_pair_alpha) { acc_pair_alpha := 1 } + + // PAIRING_RHS_MPTR += alpha * ACC_RHS_MPTR. + // First compute alpha * ACC_RHS with a one-pair G1MSM, then + // add it into the KZG RHS point. + mcopy(batch_ptr, ACC_RHS_MPTR, 0x80) + mstore(add(batch_ptr, 0x80), acc_pair_alpha) + if success { + success := staticcall(gas(), 0x0c, batch_ptr, 0xa0, batch_ptr, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(add(batch_ptr, 0x80), PAIRING_RHS_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, batch_ptr, 0x0100, PAIRING_RHS_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + + // PAIRING_LHS_MPTR += alpha * ACC_LHS_MPTR. + // Mirror the same randomized batching on the KZG LHS point. + mcopy(batch_ptr, ACC_LHS_MPTR, 0x80) + mstore(add(batch_ptr, 0x80), acc_pair_alpha) + if success { + success := staticcall(gas(), 0x0c, batch_ptr, 0xa0, batch_ptr, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(add(batch_ptr, 0x80), PAIRING_LHS_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, batch_ptr, 0x0100, PAIRING_LHS_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + } + + // The Yul `ec_pairing` helper checks + // e(arg0, G2_BASE) * e(arg1, NEG_S_G2_BASE) == 1 + // i.e. e(arg0, [1]_2) = e(arg1, [s]_2). + // + // The KZG pairing identity is + // e(final_com - v*G + x3*pi, [1]_2) = e(pi, [s]_2), + // so arg0 must be (final_com - v*G + x3*pi) and arg1 must be + // pi. The PAIRING_*_MPTR slots store + // PAIRING_LHS_MPTR := pi + // PAIRING_RHS_MPTR := final_com - v*G + x3*pi + // -- the historical "LHS"/"RHS" naming follows the dual MSM + // accumulator (left = pi, right = combined) and *not* the + // pairing argument order. Pass them swapped to ec_pairing. + if iszero(success) { revert(0, 0) } + success := ec_pairing(success, PAIRING_RHS_MPTR, PAIRING_LHS_MPTR) + + + + // Success path is terminal. Invalid inputs have already reverted, + // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } + mstore(RETURN_MPTR, 1) + return(RETURN_MPTR, 0x20) + } + } +} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/ivc/Halo2VerifyingKey.sol b/proofs/solidity-verifier/fixtures/ivc/Halo2VerifyingKey.sol new file mode 100644 index 000000000..f8c08652b --- /dev/null +++ b/proofs/solidity-verifier/fixtures/ivc/Halo2VerifyingKey.sol @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity ^0.8.24; + +/// @title Halo2 BLS12-381 verifying-key payload. +/// @notice Contract whose deployed runtime is `INVALID || generated verifier-key payload`. +/// @dev Byte 0 is an unconditional INVALID opcode so direct calls cannot execute payload bytes as code. The linked verifier pins the full runtime by length/codehash and copies the payload starting at byte 1. +/// @dev The layout follows the verifier inputs derived from +/// `midfall/proofs/src/plonk/mod.rs::VerifyingKey` and the transcript +/// `vk.hash_into` behavior used by `midfall/proofs/src/plonk/verifier.rs`. +/// +/// Layout (in 32-byte words, big-endian). The header slots are generated from +/// Rust's `VkHeaderLayout`; the byte offsets are absolute from the start of the +/// VK payload, not from byte 0 of the runtime. Runtime byte 0 is the INVALID +/// prefix; the verifier loads the payload via +/// `extcodecopy(vk, VK_MPTR, 0x01, vk_payload_len)` and then references each +/// slot by `VK_MPTR + i`. +/// +/// word 0 : vk_digest (Fq, transcript_repr of the CS) +/// word 1 : num_instances +/// word 2 : k (log2 of the domain size) +/// word 3 : n_inv (1/n in Fr) +/// word 4 : omega (n-th primitive root of unity) +/// word 5 : omega_inv +/// word 6 : omega_inv_to_l (omega_inv ^ |rotation_last|) +/// word 7 : has_accumulator (0 or 1) +/// word 8 : acc_offset (instance index of the accumulator) +/// word 9 : num_acc_limbs +/// word 10 : num_acc_limb_bits +/// word 11..14 : G1_BASE (4 words, EIP-2537 padded) +/// word 15..22 : G2_BASE (8 words, EIP-2537 padded) +/// word 23..30 : NEG_S_G2_BASE (8 words, EIP-2537 padded) +/// word 31..30 + Q_PAYLOAD : quotient VM constants + packed bytecode +/// word 31 + Q_PAYLOAD .. : fixed_comms (4 words each) +/// word 31 + Q_PAYLOAD + 4*N_FIXED .. +/// : permutation_comms (4 words each) +/// +/// Notes: +/// - `extcodehash` of this contract is pinned by the linked verifier via +/// `EXPECTED_VK_CODEHASH`, so any byte tweak is detected at deploy time. +/// - The quotient identity interpreter's static program is stored in this +/// pinned VK runtime. The verifier reads it from memory after `extcodecopy`, +/// avoiding verifier-side PUSH32/mstore immediates while keeping the program +/// covered by `EXPECTED_VK_CODEHASH`. +/// - The midnight-proofs migration bakes the per-lookup chunk counts, trashcan +/// structure, and `num_simple_selectors` into the generated verifier code. +contract Halo2VerifyingKey { + /// @notice Deploy the verifying-key payload as this contract's runtime bytecode. + /// @dev The constructor writes an INVALID byte followed by generated words into memory and returns that prefixed runtime. + /// @dev The transient construction buffer starts at `0x80`, preserving Solidity's reserved memory words. + constructor() { + assembly { + // Runtime layout: + // byte 0 : INVALID, so the payload cannot be executed + // byte 1..end : generated VK payload copied by Halo2Verifier + // + // `runtime` includes the INVALID prefix; `payload` points to word + // zero of the verifier-key data described in the contract NatSpec. + let runtime := 0x80 + let payload := add(runtime, 0x01) + mstore8(runtime, 0xfe) + // Header, base-point, and quotient-program words generated from + // VkPayloadLayout. The inline names on each mstore identify the + // exact slot in the rendered source. + mstore(add(payload, 0x0000), 0x04d431b03dc86a4ddf0aef1f258576d6df8e1b884a7aa7564fea2df1b04a0f86) // vk_digest + mstore(add(payload, 0x0020), 0x000000000000000000000000000000000000000000000000000000000000000e) // num_instances + mstore(add(payload, 0x0040), 0x0000000000000000000000000000000000000000000000000000000000000014) // k + mstore(add(payload, 0x0060), 0x73eda0144f284aae5b6554d46c21576b363d4ec725be2bff1a400fff00001001) // n_inv + mstore(add(payload, 0x0080), 0x03e1c54bcb947035a57a6e07cb98de4a2f69e02d265e09d9fece7e0e39898d4b) // omega + mstore(add(payload, 0x00a0), 0x6c39442eade0092768ac033fa6f608750624a1bb17dbc026ef97c3573a28fc8c) // omega_inv + mstore(add(payload, 0x00c0), 0x2a0ccbaa0613f093f2bb6e97859513f0b613d8587eaa92db9e5604b8d6b68d45) // omega_inv_to_l + mstore(add(payload, 0x00e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // has_accumulator + mstore(add(payload, 0x0100), 0x0000000000000000000000000000000000000000000000000000000000000004) // acc_offset + mstore(add(payload, 0x0120), 0x0000000000000000000000000000000000000000000000000000000000000007) // num_acc_limbs + mstore(add(payload, 0x0140), 0x0000000000000000000000000000000000000000000000000000000000000038) // num_acc_limb_bits + mstore(add(payload, 0x0160), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) // g1_x_hi + mstore(add(payload, 0x0180), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) // g1_x_lo + mstore(add(payload, 0x01a0), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) // g1_y_hi + mstore(add(payload, 0x01c0), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) // g1_y_lo + mstore(add(payload, 0x01e0), 0x00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051) // g2_x_c0_hi + mstore(add(payload, 0x0200), 0xc6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8) // g2_x_c0_lo + mstore(add(payload, 0x0220), 0x0000000000000000000000000000000013e02b6052719f607dacd3a088274f65) // g2_x_c1_hi + mstore(add(payload, 0x0240), 0x596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e) // g2_x_c1_lo + mstore(add(payload, 0x0260), 0x000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351a) // g2_y_c0_hi + mstore(add(payload, 0x0280), 0xadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801) // g2_y_c0_lo + mstore(add(payload, 0x02a0), 0x000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99) // g2_y_c1_hi + mstore(add(payload, 0x02c0), 0xcb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be) // g2_y_c1_lo + mstore(add(payload, 0x02e0), 0x0000000000000000000000000000000007acb569b3187c0fd1993980aa52a6e9) // neg_s_g2_x_c0_hi + mstore(add(payload, 0x0300), 0xe2080b9697fab96abd5c5f1c3b988256f2d99366f1bbccf13cf0e20702fee18c) // neg_s_g2_x_c0_lo + mstore(add(payload, 0x0320), 0x0000000000000000000000000000000004bbe1a24fcc4f988c6ef268d0c1160e) // neg_s_g2_x_c1_hi + mstore(add(payload, 0x0340), 0xac0a0c4f53d80bd74f3d2e4667be27408625a83825354e27c70859883102eb43) // neg_s_g2_x_c1_lo + mstore(add(payload, 0x0360), 0x00000000000000000000000000000000091f5fc856da557cdb852412d3fd2cef) // neg_s_g2_y_c0_hi + mstore(add(payload, 0x0380), 0x9034c9a66ce38bf356c49f6a012109440035923a9cd6c71ca0c8efa5b6badf52) // neg_s_g2_y_c0_lo + mstore(add(payload, 0x03a0), 0x000000000000000000000000000000000af8fa5434d3fdd8c90fe8e532246c49) // neg_s_g2_y_c1_hi + mstore(add(payload, 0x03c0), 0x9926d8d728ccbb4ac40381158e8da573b4895782bfdb788c8ff40ba22032eab3) // neg_s_g2_y_c1_lo + mstore(add(payload, 0x03e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // quotient_const + mstore(add(payload, 0x0400), 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140) // quotient_const + mstore(add(payload, 0x0420), 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a) // quotient_const + mstore(add(payload, 0x0440), 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db) // quotient_const + mstore(add(payload, 0x0460), 0x00bbe1fbe9ef1e2d62490b03a82bf9ef10f5e9b2323033669cf6c50481f63e05) // quotient_const + mstore(add(payload, 0x0480), 0x0000000000000000000000000000000000000000000000000100000000000000) // quotient_const + mstore(add(payload, 0x04a0), 0x0000000000000000000000000000000000010000000000000000000000000000) // quotient_const + mstore(add(payload, 0x04c0), 0x0000000000000000000000000000000000000000000000000000000400000000) // quotient_const + mstore(add(payload, 0x04e0), 0x0000000000000000000000000000000000000000040000000000000000000000) // quotient_const + mstore(add(payload, 0x0500), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x0520), 0x0000000000000000000000000000000000000000000000100000000000000000) // quotient_const + mstore(add(payload, 0x0540), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000) // quotient_const + mstore(add(payload, 0x0560), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefeffffff00000001) // quotient_const + mstore(add(payload, 0x0580), 0x73eda753299d7d483339d80809a1d80553bca402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x05a0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffb00000001) // quotient_const + mstore(add(payload, 0x05c0), 0x73eda753299d7d483339d80809a1d80553bda402fbfe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x05e0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffefffff001) // quotient_const + mstore(add(payload, 0x0600), 0x73eda753299d7d483339d80809a1d80553bda402fffe5beeffffffff00000001) // quotient_const + mstore(add(payload, 0x0620), 0x00000000000000000000000000000010ff0726c5de281020ad8016cf6f691213) // quotient_const + mstore(add(payload, 0x0640), 0x0000000000000000000000000000002c64068790f917282347187665718b04c8) // quotient_const + mstore(add(payload, 0x0660), 0x00000000000000000000000000000027241bb5338dce8a77499428839473bf3a) // quotient_const + mstore(add(payload, 0x0680), 0x0000000000000000000000000000002b7c4a26a1c7ae6fc4b499d04e4a463c4b) // quotient_const + mstore(add(payload, 0x06a0), 0x000000000000000000000000000000274bc40fcf526be95333a8c22c79465298) // quotient_const + mstore(add(payload, 0x06c0), 0x0000000000000000000000000000002a5ee6db49930276e2939d1c43ac82f744) // quotient_const + mstore(add(payload, 0x06e0), 0x73eda753299d7d483339d80809a1d7edd77e26c51c38afb5debf8afa00c15cc3) // quotient_const + mstore(add(payload, 0x0700), 0x73eda753299d7d483339d80809a1d7c553bda402fffe5bfeffffffff00000002) // quotient_const + mstore(add(payload, 0x0720), 0x73eda753299d7d483339d80809a1d80553bda402fffe53ebc627fffef6280001) // quotient_const + mstore(add(payload, 0x0740), 0x0000000000000000000001000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0760), 0x0000000100000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0780), 0x6bc66e553973f396854f5626172ba135587d41e37a68209402355093fdcaaf6c) // quotient_const + mstore(add(payload, 0x07a0), 0x63f31e3f446953960c9d6964474300df43ab29179970f642a28e39d6c883c74b) // quotient_const + mstore(add(payload, 0x07c0), 0x73eda753299d7d483339d70809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x07e0), 0x73eda752299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x0800), 0x082738fdf02989b1adea81e1f27636cffb40621f85963b6afdcaaf6b02355095) // quotient_const + mstore(add(payload, 0x0820), 0x0ffa8913e53429b2269c6ea3c25ed72610127aeb668d65bc5d71c628377c38b6) // quotient_const + mstore(add(payload, 0x0840), 0x01ec1c0519185dfe86132d479c76d0786e1e037d0b05ca47648a1c5d29492c9b) // quotient_const + mstore(add(payload, 0x0860), 0x1e179025ca2470882b34e63940ccbd7ad9090bf414d43b696e093b5a8782528f) // quotient_const + mstore(add(payload, 0x0880), 0x4298bfee9a84c8ef8e83702075cb1abeb576f146636342e3db9ea6b0a4adf29d) // quotient_const + mstore(add(payload, 0x08a0), 0x3c83b078e9abed278d042acc8f3bd21e228716f04be96af8025da860e2d1bba9) // quotient_const + mstore(add(payload, 0x08c0), 0x427868260f487d1ef07edaadf37f5dbe705bd1318290f2577ae756b009c24f11) // quotient_const + mstore(add(payload, 0x08e0), 0x03020e6a35e595abd22838beeadc45cfcb0545d85ca0ab2c59d44c203fac84a7) // quotient_const + mstore(add(payload, 0x0900), 0x000000000000000000000000000000000000000000000000d201000000010000) // quotient_const + mstore(add(payload, 0x0920), 0x0000000100001b7c3f8d3fe3c5b448f1bdeb2ae34698b72d6ce966fc208c05ed) // quotient_const + mstore(add(payload, 0x0940), 0x73eda753299d7d483339d80809a1d7fd4057a4c12f26d1c1778e3360a6820001) // quotient_const + mstore(add(payload, 0x0960), 0x057797fa7060856f215654ff11006fe0acf6a437e9477bf6f782dfac86f2cf75) // quotient_const + mstore(add(payload, 0x0980), 0x0000000000000000000000000000000000000000000000000000000000000002) // quotient_const + mstore(add(payload, 0x09a0), 0x0000000000000000000000000000000000000000000000000200000000000000) // quotient_const + mstore(add(payload, 0x09c0), 0x0000000000000000000000000000000000020000000000000000000000000000) // quotient_const + mstore(add(payload, 0x09e0), 0x73eda753299d7d483339d80809a1d7e13511a4044eaa5bff4600ffff00005556) // quotient_const + mstore(add(payload, 0x0a00), 0x73eda753299d7d483339d80809a1d7c553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x0a20), 0x73eda753299d7d483339d80809a1d7d340dd972492de594de627fffefcb80349) // quotient_const + mstore(add(payload, 0x0a40), 0x73eda753299d7d483339d80809a1d7dbdc391ab4d8ac003bbd48021b82456c9b) // quotient_const + mstore(add(payload, 0x0a60), 0x73eda753299d7d483339d80809a1d7d1448ee5caf5eefcffbc9fabc57759e23f) // quotient_const + mstore(add(payload, 0x0a80), 0x73eda753299d7d483339d80809a1d80418c74cc18bb28443d3978d1fd47ffce1) // quotient_const + mstore(add(payload, 0x0aa0), 0x0000000000000000000000000000006425c019bcda40056233b00000068ff970) // quotient_const + mstore(add(payload, 0x0ac0), 0x00000000000000000000000000000052ef09129c4ea4b786856ffbc6fb7526cc) // quotient_const + mstore(add(payload, 0x0ae0), 0x73eda753299d7d483339d80809a1d7d89a085d30fc8a7106a170ac2377c34ab9) // quotient_const + mstore(add(payload, 0x0b00), 0x73eda753299d7d483339d80809a1d7f8ce65bb936f2d836012914aca65f077e1) // quotient_const + mstore(add(payload, 0x0b20), 0x000000000000000000000000000000681e5d7c70141ebdfe86c0a873114c3b84) // quotient_const + mstore(add(payload, 0x0b40), 0x000000000000000000000000000000297784894e27525bc342b7fde37dba9366) // quotient_const + mstore(add(payload, 0x0b60), 0x0000000000000000000000000000000275ecae82e897af7658d0e5be57000640) // quotient_const + mstore(add(payload, 0x0b80), 0x000000000000000000000000000000013af65741744bd7bb2c6872df2b800320) // quotient_const + mstore(add(payload, 0x0ba0), 0x00000000000000000000000000000059736a8da406e7d5f0bd1ea7b710796a90) // quotient_const + mstore(add(payload, 0x0bc0), 0x0000000000000000000000000000000c8557e86f90d0d89eed6eb5349a0f8820) // quotient_const + mstore(add(payload, 0x0be0), 0x0453ae02a5f228d8f956b5eab4fc92bbeea5eb26b6ae4b42b4fdfcfdf026aa22) // quotient_const + mstore(add(payload, 0x0c00), 0x0000000000000000000000000000000000000000000000000000000800000000) // quotient_const + mstore(add(payload, 0x0c20), 0x0000000000000000000000000000000000000000080000000000000000000000) // quotient_const + mstore(add(payload, 0x0c40), 0x0000000000000000000000000000000000000000000000000000000000002000) // quotient_const + mstore(add(payload, 0x0c60), 0x0000000000000000000000000000000000000000000000200000000000000000) // quotient_const + mstore(add(payload, 0x0c80), 0x73eda753299d7d483339d80809a1d7f454b67d3d21d64bde527fe92f9096edee) // quotient_const + mstore(add(payload, 0x0ca0), 0x73eda753299d7d483339d80809a1d7d8efb71c7206e733dbb8e789998e74fb39) // quotient_const + mstore(add(payload, 0x0cc0), 0x73eda753299d7d483339d80809a1d7de2fa1eecf722fd187b66bd77b6b8c40c7) // quotient_const + mstore(add(payload, 0x0ce0), 0x73eda753299d7d483339d80809a1d7d9d7737d61384fec3a4b662fb0b5b9c3b6) // quotient_const + mstore(add(payload, 0x0d00), 0x73eda753299d7d483339d80809a1d7de07f99433ad9272abcc573dd286b9ad69) // quotient_const + mstore(add(payload, 0x0d20), 0x73eda753299d7d483339d80809a1d7daf4d6c8b96cfbe51c6c62e3bb537d08bd) // quotient_const + mstore(add(payload, 0x0d40), 0x00000000000000000000000000000021fe0e4d8bbc5020415b002d9eded22426) // quotient_const + mstore(add(payload, 0x0d60), 0x00000000000000000000000000000058c80d0f21f22e50468e30eccae3160990) // quotient_const + mstore(add(payload, 0x0d80), 0x0000000000000000000000000000004e48376a671b9d14ee9328510728e77e74) // quotient_const + mstore(add(payload, 0x0da0), 0x00000000000000000000000000000056f8944d438f5cdf896933a09c948c7896) // quotient_const + mstore(add(payload, 0x0dc0), 0x0000000000000000000000000000004e97881f9ea4d7d2a667518458f28ca530) // quotient_const + mstore(add(payload, 0x0de0), 0x73eda753299d7d4833351088b4af7508df8b737010b26e15294bfcbb9194fffd) // quotient_const + mstore(add(payload, 0x0e00), 0x0000000000000000000002000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0e20), 0x0000000200000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0e40), 0x639f3557494a69e4d764d44424b56a655d3cdfc3f4d1e529046aa128fb955ed7) // quotient_const + mstore(add(payload, 0x0e60), 0x53f8952b5f3529e3e600fac084e429b93398ae2c32e39086451c73ae91078e95) // quotient_const + mstore(add(payload, 0x0e80), 0x72018b4e10851f49ad26aac06d2b078ce59fa085f4f891b79b75e3a1d6b6d366) // quotient_const + mstore(add(payload, 0x0ea0), 0x55d6172d5f790cc00804f1cec8d51a8a7ab4980eeb2a209591f6c4a4787dad72) // quotient_const + mstore(add(payload, 0x0ec0), 0x3154e7648f18b458a4b667e793d6bd469e46b2bc9c9b191b2461594e5b520d64) // quotient_const + mstore(add(payload, 0x0ee0), 0x3769f6da3ff19020a635ad3b7a6605e731368d12b414f106fda2579e1d2e4458) // quotient_const + mstore(add(payload, 0x0f00), 0x31753f2d1a55002942bafd5a16227a46e361d2d17d6d69a78518a94ef63db0f0) // quotient_const + mstore(add(payload, 0x0f20), 0x70eb98e8f3b7e79c61119f491ec5923588b85e2aa35db0d2a62bb3dec0537b5a) // quotient_const + mstore(add(payload, 0x0f40), 0x03d8380a3230bbfd0c265a8f38eda0f0dc3c06fa160b948ec91438ba52925936) // quotient_const + mstore(add(payload, 0x0f60), 0x3c2f204b9448e1105669cc7281997af5b21217e829a876d2dc1276b50f04a51e) // quotient_const + mstore(add(payload, 0x0f80), 0x1143d88a0b6c1496e9cd0838e1f45d7817303e89c6c829c8b73d4d62495be539) // quotient_const + mstore(add(payload, 0x0fa0), 0x0519b99ea9ba5d06e6ce7d9114d5cc36f15089dd97d479f104bb50c2c5a37751) // quotient_const + mstore(add(payload, 0x0fc0), 0x110328f8f4f37cf5adc3dd53dd5ce3778cf9fe60052388aff5cead6113849e21) // quotient_const + mstore(add(payload, 0x0fe0), 0x057797fa7060856f215655ff11006fee9a1697597c277945ddaadfac83aad2c0) // quotient_const + mstore(add(payload, 0x1000), 0x0000000000000000000000000000003212e00cde6d2002b119d800000347fcb8) // quotient_const + mstore(add(payload, 0x1020), 0x000000000000000000000000000000340f2ebe380a0f5eff4360543988a61dc2) // quotient_const + mstore(add(payload, 0x1040), 0x0000000000000000000000000000002cb9b546d20373eaf85e8f53db883cb548) // quotient_const + mstore(add(payload, 0x1060), 0x0453ae02a5f228d8f956b6eab50092aaff9ec460d8863b22077de62a80bd8812) // quotient_const + mstore(add(payload, 0x1080), 0x73eda753299d7d4833351088b4af7508df8b737010b26601ef73fcbb87bd0000) // quotient_const + mstore(add(payload, 0x10a0), 0x0aef2ff4e0c10ade42aca9fe2200e00159ed486fd28ef7edef05bf590de59ef3) // quotient_const + mstore(add(payload, 0x10c0), 0x0000000000000000000000000000000000000000000000000000000000000006) // quotient_const + mstore(add(payload, 0x10e0), 0x0000000000000000000000000000000000000000000000000600000000000000) // quotient_const + mstore(add(payload, 0x1100), 0x0000000000000000000000000000000000060000000000000000000000000000) // quotient_const + mstore(add(payload, 0x1120), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffffff) // quotient_const + mstore(add(payload, 0x1140), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefdffffff00000001) // quotient_const + mstore(add(payload, 0x1160), 0x73eda753299d7d483339d80809a1d80553bba402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1180), 0x0000000000000000000000000000000000000000000000000000000000000003) // quotient_const + mstore(add(payload, 0x11a0), 0x0000000000000000000000000000000000030000000000000000000000000000) // quotient_const + mstore(add(payload, 0x11c0), 0x0000000000000000000000000000012c71404d368ec010269b10000013afec50) // quotient_const + mstore(add(payload, 0x11e0), 0x000000000000000000000000000000f8cd1b37d4ebee2693904ff354f25f7464) // quotient_const + mstore(add(payload, 0x1200), 0x000000000000000000000000000001385b1875503c5c39fb9441f95933e4b28c) // quotient_const + mstore(add(payload, 0x1220), 0x0000000000000000000000000000007c668d9bea75f71349c827f9aa792fba32) // quotient_const + mstore(add(payload, 0x1240), 0x0000000000000000000000000000000761c60b88b9c70e630a72b13b050012c0) // quotient_const + mstore(add(payload, 0x1260), 0x73eda753299d7d483339d80809a1d7a12dfd8a4625be569ccc4ffffef9700691) // quotient_const + mstore(add(payload, 0x1280), 0x73eda753299d7d483339d80809a1d7b264b49166b159a4787a900438048ad935) // quotient_const + mstore(add(payload, 0x12a0), 0x00000000000000000000000000000003b0e305c45ce387318539589d82800960) // quotient_const + mstore(add(payload, 0x12c0), 0x0000000000000000000000000000010c5a3fa8ec14b781d2375bf725316c3fb0) // quotient_const + mstore(add(payload, 0x12e0), 0x000000000000000000000000000000259007b94eb27289dcc84c1f9dce2e9860) // quotient_const + mstore(add(payload, 0x1300), 0x73eda753299d7d483339d80809a1d79d35602792ebdf9e00793f578beeb3c47d) // quotient_const + mstore(add(payload, 0x1320), 0x73eda753299d7d483339d80809a1d802ddd0f5801766ac88a72f1a40a8fff9c1) // quotient_const + mstore(add(payload, 0x1340), 0x73eda753299d7d483339d80809a1d7abe053165ef916860e42e15847ef869571) // quotient_const + mstore(add(payload, 0x1360), 0x73eda753299d7d483339d80809a1d7ec490dd323de5caac125229595cbe0efc1) // quotient_const + mstore(add(payload, 0x1380), 0x08a75c054be451b1f2ad6bd569f92577dd4bd64d6d5c968569fbf9fbe04d544d) // quotient_const + mstore(add(payload, 0x13a0), 0x0000000000000000000000000000000000000000000000000000001800000000) // quotient_const + mstore(add(payload, 0x13c0), 0x0000000000000000000000000000000000000000180000000000000000000000) // quotient_const + mstore(add(payload, 0x13e0), 0x0000000000000000000000000000000000000000000000000000000000006000) // quotient_const + mstore(add(payload, 0x1400), 0x0000000000000000000000000000000000000000000000600000000000000000) // quotient_const + mstore(add(payload, 0x1420), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffff700000001) // quotient_const + mstore(add(payload, 0x1440), 0x73eda753299d7d483339d80809a1d80553bda402f7fe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1460), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffe001) // quotient_const + mstore(add(payload, 0x1480), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bdeffffffff00000001) // quotient_const + mstore(add(payload, 0x14a0), 0x73eda753299d7d483339d80809a1d7e355af567743ae3bbda4ffd260212ddbdb) // quotient_const + mstore(add(payload, 0x14c0), 0x73eda753299d7d483339d80809a1d7ac8bb094e10dd00bb871cf13341ce9f671) // quotient_const + mstore(add(payload, 0x14e0), 0x73eda753299d7d483339d80809a1d7b70b86399be46147106cd7aef7d718818d) // quotient_const + mstore(add(payload, 0x1500), 0x73eda753299d7d483339d80809a1d7ae5b2956bf70a17c7596cc5f626b73876b) // quotient_const + mstore(add(payload, 0x1520), 0x73eda753299d7d483339d80809a1d7b6bc3584645b26895898ae7ba60d735ad1) // quotient_const + mstore(add(payload, 0x1540), 0x73eda753299d7d483339d80809a1d7b095efed6fd9f96e39d8c5c777a6fa1179) // quotient_const + mstore(add(payload, 0x1560), 0x00000000000000000000000000000065fa2ae8a334f060c4110088dc9c766c72) // quotient_const + mstore(add(payload, 0x1580), 0x00000000000000000000000000000000000000000c0000000000000000000000) // quotient_const + mstore(add(payload, 0x15a0), 0x0000000000000000000000000000010a58272d65d68af0d3aa92c660a9421cb0) // quotient_const + mstore(add(payload, 0x15c0), 0x0000000000000000000000000000000000000000000000300000000000000000) // quotient_const + mstore(add(payload, 0x15e0), 0x000000000000000000000000000000ead8a63f3552d73ecbb978f3157ab67b5c) // quotient_const + mstore(add(payload, 0x1600), 0x000000000000000000000000000000852c1396b2eb457869d549633054a10e58) // quotient_const + mstore(add(payload, 0x1620), 0x00000000000000000000000000000104e9bce7caae169e9c3b9ae1d5bda569c2) // quotient_const + mstore(add(payload, 0x1640), 0x0000000000000000000000000000008274de73e5570b4f4e1dcd70eaded2b4e1) // quotient_const + mstore(add(payload, 0x1660), 0x000000000000000000000000000000ebc6985edbee8777f335f48d0ad7a5ef90) // quotient_const + mstore(add(payload, 0x1680), 0x0000000000000000000000000000007f1cb491dcb90764a7bad754cb0588e5cc) // quotient_const + mstore(add(payload, 0x16a0), 0x73eda753299d7d48333049095fbd120c6b5942dd2166802b5297f978232a0002) // quotient_const + mstore(add(payload, 0x16c0), 0x0000000000000000000006000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x16e0), 0x0000000600000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x1700), 0x4302515f88a4431e1fbaccbc5adc8f25703b5745de78f77d0d3fe37cf2c01c83) // quotient_const + mstore(add(payload, 0x1720), 0x140e70dbca64831b4b8f40317b68cd20f34ec27e98adf994cf555b0db316abbd) // quotient_const + mstore(add(payload, 0x1740), 0x73eda753299d7d483339d60809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1760), 0x73eda751299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1780), 0x104e71fbe05313635bd503c3e4ec6d9ff680c43f0b2c76d5fb955ed6046aa12a) // quotient_const + mstore(add(payload, 0x17a0), 0x1ff51227ca6853644d38dd4784bdae4c2024f5d6cd1acb78bae38c506ef8716c) // quotient_const + mstore(add(payload, 0x17c0), 0x70156f48f76cc14b27137d78d0b4371477819d08e9f2c77036ebc744ad6da6cb) // quotient_const + mstore(add(payload, 0x17e0), 0x37be870795549c37dcd00b9588085d0fa1ab8c1ad655e52c23ed8949f0fb5ae3) // quotient_const + mstore(add(payload, 0x1800), 0x62a9cec91e3168b1496ccfcf27ad7a8d3c8d65793936323648c2b29cb6a41ac8) // quotient_const + mstore(add(payload, 0x1820), 0x6ed3edb47fe320414c6b5a76f4cc0bce626d1a256829e20dfb44af3c3a5c88b0) // quotient_const + mstore(add(payload, 0x1840), 0x62ea7e5a34aa00528575fab42c44f48dc6c3a5a2fadad34f0a31529dec7b61e0) // quotient_const + mstore(add(payload, 0x1860), 0x6de98a7ebdd251f08ee9668a33e94c65bdb3185246bd05a64c5767be80a6f6b3) // quotient_const + mstore(add(payload, 0x1880), 0x0b88a81e969233f724730fadaac8e2d294b414ee4222bdac5b3caa2ef7b70ba2) // quotient_const + mstore(add(payload, 0x18a0), 0x0000000300000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x18c0), 0x409fb98f933d25e8d0038d4f7b2a98dbc278a3b57cfb0879943764202d0def59) // quotient_const + mstore(add(payload, 0x18e0), 0x43fe0c177a010031bf648c1cc285529323863340cc562ac9e7aaad86598b55df) // quotient_const + mstore(add(payload, 0x1900), 0x33cb899e22443dc4bd6718aaa5dd18684590bb9d54587d5a25b7e826dc13afab) // quotient_const + mstore(add(payload, 0x1920), 0x5a46b0715e6d5198819eb2abc26638708b1b23dc3e7cb23c4a1bb20f9686f7ad) // quotient_const + mstore(add(payload, 0x1940), 0x0f4d2cdbfd2f1714b46b78b33e8164a4d3f19d98c77d6dd30e31f24850ea65f3) // quotient_const + mstore(add(payload, 0x1960), 0x419d6a1793664a2e73d2a85da4119e5513d7a0cde3bde4e90718f923a87532fa) // quotient_const + mstore(add(payload, 0x1980), 0x33097aeadeda76e1094b97fb9816aa66a6edfb200f6a9a0fe16c08233a8dda63) // quotient_const + mstore(add(payload, 0x19a0), 0x09062b3ea1b0c1037678aa3cc094d16f610fd18915e201850d7ce460bf058df5) // quotient_const + mstore(add(payload, 0x19c0), 0x0456a29a706afacf2158850711006fe0acf6a437e9477bf6f782dfac86f2cf7b) // quotient_const + mstore(add(payload, 0x19e0), 0x0397cc06bc030aab970dabe70cd498bbeea8daaea65607bb6a872125fec74a10) // quotient_const + mstore(add(payload, 0x1a00), 0x73eda753299d7d4833351088b4af7508df8b737010b26e15294bfcbb91950003) // quotient_const + mstore(add(payload, 0x1a20), 0x0594e0109a0005958008060d000b0200010594a01194a01194a005950008060d) // quotient_program + mstore(add(payload, 0x1a40), 0x000b0300000594c01194c01194c005952008060d000b0300010594e01194e011) // quotient_program + mstore(add(payload, 0x1a60), 0x94e00595a008060d000b0300011b00001b000105958008109aa00594a01194a0) // quotient_program + mstore(add(payload, 0x1a80), 0x1195000d01060594c01194c01195200d02060594e01194e01195a00d03060d00) // quotient_program + mstore(add(payload, 0x1aa0), 0x0b0300011b000221020403070002000094a00594c00694e00795000895200095) // quotient_program + mstore(add(payload, 0x1ac0), 0x400595600695800995a00a95c00b95e00c96000d96200e96400f966010968011) // quotient_program + mstore(add(payload, 0x1ae0), 0x96a00796c00896e00997000a972094a00095400595600695800796c00896e009) // quotient_program + mstore(add(payload, 0x1b00), 0x97000a972094c00595400695600795800896c00996e00a970012972094e00695) // quotient_program + mstore(add(payload, 0x1b20), 0x400795600895800996c00a96e012970013972095000795400895600995800a96) // quotient_program + mstore(add(payload, 0x1b40), 0xc01296e013970014972095200895400995600a95801296c01396e01497001597) // quotient_program + mstore(add(payload, 0x1b60), 0x2095a00995400a95601295801396c01496e015970016972095c00a9540129560) // quotient_program + mstore(add(payload, 0x1b80), 0x1395801496c01596e01697001797201897401997800b04000121021a03070001) // quotient_program + mstore(add(payload, 0x1ba0), 0x000094a00594c00694e01b95001c95200095400595600695801d95a01e95c00b) // quotient_program + mstore(add(payload, 0x1bc0), 0x95e00c96000d96201f96402096602196802296a01b96c01c96e01d97001e9720) // quotient_program + mstore(add(payload, 0x1be0), 0x94a00095400595600695801b96c01c96e01d97001e972094c00595400695601b) // quotient_program + mstore(add(payload, 0x1c00), 0x95801c96c01d96e01e970023972094e00695401b95601c95801d96c01e96e023) // quotient_program + mstore(add(payload, 0x1c20), 0x970024972095001b95401c95601d95801e96c02396e024970025972095201c95) // quotient_program + mstore(add(payload, 0x1c40), 0x401d95601e95802396c02496e025970026972095a01d95401e95602395802496) // quotient_program + mstore(add(payload, 0x1c60), 0xc02596e026970027972095c01e95402395602495802596c02696e02797002897) // quotient_program + mstore(add(payload, 0x1c80), 0x202997400b0400011b000321022a01000009000995a00a95c00b95e00c96000d) // quotient_program + mstore(add(payload, 0x1ca0), 0x96200e96400f96600094a00594c00694e00795000895201096801196a0189740) // quotient_program + mstore(add(payload, 0x1cc0), 0x1997800b05000121022b01000008001d95a01e95c00b95e00c96000d96201f96) // quotient_program + mstore(add(payload, 0x1ce0), 0x402096600094a00594c00694e01b95001c95202196802296a02997400b050001) // quotient_program + mstore(add(payload, 0x1d00), 0x210397a02c0000000b2b0b94a00c94c00d94e02d95402e95602f95800b95e00c) // quotient_program + mstore(add(payload, 0x1d20), 0x96000d96203097403197600b94a095e00c94a096000d94a096200c94c095e00d) // quotient_program + mstore(add(payload, 0x1d40), 0x94c096003294c096a00d94e095e03294e096803394e096a03295009660339500) // quotient_program + mstore(add(payload, 0x1d60), 0x968034950096a032952096403395209660349520968035952096a00095409540) // quotient_program + mstore(add(payload, 0x1d80), 0x2e954095602f9540958006956095603695609720369580970037958097203295) // quotient_program + mstore(add(payload, 0x1da0), 0xa096203395a096403495a096603595a096803895a096a03295c096003395c096) // quotient_program + mstore(add(payload, 0x1dc0), 0x203495c096403595c096603895c096803995c096a03696c096e03796c097003a) // quotient_program + mstore(add(payload, 0x1de0), 0x96c097203b96e096e03a96e097003c96e097203d970097003e970097203f9720) // quotient_program + mstore(add(payload, 0x1e00), 0x97200d000b060000210397a04003080002150b94a00c94c00d94e00e95000f95) // quotient_program + mstore(add(payload, 0x1e20), 0x202d95402e95602f95801095a01195c00b95e00c96000d96200e96400f966010) // quotient_program + mstore(add(payload, 0x1e40), 0x96801196a04196c04296e043970044972094a00b95e00c96000d96200e96400f) // quotient_program + mstore(add(payload, 0x1e60), 0x96601096801196a094c00c95e00d96000e96200f96401096601196804596a094) // quotient_program + mstore(add(payload, 0x1e80), 0xe00d95e00e96000f96201096401196604596804696a095000e95e00f96001096) // quotient_program + mstore(add(payload, 0x1ea0), 0x201196404596604696804796a095200f95e01096001196204596404696604796) // quotient_program + mstore(add(payload, 0x1ec0), 0x804896a095400095402e95602f95804196c04296e043970044972095a01095e0) // quotient_program + mstore(add(payload, 0x1ee0), 0x1196004596204696404796604896804996a095c01195e0459600469620479640) // quotient_program + mstore(add(payload, 0x1f00), 0x4896604996804a96a01897401997800695609560419560958042956096c04395) // quotient_program + mstore(add(payload, 0x1f20), 0x6096e044956097004b95609720089580958043958096c044958096e04b958097) // quotient_program + mstore(add(payload, 0x1f40), 0x004c958097200a96c096c04b96c096e04c96c097004d96c097201396e096e04d) // quotient_program + mstore(add(payload, 0x1f60), 0x96e097004e96e0972015970097004f9700972017972097200d000b0600012103) // quotient_program + mstore(add(payload, 0x1f80), 0x97a05003080001150b94a00c94c00d94e01f95002095202d95402e95602f9580) // quotient_program + mstore(add(payload, 0x1fa0), 0x2195a02295c00b95e00c96000d96201f96402096602196802296a05196c05296) // quotient_program + mstore(add(payload, 0x1fc0), 0xe053970054972094a00b95e00c96000d96201f96402096602196802296a094c0) // quotient_program + mstore(add(payload, 0x1fe0), 0x0c95e00d96001f96202096402196602296805596a094e00d95e01f9600209620) // quotient_program + mstore(add(payload, 0x2000), 0x2196402296605596805696a095001f95e0209600219620229640559660569680) // quotient_program + mstore(add(payload, 0x2020), 0x5796a095202095e02196002296205596405696605796805896a095400095402e) // quotient_program + mstore(add(payload, 0x2040), 0x95602f95805196c05296e053970054972095a02195e022960055962056964057) // quotient_program + mstore(add(payload, 0x2060), 0x96605896805996a095c02295e05596005696205796405896605996805a96a029) // quotient_program + mstore(add(payload, 0x2080), 0x97400695609560519560958052956096c053956096e054956097005b95609720) // quotient_program + mstore(add(payload, 0x20a0), 0x1c9580958053958096c054958096e05b958097005c958097201e96c096c05b96) // quotient_program + mstore(add(payload, 0x20c0), 0xc096e05c96c097005d96c097202496e096e05d96e097005e96e0972026970097) // quotient_program + mstore(add(payload, 0x20e0), 0x005f9700972028972097200d000b060001210397a0600000000c390b94a00c94) // quotient_program + mstore(add(payload, 0x2100), 0xc00d94e03097403197600097a00097c00597e00698000b98a00c98c00d98e000) // quotient_program + mstore(add(payload, 0x2120), 0x954097c005954097e006954098000b954098a00c954098c00d954098e0059560) // quotient_program + mstore(add(payload, 0x2140), 0x97c006956097e061956098800c956098a00d956098c0329560996006958097c0) // quotient_program + mstore(add(payload, 0x2160), 0x61958098603b958098800d958098a0329580994033958099600095e097a00596) // quotient_program + mstore(add(payload, 0x2180), 0x0097a006962097a06196c098403b96c098606296c098803296c099203396c099) // quotient_program + mstore(add(payload, 0x21a0), 0x403496c099606196e098203b96e098406296e098603d96e098803296e0990033) // quotient_program + mstore(add(payload, 0x21c0), 0x96e099203496e099403596e0996061970098003b9700982062970098403d9700) // quotient_program + mstore(add(payload, 0x21e0), 0x9860639700988032970098e03397009900349700992035970099403897009960) // quotient_program + mstore(add(payload, 0x2200), 0x61972097e03b9720980062972098203d9720984063972098603f972098803297) // quotient_program + mstore(add(payload, 0x2220), 0x2098c033972098e034972099003597209920389720994039972099600d000b07) // quotient_program + mstore(add(payload, 0x2240), 0x0000210397a064020f000a001997800097a00097c00597e00698000798200898) // quotient_program + mstore(add(payload, 0x2260), 0x400998600a98800b98a00c98c00d98e00e99000f992097a00095e00596000696) // quotient_program + mstore(add(payload, 0x2280), 0x200796400896600996800a96a097c00095400595600695800796c00896e00997) // quotient_program + mstore(add(payload, 0x22a0), 0x000a972097e00595400695600795800896c00996e00a97001297209800069540) // quotient_program + mstore(add(payload, 0x22c0), 0x0795600895800996c00a96e012970013972098200795400895600995800a96c0) // quotient_program + mstore(add(payload, 0x22e0), 0x1296e013970014972098400895400995600a95801296c01396e0149700159720) // quotient_program + mstore(add(payload, 0x2300), 0x98600995400a95601295801396c01496e015970016972098800a954012956013) // quotient_program + mstore(add(payload, 0x2320), 0x95801496c01596e016970017972095400b98a00c98c00d98e00e99000f992010) // quotient_program + mstore(add(payload, 0x2340), 0x994011996095600c98a00d98c00e98e00f990010992011994045996095800d98) // quotient_program + mstore(add(payload, 0x2360), 0xa00e98c00f98e010990011992045994046996096c00e98a00f98c01098e01199) // quotient_program + mstore(add(payload, 0x2380), 0x0045992046994047996096e00f98a01098c01198e04599004699204799404899) // quotient_program + mstore(add(payload, 0x23a0), 0x6097001098a01198c04598e046990047992048994049996097201198a04598c0) // quotient_program + mstore(add(payload, 0x23c0), 0x4698e04799004899204999404a99600b94a00c94c00d94e00e95000f95201095) // quotient_program + mstore(add(payload, 0x23e0), 0xa01195c01897401099401199600d000b070001210397a065020f0009000097a0) // quotient_program + mstore(add(payload, 0x2400), 0x0097c00597e00698001b98201c98401d98601e98800b98a00c98c00d98e01f99) // quotient_program + mstore(add(payload, 0x2420), 0x0020992021994097a00095e00596000696201b96401c96601d96801e96a097c0) // quotient_program + mstore(add(payload, 0x2440), 0x0095400595600695801b96c01c96e01d97001e972097e00595400695601b9580) // quotient_program + mstore(add(payload, 0x2460), 0x1c96c01d96e01e970023972098000695401b95601c95801d96c01e96e0239700) // quotient_program + mstore(add(payload, 0x2480), 0x24972098201b95401c95601d95801e96c02396e024970025972098401c95401d) // quotient_program + mstore(add(payload, 0x24a0), 0x95601e95802396c02496e025970026972098601d95401e95602395802496c025) // quotient_program + mstore(add(payload, 0x24c0), 0x96e026970027972098801e95402395602495802596c02696e027970028972095) // quotient_program + mstore(add(payload, 0x24e0), 0x400b98a00c98c00d98e01f990020992021994022996095600c98a00d98c01f98) // quotient_program + mstore(add(payload, 0x2500), 0xe020990021992022994055996095800d98a01f98c02098e02199002299205599) // quotient_program + mstore(add(payload, 0x2520), 0x4056996096c01f98a02098c02198e022990055992056994057996096e02098a0) // quotient_program + mstore(add(payload, 0x2540), 0x2198c02298e055990056992057994058996097002198a02298c05598e0569900) // quotient_program + mstore(add(payload, 0x2560), 0x57992058994059996097202298a05598c05698e05799005899205999405a9960) // quotient_program + mstore(add(payload, 0x2580), 0x0b94a00c94c00d94e01f95002095202195a02295c02997402299600d000b0700) // quotient_program + mstore(add(payload, 0x25a0), 0x01210397a0660000000b2b6794a06894c06994e06a95406b95606c95806a95e0) // quotient_program + mstore(add(payload, 0x25c0), 0x6b96006c96203097403197606d94a094a06894a094c06994a094e06e94c094c0) // quotient_program + mstore(add(payload, 0x25e0), 0x6f94c095c06f94e095a07094e095c06f9500952070950095a071950095c07295) // quotient_program + mstore(add(payload, 0x2600), 0x20952071952095a073952095c06a954095e06b954096006c954096206b956095) // quotient_program + mstore(add(payload, 0x2620), 0xe06c9560960074956096a06c958095e0749580968075958096a07695a095a077) // quotient_program + mstore(add(payload, 0x2640), 0x95a095c07895c095c074960097207496209700759620972074964096e0759640) // quotient_program + mstore(add(payload, 0x2660), 0x9700799640972074966096c075966096e079966097007a9660972075968096c0) // quotient_program + mstore(add(payload, 0x2680), 0x79968096e07a968097007b968097207996a096c07a96a096e07b96a097007c96) // quotient_program + mstore(add(payload, 0x26a0), 0xa097200d000b080000210397a07d03080002156794a06894c06994e07e95007f) // quotient_program + mstore(add(payload, 0x26c0), 0x95206a95406b95606c95808095a08195c06a95e06b96006c9620829640839660) // quotient_program + mstore(add(payload, 0x26e0), 0x8496808596a08296c08396e084970085972094a06d94a06894c06994e07e9500) // quotient_program + mstore(add(payload, 0x2700), 0x7f95208095a08195c095406a95e06b96006c96208296408396608496808596a0) // quotient_program + mstore(add(payload, 0x2720), 0x95606b95e06c96008296208396408496608596808696a095806c95e082960083) // quotient_program + mstore(add(payload, 0x2740), 0x96208496408596608696808796a096c08295e083960084962085964086966087) // quotient_program + mstore(add(payload, 0x2760), 0x96808896a096e08395e08496008596208696408796608896808996a097008495) // quotient_program + mstore(add(payload, 0x2780), 0xe08596008696208796408896608996808a96a097208595e08696008796208896) // quotient_program + mstore(add(payload, 0x27a0), 0x408996608a96808b96a01897401997806e94c094c07e94c094e07f94c0950080) // quotient_program + mstore(add(payload, 0x27c0), 0x94c095208194c095a08c94c095c08d94e094e08094e095008194e095208c94e0) // quotient_program + mstore(add(payload, 0x27e0), 0x95a08e94e095c08f950095008c950095208e950095a090950095c09195209520) // quotient_program + mstore(add(payload, 0x2800), 0x90952095a092952095c09395a095a09495a095c09595c095c00d000b08000121) // quotient_program + mstore(add(payload, 0x2820), 0x0397a09603080001156794a06894c06994e09795009895206a95406b95606c95) // quotient_program + mstore(add(payload, 0x2840), 0x809995a09a95c06a95e06b96006c96209b96409c96609d96809e96a09b96c09c) // quotient_program + mstore(add(payload, 0x2860), 0x96e09d97009e972094a06d94a06894c06994e09795009895209995a09a95c095) // quotient_program + mstore(add(payload, 0x2880), 0x406a95e06b96006c96209b96409c96609d96809e96a095606b95e06c96009b96) // quotient_program + mstore(add(payload, 0x28a0), 0x209c96409d96609e96809f96a095806c95e09b96009c96209d96409e96609f96) // quotient_program + mstore(add(payload, 0x28c0), 0x80a096a096c09b95e09c96009d96209e96409f9660a09680a196a096e09c95e0) // quotient_program + mstore(add(payload, 0x28e0), 0x9d96009e96209f9640a09660a19680a296a097009d95e09e96009f9620a09640) // quotient_program + mstore(add(payload, 0x2900), 0xa19660a29680a396a097209e95e09f9600a09620a19640a29660a39680a496a0) // quotient_program + mstore(add(payload, 0x2920), 0x2997406e94c094c09794c094e09894c095009994c095209a94c095a0a594c095) // quotient_program + mstore(add(payload, 0x2940), 0xc0a694e094e09994e095009a94e09520a594e095a0a794e095c0a895009500a5) // quotient_program + mstore(add(payload, 0x2960), 0x95009520a7950095a0a9950095c0aa95209520a9952095a0ab952095c0ac95a0) // quotient_program + mstore(add(payload, 0x2980), 0x95a0ad95a095c0ae95c095c00d000b080001210397a0af0000000e100094a005) // quotient_program + mstore(add(payload, 0x29a0), 0x94c00694e06a95406b95606c95800095e00596000696203097403197600097c0) // quotient_program + mstore(add(payload, 0x29c0), 0x0597e00698000b954095406b954095606c954095800d95609560749560972074) // quotient_program + mstore(add(payload, 0x29e0), 0x9580970075958097207496c096e07596c097007996c097203396e096e07996e0) // quotient_program + mstore(add(payload, 0x2a00), 0x97007a96e0972035970097007b9700972039972097200d000b090000210397a0) // quotient_program + mstore(add(payload, 0x2a20), 0xb004010002150094a00594c00694e00795000895206a95406b95606c95800995) // quotient_program + mstore(add(payload, 0x2a40), 0xa00a95c00095e00596000696200796400896600996800a96a08296c08396e084) // quotient_program + mstore(add(payload, 0x2a60), 0x97008597200097c00597e00698000798200898400998600a988095400b95406b) // quotient_program + mstore(add(payload, 0x2a80), 0x95606c95808296c08396e08497008597201897401997800d9560956082956095) // quotient_program + mstore(add(payload, 0x2aa0), 0x8083956096c084956096e0859560970086956097200f9580958084958096c085) // quotient_program + mstore(add(payload, 0x2ac0), 0x958096e0869580970087958097201196c096c08696c096e08796c097008896c0) // quotient_program + mstore(add(payload, 0x2ae0), 0x97204696e096e08896e097008996e0972048970097008a970097204a97209720) // quotient_program + mstore(add(payload, 0x2b00), 0x0d000b090001210397a0b104010001150094a00594c00694e01b95001c95206a) // quotient_program + mstore(add(payload, 0x2b20), 0x95406b95606c95801d95a01e95c00095e00596000696201b96401c96601d9680) // quotient_program + mstore(add(payload, 0x2b40), 0x1e96a09b96c09c96e09d97009e97200097c00597e00698001b98201c98401d98) // quotient_program + mstore(add(payload, 0x2b60), 0x601e988095400b95406b95606c95809b96c09c96e09d97009e97202997400d95) // quotient_program + mstore(add(payload, 0x2b80), 0x6095609b956095809c956096c09d956096e09e956097009f9560972020958095) // quotient_program + mstore(add(payload, 0x2ba0), 0x809d958096c09e958096e09f95809700a0958097202296c096c09f96c096e0a0) // quotient_program + mstore(add(payload, 0x2bc0), 0x96c09700a196c097205696e096e0a196e09700a296e097205897009700a39700) // quotient_program + mstore(add(payload, 0x2be0), 0x97205a972097200d000b090001191f0000000000000000000000000000000000) // quotient_program + // Fixed-column commitment 0, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2c00), 0x00000000000000000000000000000000055f7961345dce7ce57401dd993cc81a) // fixed_comms[0].x_hi + mstore(add(payload, 0x2c20), 0xb4a8bb072416d10d143dbceefaa489acea245ef19b9b96fef5bf433eb3a11715) // fixed_comms[0].x_lo + mstore(add(payload, 0x2c40), 0x00000000000000000000000000000000123e8a257be057ec25558c37e4b17ce9) // fixed_comms[0].y_hi + mstore(add(payload, 0x2c60), 0x8e3b8e47d06f0961e4194860938f70e8f8f29e6c09dc697cb5c3486879220eaf) // fixed_comms[0].y_lo + // Fixed-column commitment 1, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2c80), 0x000000000000000000000000000000000dc17ef381e9c195813396905a4c7619) // fixed_comms[1].x_hi + mstore(add(payload, 0x2ca0), 0x326e62da20d14e245cf995ae353e85749ad599d8859cdf7ff0996b64ad724553) // fixed_comms[1].x_lo + mstore(add(payload, 0x2cc0), 0x0000000000000000000000000000000004af0ea1ccdc1cd0a2a638aa09f6e2ae) // fixed_comms[1].y_hi + mstore(add(payload, 0x2ce0), 0xe2fe34ad7bd96fb709c73d2d9705c120c1c78c8702fc136bcecb58a6efc4353d) // fixed_comms[1].y_lo + // Fixed-column commitment 2, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2d00), 0x0000000000000000000000000000000018da87ffd53a1cdfc243a1f594c7db5f) // fixed_comms[2].x_hi + mstore(add(payload, 0x2d20), 0x6a20adfd78c0e9d2dd4a3377189dcaf31f886eecc6bcfd19d7263ff57b36c01e) // fixed_comms[2].x_lo + mstore(add(payload, 0x2d40), 0x0000000000000000000000000000000001921c576e8a2684cc7521fbf6ec96c3) // fixed_comms[2].y_hi + mstore(add(payload, 0x2d60), 0xde86625c4429546e64909b0f415e9b7bcff50f6e96df84f795a6bf7c3fd0e085) // fixed_comms[2].y_lo + // Fixed-column commitment 3, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2d80), 0x000000000000000000000000000000001390c4b48f0af2e2a9332b1851fbb5d1) // fixed_comms[3].x_hi + mstore(add(payload, 0x2da0), 0xe13c168e64b12da23a13fb7b449e2f8d38dd385220d9a9cc7af8c1cb5d7e7364) // fixed_comms[3].x_lo + mstore(add(payload, 0x2dc0), 0x0000000000000000000000000000000019b9ac80c33724396a9da36abc8884bc) // fixed_comms[3].y_hi + mstore(add(payload, 0x2de0), 0x9adb1d0bf586080efd6ffd8520aab78d8d205c0a11726db029aecc899111d9a9) // fixed_comms[3].y_lo + // Fixed-column commitment 4, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2e00), 0x0000000000000000000000000000000015bc72a82a34331b999a01881b5c4b3b) // fixed_comms[4].x_hi + mstore(add(payload, 0x2e20), 0xb138f213ddd19ae669c32f961811a4164e6eec8fac46a36f5a4e04870f11a3d1) // fixed_comms[4].x_lo + mstore(add(payload, 0x2e40), 0x00000000000000000000000000000000197b5b9237d51d93dc155332b6330653) // fixed_comms[4].y_hi + mstore(add(payload, 0x2e60), 0xef5fa3e86ca7b8acaf6cb32697d8ececfbdac8b2ffe2f53e47bdc8ba9e3993b6) // fixed_comms[4].y_lo + // Fixed-column commitment 5, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2e80), 0x0000000000000000000000000000000013eb9d933b5284bfef6caf1f9e08ca59) // fixed_comms[5].x_hi + mstore(add(payload, 0x2ea0), 0x283ea2dbd92fc9a748e3761fff443680718b50347a55be560c0229972f7e0a24) // fixed_comms[5].x_lo + mstore(add(payload, 0x2ec0), 0x000000000000000000000000000000001100235c0764123ef1a72a76e2abc95c) // fixed_comms[5].y_hi + mstore(add(payload, 0x2ee0), 0x8af88c99dda9239bf71fca4d07b5fee527d576ad79c679035b5836347686d74b) // fixed_comms[5].y_lo + // Fixed-column commitment 6, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2f00), 0x000000000000000000000000000000000563bb1e4b8d6ac080403aa94fe32b7d) // fixed_comms[6].x_hi + mstore(add(payload, 0x2f20), 0xe1921a07b4a8ade8f03fbf6079d44191250a74ad822758d3f3c76ac1dcf34844) // fixed_comms[6].x_lo + mstore(add(payload, 0x2f40), 0x000000000000000000000000000000001660e1d7dc487ef2074c1cbcc53f96c4) // fixed_comms[6].y_hi + mstore(add(payload, 0x2f60), 0x90a10debafc892aa61fc1f5deccbe9e504f860b20ab154069fc7d9db6abd4b8c) // fixed_comms[6].y_lo + // Fixed-column commitment 7, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2f80), 0x0000000000000000000000000000000011d8086a3a74772d6977655ec27ea545) // fixed_comms[7].x_hi + mstore(add(payload, 0x2fa0), 0xb8f93c43434e29d5987c92a14aff1f1eaaf7301c5a7e3089af5e92d1f848d0ca) // fixed_comms[7].x_lo + mstore(add(payload, 0x2fc0), 0x0000000000000000000000000000000005dad0d11cdc8ea706cfc3def7d4b133) // fixed_comms[7].y_hi + mstore(add(payload, 0x2fe0), 0xc3589649c58c36b9396e7ba08d2dcce7e9ffc79c05293e88ffdcd779031bd430) // fixed_comms[7].y_lo + // Fixed-column commitment 8, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3000), 0x000000000000000000000000000000000c93bd7351261d64a616e0136a9d422b) // fixed_comms[8].x_hi + mstore(add(payload, 0x3020), 0xb749e19c58376c59582349ad89c4cd137403a5708d9c57caa9aa60a61ebac5eb) // fixed_comms[8].x_lo + mstore(add(payload, 0x3040), 0x000000000000000000000000000000000cbfb4f76cc2e2dd1cb5c3d5102d3b9a) // fixed_comms[8].y_hi + mstore(add(payload, 0x3060), 0xd4a9cd56cbcb6df99c181927f6448f16319d629b8e456d7c82888b8ebff6605c) // fixed_comms[8].y_lo + // Fixed-column commitment 9, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3080), 0x0000000000000000000000000000000008727a8cb32cb038513549fb17b3ba8d) // fixed_comms[9].x_hi + mstore(add(payload, 0x30a0), 0xd9695316ca607544e71a5430e6910b8fee65f6ad1a50f524c684c5e957c1c73e) // fixed_comms[9].x_lo + mstore(add(payload, 0x30c0), 0x000000000000000000000000000000000108a6377aea32a5e3bbce056526f625) // fixed_comms[9].y_hi + mstore(add(payload, 0x30e0), 0x33a146b087d572d2cdda900fca8baea1863367075e4b606110b6325be4397d52) // fixed_comms[9].y_lo + // Fixed-column commitment 10, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3100), 0x0000000000000000000000000000000004757ce24e0add6ba492d720099e2fbd) // fixed_comms[10].x_hi + mstore(add(payload, 0x3120), 0xc54defd9e931ec5af33e4a92ad1867c2f4d790e30268a97b42e74f65d1d26feb) // fixed_comms[10].x_lo + mstore(add(payload, 0x3140), 0x0000000000000000000000000000000010f63d4681250b4d2f91725c42a7993b) // fixed_comms[10].y_hi + mstore(add(payload, 0x3160), 0xaa140dc9f52cc57f62aa46386e696d66b92a24406de6a8dfb053ebf589cd908b) // fixed_comms[10].y_lo + // Fixed-column commitment 11, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3180), 0x00000000000000000000000000000000085b6410030ecdc020942851047cf237) // fixed_comms[11].x_hi + mstore(add(payload, 0x31a0), 0x9c9c9bb218540241434381109f505f21842aadb79e1290bf15d09779052c830e) // fixed_comms[11].x_lo + mstore(add(payload, 0x31c0), 0x0000000000000000000000000000000001662c17e52c0576a1daf532fd9b5d44) // fixed_comms[11].y_hi + mstore(add(payload, 0x31e0), 0xa3b693810486d8cc2b3231928889a5901b11f8de1ea0a8d7da54719bb1bacb39) // fixed_comms[11].y_lo + // Fixed-column commitment 12, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3200), 0x000000000000000000000000000000001842a8cec63398e3cd72495091717037) // fixed_comms[12].x_hi + mstore(add(payload, 0x3220), 0xed2687fe07e288a66dbddadb866eab042e668795097a24350329af3a2faa15e8) // fixed_comms[12].x_lo + mstore(add(payload, 0x3240), 0x000000000000000000000000000000000cee59973fde1d885353d9f171f17c99) // fixed_comms[12].y_hi + mstore(add(payload, 0x3260), 0xfd88d67302f6cec5cbcd7d8430f17485f2bbaa4a092e725824499b3fc0cf01bd) // fixed_comms[12].y_lo + // Fixed-column commitment 13, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3280), 0x0000000000000000000000000000000019d8a32c4ce7586146ff1ba0ac184d1e) // fixed_comms[13].x_hi + mstore(add(payload, 0x32a0), 0x8263acc4da9e972d5b111879f60990df5070a38e99c93a8e9f52e94ffc5e889e) // fixed_comms[13].x_lo + mstore(add(payload, 0x32c0), 0x00000000000000000000000000000000027e571331ef494a3859d52330b48271) // fixed_comms[13].y_hi + mstore(add(payload, 0x32e0), 0x9386739b55372fd11694634dc2fe32fc5ed8a01fbe87cdbbcfacb4aed4903fc3) // fixed_comms[13].y_lo + // Fixed-column commitment 14, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3300), 0x00000000000000000000000000000000017d226d304f23f7f52ffe5adadcaca9) // fixed_comms[14].x_hi + mstore(add(payload, 0x3320), 0x54a9d37b66adcd661144899193c80d6d365b30bd133f0bd5a60c12da38b4685e) // fixed_comms[14].x_lo + mstore(add(payload, 0x3340), 0x000000000000000000000000000000000f485636ff7bd4a83beb91de87df7ed4) // fixed_comms[14].y_hi + mstore(add(payload, 0x3360), 0x4757ccb9b4c2999112e89a82d9c72579b1d0020cdda10f3e0b41bf57aec34686) // fixed_comms[14].y_lo + // Fixed-column commitment 15, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3380), 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[15].x_hi + mstore(add(payload, 0x33a0), 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[15].x_lo + mstore(add(payload, 0x33c0), 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[15].y_hi + mstore(add(payload, 0x33e0), 0x0000000000000000000000000000000000000000000000000000000000000000) // fixed_comms[15].y_lo + // Fixed-column commitment 16, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3400), 0x0000000000000000000000000000000008fd1060bd58dfc0e828393d62e3aee0) // fixed_comms[16].x_hi + mstore(add(payload, 0x3420), 0xfe8983d71d04414402f77df4de23f2a73b3c5c48ee85fa51fcd64a3027e0167b) // fixed_comms[16].x_lo + mstore(add(payload, 0x3440), 0x000000000000000000000000000000001024eab25af80d874e553df0f690d9e3) // fixed_comms[16].y_hi + mstore(add(payload, 0x3460), 0x634a41f5518caaaeee1dbc993ced1264ade8431a680d202247d83eb8ef1ed620) // fixed_comms[16].y_lo + // Fixed-column commitment 17, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3480), 0x000000000000000000000000000000000b877a879e46bc947071a221c60797d8) // fixed_comms[17].x_hi + mstore(add(payload, 0x34a0), 0x0d338c66213b76689a66c79be8734280510aa45f8c79ce0eaaba3d7f2c201c99) // fixed_comms[17].x_lo + mstore(add(payload, 0x34c0), 0x000000000000000000000000000000000ffcad707a79c0b29c100d2ab1b60935) // fixed_comms[17].y_hi + mstore(add(payload, 0x34e0), 0x2a2c605009875c39b73bb6d3e2216b305369028923566fc6ef2d22c8b74ce2bf) // fixed_comms[17].y_lo + // Fixed-column commitment 18, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3500), 0x000000000000000000000000000000000d0d0b7c841523297c824431ffca522d) // fixed_comms[18].x_hi + mstore(add(payload, 0x3520), 0xb24daa6cbd8c5f783501a4681bf7367e7aa5d71fca25234cd1a8f31ff24b9871) // fixed_comms[18].x_lo + mstore(add(payload, 0x3540), 0x000000000000000000000000000000001396209d456313b44ec6d4a2fe5f434f) // fixed_comms[18].y_hi + mstore(add(payload, 0x3560), 0xd5bd61218a04eaee084c376ade74d9bceba17fb9e12c2341581e5279596d7c97) // fixed_comms[18].y_lo + // Fixed-column commitment 19, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3580), 0x00000000000000000000000000000000136cd0a2afc84eaecd680d24627d704f) // fixed_comms[19].x_hi + mstore(add(payload, 0x35a0), 0x7e67e5d7fa3094f1aac03b8600277d394817f19fd0b0810dd2087b359429bf11) // fixed_comms[19].x_lo + mstore(add(payload, 0x35c0), 0x0000000000000000000000000000000015cedd2e0ff3e776d58981915174cb65) // fixed_comms[19].y_hi + mstore(add(payload, 0x35e0), 0x13bbf7d0f959d6be24d858ac4ae78c6b9ba0c80d69183a84cd73ec4e47693a14) // fixed_comms[19].y_lo + // Fixed-column commitment 20, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3600), 0x0000000000000000000000000000000010e71146f473749481916e34fff2ba4a) // fixed_comms[20].x_hi + mstore(add(payload, 0x3620), 0xd3e26bb933c011913f506f7892a36f43f7ba4f88b3f1c632e707bbffede7feb9) // fixed_comms[20].x_lo + mstore(add(payload, 0x3640), 0x000000000000000000000000000000000982bd3e7a5ba58d468d0835936ad925) // fixed_comms[20].y_hi + mstore(add(payload, 0x3660), 0xa3d7160336e586296b5e3742251d002e79d8d8d409bf94bafd09719f3ff0b382) // fixed_comms[20].y_lo + // Fixed-column commitment 21, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3680), 0x00000000000000000000000000000000141c94b0741a3d6471b0dd59b3420d9e) // fixed_comms[21].x_hi + mstore(add(payload, 0x36a0), 0xa03be602a9ebe7f1567966db7e3ace5a14ae393194015380293d1d995596651c) // fixed_comms[21].x_lo + mstore(add(payload, 0x36c0), 0x000000000000000000000000000000000185758fd177d9c06fad9502b24ca417) // fixed_comms[21].y_hi + mstore(add(payload, 0x36e0), 0x9cbb2dd41d7ede5248fb78316c557683f183c083b731a261a7381d45cf930742) // fixed_comms[21].y_lo + // Fixed-column commitment 22, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3700), 0x0000000000000000000000000000000001e93db65a35232bfd9581766d6c5c59) // fixed_comms[22].x_hi + mstore(add(payload, 0x3720), 0xa90a6d1906f94fc5643024253805f7a17b6fc2428bbe864add9dd8124518153c) // fixed_comms[22].x_lo + mstore(add(payload, 0x3740), 0x000000000000000000000000000000001897a8f562cb20282c670eecf5f77249) // fixed_comms[22].y_hi + mstore(add(payload, 0x3760), 0x4df0f2823207b6a3832a1b0d7987b20827e8416b563efbbfe212f510acf289df) // fixed_comms[22].y_lo + // Fixed-column commitment 23, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3780), 0x000000000000000000000000000000001141339a865a8fc5477605fae30566b9) // fixed_comms[23].x_hi + mstore(add(payload, 0x37a0), 0x7bd7c27d8d218efa4eb28c80916844e8ec5d47c7dc2f871545707f004b7bde50) // fixed_comms[23].x_lo + mstore(add(payload, 0x37c0), 0x000000000000000000000000000000000a728fcbfa5a7a5bfae84b4ab18b83e6) // fixed_comms[23].y_hi + mstore(add(payload, 0x37e0), 0xb10f17f88be257afb9d981d6516cfd2c4b3e89f5fa5ca13434fd95b24bc0f5c1) // fixed_comms[23].y_lo + // Fixed-column commitment 24, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3800), 0x0000000000000000000000000000000002a788024d9035e7e40f601bb8082ff8) // fixed_comms[24].x_hi + mstore(add(payload, 0x3820), 0x53ec39d899cd245ad2ee9ad32549a184d0c5cb4d4d99940a1cda52c4aa50f79d) // fixed_comms[24].x_lo + mstore(add(payload, 0x3840), 0x0000000000000000000000000000000019a91d8fc2a4d3db45f22db05108197e) // fixed_comms[24].y_hi + mstore(add(payload, 0x3860), 0x7eac8129c7add70a75761fd1e18fa2f40ea6e687a3f43b41995a2e74bf31ff24) // fixed_comms[24].y_lo + // Fixed-column commitment 25, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3880), 0x0000000000000000000000000000000001442c60b19c670229debddb76233f20) // fixed_comms[25].x_hi + mstore(add(payload, 0x38a0), 0x6a6627da0dc462833c112cdb1784e1f5f8a9aec196cf07b4442d408dbdc8de34) // fixed_comms[25].x_lo + mstore(add(payload, 0x38c0), 0x0000000000000000000000000000000013f3b855645d02a97df448668efd5d7f) // fixed_comms[25].y_hi + mstore(add(payload, 0x38e0), 0x8d52d302c0337d44e292fff6ae85d5a6dc5a59067e2a4f0925f48080dc521d40) // fixed_comms[25].y_lo + // Fixed-column commitment 26, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3900), 0x00000000000000000000000000000000190429c7a977675dbb8d96b442fe3eb0) // fixed_comms[26].x_hi + mstore(add(payload, 0x3920), 0xb466636ed724932185b8820e3f115e95ce19cde31f721358e32beda77fa44c98) // fixed_comms[26].x_lo + mstore(add(payload, 0x3940), 0x00000000000000000000000000000000058d18d63ff3a3abf337b6edc6a7709a) // fixed_comms[26].y_hi + mstore(add(payload, 0x3960), 0x36a019565bfbe01076597121c5484c2f3c63a301ed971058d75bb5d61b50bf05) // fixed_comms[26].y_lo + // Permutation commitment 0, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3980), 0x00000000000000000000000000000000120ea7ffaddae135109dc68a0169e39e) // permutation_comms[0].x_hi + mstore(add(payload, 0x39a0), 0x17f95865ea6411611a36d6ee8affde13b3120990c5feff3a2dc32b1b71606512) // permutation_comms[0].x_lo + mstore(add(payload, 0x39c0), 0x000000000000000000000000000000000d8604c2a5312ba81e5c56ad904e3fd3) // permutation_comms[0].y_hi + mstore(add(payload, 0x39e0), 0xc73a292e67febcd994e3d882c050e50940bcd0364b49eaf0ea8c838123757e51) // permutation_comms[0].y_lo + // Permutation commitment 1, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3a00), 0x0000000000000000000000000000000012baec9d926370da0174d2da9125909f) // permutation_comms[1].x_hi + mstore(add(payload, 0x3a20), 0x51c030289c7267c9173faacb20c43e027ba889a257fc9da7685fe92a1d8e1159) // permutation_comms[1].x_lo + mstore(add(payload, 0x3a40), 0x00000000000000000000000000000000151c6bdabf6387e09e4ab927a972d0a5) // permutation_comms[1].y_hi + mstore(add(payload, 0x3a60), 0x93e74bf9513d1d9be31b2d8828eb248d8b76fd529ee3d3343629b1327f47b74c) // permutation_comms[1].y_lo + // Permutation commitment 2, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3a80), 0x000000000000000000000000000000000502f4f83331ce4fe3b1e618b27af0d4) // permutation_comms[2].x_hi + mstore(add(payload, 0x3aa0), 0xed1c9014937f4e0c989f42c2a22d0d54e395b1e6d35830f1681e7f7633bd5e48) // permutation_comms[2].x_lo + mstore(add(payload, 0x3ac0), 0x00000000000000000000000000000000158fff4ecfaf728c449c4f9955fe87b1) // permutation_comms[2].y_hi + mstore(add(payload, 0x3ae0), 0x8938d2b58f9f4325ad6c56ef82526e5d6bedff11dd4ef4730c55430e7ecc12b7) // permutation_comms[2].y_lo + // Permutation commitment 3, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3b00), 0x00000000000000000000000000000000157dbf7d9e1605bb29df570e4e4165d2) // permutation_comms[3].x_hi + mstore(add(payload, 0x3b20), 0xe3af202d72afd27c2516ad10f0ef973f33dddb0fb0e34df741b4385b713cd1a8) // permutation_comms[3].x_lo + mstore(add(payload, 0x3b40), 0x000000000000000000000000000000001227de928658870ba5ecaeae8dc6272e) // permutation_comms[3].y_hi + mstore(add(payload, 0x3b60), 0x3c0f65f9d0e0daf9bcae0c8d2f61f4dfe5603cd4f9c2efe24c8bb68df6091f8b) // permutation_comms[3].y_lo + // Permutation commitment 4, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3b80), 0x00000000000000000000000000000000095679f4757e1699305ce877a6cede75) // permutation_comms[4].x_hi + mstore(add(payload, 0x3ba0), 0xa7e634400e785e5c802142e6df51aa4b9d707d525cf7a80209699c36020b7971) // permutation_comms[4].x_lo + mstore(add(payload, 0x3bc0), 0x00000000000000000000000000000000044f9e79c7622ffb279d558f84e0e7ec) // permutation_comms[4].y_hi + mstore(add(payload, 0x3be0), 0x8355f52668f5832af0d5f80a018da5d9cf622b4ad414ef10eaad16f94ddc8def) // permutation_comms[4].y_lo + // Permutation commitment 5, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3c00), 0x000000000000000000000000000000000dc0d3a9a27cacb14bf99d6c879fc0f2) // permutation_comms[5].x_hi + mstore(add(payload, 0x3c20), 0x92f80d3f8becaa901863aa7ca5b046bbe0f61b5da880eb4b1bb5b40ecf735d54) // permutation_comms[5].x_lo + mstore(add(payload, 0x3c40), 0x00000000000000000000000000000000077a8a36b30f3ba3d444bc0427088640) // permutation_comms[5].y_hi + mstore(add(payload, 0x3c60), 0x8840d2e5d575557d7495f63d40abf4b7daa04fc2bd662f6e299fc27aca5de4c5) // permutation_comms[5].y_lo + // Permutation commitment 6, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3c80), 0x0000000000000000000000000000000000986f7215a9b5e4bc69608acaeb755c) // permutation_comms[6].x_hi + mstore(add(payload, 0x3ca0), 0xc0ed4dd7bd88976da07e9c47756f1c9b90ef494c5cc3031ac6c9b5570fe5c45d) // permutation_comms[6].x_lo + mstore(add(payload, 0x3cc0), 0x0000000000000000000000000000000003658180fe0ac3ab217301cc34d2f9aa) // permutation_comms[6].y_hi + mstore(add(payload, 0x3ce0), 0x04b19a3be7154ad43d1737fd1668783a7069648dfb24de83a6b13d7261001032) // permutation_comms[6].y_lo + // Permutation commitment 7, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3d00), 0x0000000000000000000000000000000001a53f321f3bf6d20af84c660efc018e) // permutation_comms[7].x_hi + mstore(add(payload, 0x3d20), 0xa56fe987917489d477e3f2ac4da73c5a1e032748de091337870f0d5405473cd7) // permutation_comms[7].x_lo + mstore(add(payload, 0x3d40), 0x000000000000000000000000000000000cc1da20073f989dd769a6d1003df07d) // permutation_comms[7].y_hi + mstore(add(payload, 0x3d60), 0x5f354c49e670c02fcd04d732bf1dcf1ea91bf6fda6ccaf7b3884498827925ad7) // permutation_comms[7].y_lo + // Permutation commitment 8, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3d80), 0x0000000000000000000000000000000006416291ebc77017412205eb4523f2c4) // permutation_comms[8].x_hi + mstore(add(payload, 0x3da0), 0xfd559ac96201738085b756438d69e28d22d12d42527f26d4d076768379361baf) // permutation_comms[8].x_lo + mstore(add(payload, 0x3dc0), 0x000000000000000000000000000000000d7c5e9e2123f1a9259e09115e8bcbde) // permutation_comms[8].y_hi + mstore(add(payload, 0x3de0), 0x993cb20b6226d0b408d9ba2ea78f60939c10a918d0b706c0069e74ba1eeb7587) // permutation_comms[8].y_lo + // Permutation commitment 9, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3e00), 0x00000000000000000000000000000000155b9dd46792d67982688c923afe6f78) // permutation_comms[9].x_hi + mstore(add(payload, 0x3e20), 0xf0a9671faf2fbd90216a37854e9cc8f38a4e4e2edfbec79eabb17473cb14233a) // permutation_comms[9].x_lo + mstore(add(payload, 0x3e40), 0x000000000000000000000000000000001136f0b77aaf0d619c1ab6b1f0dcee68) // permutation_comms[9].y_hi + mstore(add(payload, 0x3e60), 0x5a362fa96be37353acf9f0ad8061c2da4ea54d0a983b25c140fff64eb5814a43) // permutation_comms[9].y_lo + // Permutation commitment 10, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3e80), 0x00000000000000000000000000000000190c34b5f99861f6dfcdde40fbfd95d0) // permutation_comms[10].x_hi + mstore(add(payload, 0x3ea0), 0x95a6bd47b58b054fe90452e3b358cd4dba6dd76fa6d5877d1ad1a6dc5f2c2ad7) // permutation_comms[10].x_lo + mstore(add(payload, 0x3ec0), 0x00000000000000000000000000000000025a2fa63f92a2b0012325d053fb4dd7) // permutation_comms[10].y_hi + mstore(add(payload, 0x3ee0), 0xeea7e8f98e3d57f1d404d8c6266072c77e1bf610f1b2d7e66a24c4eb5c7fa858) // permutation_comms[10].y_lo + // Permutation commitment 11, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3f00), 0x0000000000000000000000000000000006dba7402f78627c2b84e3f197be1ff9) // permutation_comms[11].x_hi + mstore(add(payload, 0x3f20), 0xe3b778c0de9938ede71f40c70a82c4b21e7f7e17f5ae37d8f431d8c56cb0cb7d) // permutation_comms[11].x_lo + mstore(add(payload, 0x3f40), 0x0000000000000000000000000000000002f707f86413969433104530051e0e7f) // permutation_comms[11].y_hi + mstore(add(payload, 0x3f60), 0xef774edc804973bdd3807446ef32363e657593d67b0460a33e76fc23d9feb998) // permutation_comms[11].y_lo + // Permutation commitment 12, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3f80), 0x00000000000000000000000000000000116f8891da6e5ddfca44225df76baa02) // permutation_comms[12].x_hi + mstore(add(payload, 0x3fa0), 0x795f3f817116f937e2102b4196eb86112a0d3290a6811fa1e7ada37bdcd91129) // permutation_comms[12].x_lo + mstore(add(payload, 0x3fc0), 0x0000000000000000000000000000000012b7e92df964086ecd115e6b47daad77) // permutation_comms[12].y_hi + mstore(add(payload, 0x3fe0), 0xcf396451a41cf55fa02fe68aaea4416953ad112a6608797bcc1f4a06ec6a786f) // permutation_comms[12].y_lo + // Permutation commitment 13, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4000), 0x000000000000000000000000000000000c92a9011db64f5f7857340d5ecc0e76) // permutation_comms[13].x_hi + mstore(add(payload, 0x4020), 0x6d6a2f4362053c3253503813a1a4167c6848cdbae2c90b104fc2a05290a24f78) // permutation_comms[13].x_lo + mstore(add(payload, 0x4040), 0x00000000000000000000000000000000143212478a1a01c12e08520644d09291) // permutation_comms[13].y_hi + mstore(add(payload, 0x4060), 0x4b417957d5c80179f30c06b68b329aa111de3f613cc66c70fd4841ec26139999) // permutation_comms[13].y_lo + // Permutation commitment 14, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4080), 0x000000000000000000000000000000000a03f34520ffafc5b6a3f90f70e4e373) // permutation_comms[14].x_hi + mstore(add(payload, 0x40a0), 0x673b3105b9864666ae11b398378b0648279065fc693c5edc4d42da133102545c) // permutation_comms[14].x_lo + mstore(add(payload, 0x40c0), 0x00000000000000000000000000000000073205a58fda6d5adfd59e82867a548c) // permutation_comms[14].y_hi + mstore(add(payload, 0x40e0), 0x80b028b16770d9a79d949f623d043d9aeff1cac440bd1482d1c898d7fc7bbbce) // permutation_comms[14].y_lo + // Permutation commitment 15, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4100), 0x0000000000000000000000000000000008f70210c58006a3cb8b927287fa0bfb) // permutation_comms[15].x_hi + mstore(add(payload, 0x4120), 0x55da954a29534ecb479d764e8ca977440dee39b3d09ebbe6b3fbb9c5b92cfe8b) // permutation_comms[15].x_lo + mstore(add(payload, 0x4140), 0x0000000000000000000000000000000007285c63d0eadb56452c598285dfaa89) // permutation_comms[15].y_hi + mstore(add(payload, 0x4160), 0x7d569c04ddbe85aa5deb978df472a328eb6db26586db41278f2cddeee55568cf) // permutation_comms[15].y_lo + // Permutation commitment 16, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4180), 0x0000000000000000000000000000000019e4494289915c32cc82722cdf9cd7f2) // permutation_comms[16].x_hi + mstore(add(payload, 0x41a0), 0xdc0187db7a50b887b1b5b2003c62effcf00f99c2a920ec56cfee0462655ac108) // permutation_comms[16].x_lo + mstore(add(payload, 0x41c0), 0x000000000000000000000000000000001933cb285d7f70cc217fdf83c792d776) // permutation_comms[16].y_hi + mstore(add(payload, 0x41e0), 0x527c8109795e8922038ac6a98e67c92d0fcb6e3a700e7ec2c0fa98cc1df52702) // permutation_comms[16].y_lo + // Permutation commitment 17, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4200), 0x000000000000000000000000000000000485fd87bac44c42dc3c80fd042cc7d0) // permutation_comms[17].x_hi + mstore(add(payload, 0x4220), 0xbb99316753b744e151e09c7f07e670300181f2f8e79a8b37c9828ac6be7a9573) // permutation_comms[17].x_lo + mstore(add(payload, 0x4240), 0x000000000000000000000000000000000fd370bb45717fa6282230adcff6e4b6) // permutation_comms[17].y_hi + mstore(add(payload, 0x4260), 0xab0ead4db625a61209a55551a96a3b5618f2996e645f09bbfda12ca3881536ac) // permutation_comms[17].y_lo + + // Return exactly the INVALID prefix plus the generated payload. The + // linked verifier pins this byte length and the resulting codehash. + return(runtime, 0x4281) + } + } +} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/ivc/README.md b/proofs/solidity-verifier/fixtures/ivc/README.md new file mode 100644 index 000000000..350fbe00f --- /dev/null +++ b/proofs/solidity-verifier/fixtures/ivc/README.md @@ -0,0 +1,48 @@ +# IVC Public-Accumulator Replay Fixture + +Pre-rendered artifacts for `tests/ivc_accumulator_replay.rs`, which replays a +real IVC final proof and then mutates the accumulator public inputs to check the +decoder in `templates/partials/verifier/AccumulatorHelpers.yul` rejects them. + +These are *rendered* contracts plus matching calldata rather than a verifying +key and proof, so the replay needs only solc and revm -- no SRS, no proving run, +and no `midnight-aggregation`. A verifier cannot be rendered from a VK without +the full SRS, because `SolidityGenerator` consumes `params.g_lagrange()`; that +is what makes a vk.bin-based replay unusable in CI. + +## Provenance + +| Field | Value | +| --- | --- | +| Source commit | `f5b7466` | +| Rendered by | `tests/ivc_keccak_solidity.rs` (`ivc_final_keccak_solidity_e2e`) | +| Circuit | IVC k=19 leaves, k=20 decider | +| Accumulator | `AccumulatorEncoding::new(offset=4, num_limbs=7, num_limb_bits=56)` | +| Verified on-chain | yes, 1,285,274 gas under revm Prague | + +## Regenerating + +Requires `midnight-srs-2p19` and `midnight-srs-2p20` in `SRS_DIR` +(~300 MB, from ): + +```bash +HALO2_SOLIDITY_RUN_IVC_BENCH=1 \ + SRS_DIR=/path/to/midfall/zk_stdlib/examples/assets \ + cargo test --release \ + --features evm,truncated-challenges,in-circuit-fewer-point-sets \ + --test ivc_keccak_solidity -- --nocapture + +cp target/ivc-keccak-solidity-dump/{Halo2Verifier.sol,Halo2VerifyingKey.sol,\ +Halo2QuotientEvaluator.sol,calldata.bin} fixtures/ivc/ +``` + +Then update the source commit above. + +## Staleness + +This is a snapshot of the codegen that produced it. The artifacts are +self-consistent, so the replay keeps passing after a codegen change -- it just +stops exercising current output. Detecting that automatically would mean +re-rendering, which needs the SRS again, so it is tracked by the commit stamp +above rather than by an assertion. Regenerate after changes to the accumulator +templates or the memory layout. diff --git a/proofs/solidity-verifier/fixtures/ivc/calldata.bin b/proofs/solidity-verifier/fixtures/ivc/calldata.bin new file mode 100644 index 000000000..9aed1ca89 Binary files /dev/null and b/proofs/solidity-verifier/fixtures/ivc/calldata.bin differ diff --git a/proofs/solidity-verifier/fixtures/ivc/instance.bin b/proofs/solidity-verifier/fixtures/ivc/instance.bin deleted file mode 100644 index 9247d55a5..000000000 Binary files a/proofs/solidity-verifier/fixtures/ivc/instance.bin and /dev/null differ diff --git a/proofs/solidity-verifier/fixtures/ivc/proof.bin b/proofs/solidity-verifier/fixtures/ivc/proof.bin deleted file mode 100644 index 910bd9d65..000000000 Binary files a/proofs/solidity-verifier/fixtures/ivc/proof.bin and /dev/null differ diff --git a/proofs/solidity-verifier/fixtures/ivc/rust_trace.json b/proofs/solidity-verifier/fixtures/ivc/rust_trace.json deleted file mode 100644 index 3f5fe0189..000000000 --- a/proofs/solidity-verifier/fixtures/ivc/rust_trace.json +++ /dev/null @@ -1,1068 +0,0 @@ -{ - "entries": [ - { - "kind": "Intermediate", - "data": { - "tag": "vk_repr", - "fe_be_hex": "51a3ad301c5292dcc208abbe14bae00feebc636408a4b2c54d5edd07ffc492a2" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[0]", - "eip2537_hex": "9324a75f7803c2fb7be84f8f8f92397fc38f438472414438972d85137fa1e884f7fde28c1f4b1d3555b721915e60a4d7" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[1]", - "eip2537_hex": "afabdc807327c5de8fc13495028900d83ac62f2a9533b780582fdd18bd68e5e20287c861c82923e6576330099dec584b" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[2]", - "eip2537_hex": "ab042a4727ee1f333d73706db25bd1f229cb608765128af1b9377b3e2b4795e85503deb7f086fdb781a3c4d190205a40" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[3]", - "eip2537_hex": "986d652bf616055fcb845f79e496bb94a6dcdc7e332a53d2c1d2370a637b0d2a4f2636551e681cf1eb70c561defa62c7" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[4]", - "eip2537_hex": "a24481617df262dbd06b5fddf658f9c32e49c6403bff43252ea10fb35c37d987af93346ecee6db2b794f8ea6b2b60286" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[5]", - "eip2537_hex": "955ddbc47fea8bf6280024d6b455ebb60462d79a19f49187443bcdcec9b16a84f41f41367805521eb89f876c13cc2880" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[6]", - "eip2537_hex": "872ba4345eb8cc6bc2fca678eae0455ed17ceca164901368a453b3826fde369e8cf63db2a87b14fc54073a369ccabfe2" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[7]", - "eip2537_hex": "8e14088f20db449b8f49695c3c84d8d873faedfdde1ebbe87c39c81765d149380c313b4242278b752177fd7b661d0554" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[8]", - "eip2537_hex": "b5e8f709e287c8a92bcfae306d20a15034f8f04e0cd8dee49059feaec3e99e002d4268f36f613598b68b55cc3b6ffaf6" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[9]", - "eip2537_hex": "93c8f3366eb85800f928116e3a177bb36a5baa5b698e70afc838e2e9eac2b35a5a0895da21f21f86b4dd5833447118ac" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[10]", - "eip2537_hex": "a23d93bcefea6c36837e348911ea299e8f478c5097f803c74c9aa0752a09c812749102e3a3321193452d710a95ed97fa" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[11]", - "eip2537_hex": "8c509b6d23069f785cdcac3319040d8b5dd155bac5e4a3b815891e6a571dedf99a2d46b4670dfdaa7c89cf43fc4f7df9" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[12]", - "eip2537_hex": "b642bfcd07db3fb367d968a923c87f6c0db808f812cd9014a01f36d8b1a33b20de6409ebd7db009104cdf07ce9ddd872" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[13]", - "eip2537_hex": "8980d60b9cfe2b6d5327ced4400e8ac611d292991c0abd3a2c7e692ff63b866ebf4c1a54f15c38603e60ec9247578c24" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "advice[14]", - "eip2537_hex": "b946984d70cd450a0f9def10b8f8694e58f74b5cfc521963170307df92baa7e8237fe25d9fe81c0421d909ad0f5a6b76" - } - }, - { - "kind": "Challenge", - "data": { - "name": "theta", - "fe_be_hex": "3ff9db99be64b81421248a42da80d4cd7ba4aa6bb94e8ca2dc7c04b5feaabccf" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_mult", - "eip2537_hex": "883447beb33d2cd632274e5745542ba9f827e3de62e2218a3139751cb8997abcbd90a1ca2dd9be5f6c861b06c2c9ebbc" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_mult", - "eip2537_hex": "a06266251c32ea2ac0f43fb5dcee056bb7e8002fd9ea8480c384085f1e788e542965d48ab07e31d0140987c4b566eca4" - } - }, - { - "kind": "Challenge", - "data": { - "name": "beta", - "fe_be_hex": "5e1004307780e019454167777748c63f1ceb0bfd8137c218f46a29d9fcd8997b" - } - }, - { - "kind": "Challenge", - "data": { - "name": "gamma", - "fe_be_hex": "0058d4bedb482ef6ec68cc4102d79d26a3e68c67a660554306766a08d0b4bae4" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "b992ef1d42c2f3db9b82a33465d87623c31772480224aceaa7bf46ab89cca003b1973ef7cb161a373cb00d968286aafd" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "b124921f1914ac76be4886f24155f31c4ec67e4bfd34b400eb9d1a282310d7c4e7577f80297f48c5e46e17247c9cc03c" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "9215d3b97960403604292c0e49b5560bda50f7de776039dd4792ddf1afcf4485e4036d4c6f3d84c6a730805f433432c8" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "89d2652251e8c6df970de2fc4b5090d192110de217fb3da3b11db3886c472d7b9d4f9b4eac8f599e90071394dc7efe0e" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "86302bed0abdb7528321e4170aaef2962c67c8926908dc990923971736f90f2173ea372b7110afe8d9e393cb73da0677" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "perm_product", - "eip2537_hex": "b36a8b2ee086077c3673c953aa45c68f947735789e2c386fe2c7bc768251bff3eea2ac6681d1ba9dbf24a774ffb41662" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_helper", - "eip2537_hex": "88451ae997148f4b0b042066e35fd50c3a39a24507945b0ea61d3a5557d39e45f01740cc16d469f688730cda358e55fa" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_acc", - "eip2537_hex": "b14d249dd6b8c44b4007503b116d26e684be9ff9d1e76c66b9a0182f4d4a5998bb0bbea3c807f2294543b0651afaaede" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_helper", - "eip2537_hex": "a2d3ee8eea5e7e755b69bb79d8fc18ca688ff7487afb894c3077ab6c68cb8d7151e96009cc5bea912ad2fcca23c63f26" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "lookup_acc", - "eip2537_hex": "84189193c82ea16ce40c6c3a7670c782d9ef22343531180d8f15aef0e3ac7714c6b4d2c3eb58485630260e95a094e50d" - } - }, - { - "kind": "Challenge", - "data": { - "name": "trash_challenge", - "fe_be_hex": "0de9348372df1d1e7432fcb24ef5ecbf1358ae5dbe88097c7df40bfcb8d7889d" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "trashcan", - "eip2537_hex": "a846009a66f994210618221e9d4c3b7dd2830290c5a316a1befb1cb3fa2fc5ef84b679e5e5766914673284cbf6a1f3f8" - } - }, - { - "kind": "Challenge", - "data": { - "name": "y", - "fe_be_hex": "717b58f00b4d3d2793757513329c2c1d50deb5b93956dfd2048548a4d7837d9f" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "quotient_limb", - "eip2537_hex": "b4a018c05d3d9fa4b36bf3f2ae2feca08a658412829a26fe41aec4134158fba7739358e125cea1a5914f72cf9b525e72" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "quotient_limb", - "eip2537_hex": "815a896114a233d654689dafd891428d126e7e620e30dee5a5f1c0d9ceabc0581fa23b37dbd0b7775aeabd9fccb1d3b1" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "quotient_limb", - "eip2537_hex": "b6b9678337e622f2d663b2293748d82b7102ac83213590b5813c5099481fa123f81cf582d5f7b4e81dd3ce024d79097d" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "quotient_limb", - "eip2537_hex": "a2c5f0060ffd5c4f8741b884ee6834a04366739fecb2bbef7f27b8e2d08065b4e979ede81b398a1ac6cad46664035ead" - } - }, - { - "kind": "Challenge", - "data": { - "name": "x", - "fe_be_hex": "25a95a70b527cd00441e66fc7716f297fce60d998bcc0158e816488a393e408c" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "committed_instance_eval", - "fe_be_hex": "0000000000000000000000000000000000000000000000000000000000000000" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[0]", - "fe_be_hex": "2aff2f04fce943e11f15b05b610d1006f7276184a3eb52764a9b162ef19b774b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[1]", - "fe_be_hex": "613ce970681657d1997019c85dd9e468b607ebeb81c81bf5b3e373afcf25e417" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[2]", - "fe_be_hex": "5ad602afd794d4ba4fa04d59b442983c446b7a1924f36a1c869b6fc2718f1b33" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[3]", - "fe_be_hex": "1b19d3101f528b398c5636f9af64ceb203941f5a69906344f6018dcdd93ec9f8" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[4]", - "fe_be_hex": "63f62d35bef1451fe363e8ae48f478aa78d3775c1df49470ed9b08acc8a582f1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[5]", - "fe_be_hex": "6120b59c0ef4b1422d808ba3bf08c4319da59fe04f3ed69e30cca0e30438fc02" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[6]", - "fe_be_hex": "04924d8799758ac570dbce5be1bccde9de9b44f64ba93506721bc8fd9b0bbaed" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[7]", - "fe_be_hex": "2cb4f7a76de56ef73523af97075a0498d7789853d23aad7fc12c7b7f7092a018" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[8]", - "fe_be_hex": "62b0bdacb806ca168215731c666c8d1b3d35241023674f4e8866119719dbfc3f" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[9]", - "fe_be_hex": "5d9ea3fb49590e6dbc8a42f662741fec9a7322411f948f8d3f54bd70191a6f3b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[10]", - "fe_be_hex": "28c0c553847c9c7c586fa0c979a308b2d4c0c87e3c7061d200f7aef29776f159" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[11]", - "fe_be_hex": "48a364fdc95646a925fe21aaf84ed1fc70e97483af6ce14452c251c4fd20ab8c" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[12]", - "fe_be_hex": "4e4e184924e843a78524088430f840c2734476144dd5adf53bb35071bb7b2d0e" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[13]", - "fe_be_hex": "1061413a5fd1787a3f0d54dad842be30a6097a89c52db89360ecd2bbdc98dca9" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[14]", - "fe_be_hex": "64da1115a5c5107dc2aa9304d6dc5b3d83195eb097c28fc05cd57a218fbef7c9" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[15]", - "fe_be_hex": "33ffd290974cedb43eb4e5e4cf9ae0a8c6b1c45b45654b5e960ab34df74efb2a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[16]", - "fe_be_hex": "5cfeb68e3f0b568eadbc559b3466c6d236161a3c8a040c0b4514b8145ce79238" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[17]", - "fe_be_hex": "18e676097fd27a394eecbfc4fc9e77b93bcec22fa31c8cb5df47148a24d61f5a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[18]", - "fe_be_hex": "188899add7daab3c574a1ea69dc2462b98f4784965414b67fe968533a5046841" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[19]", - "fe_be_hex": "678d9102966f8980e4ab679b0e8d89062a8a4b4287350de27b073897526af3d3" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[20]", - "fe_be_hex": "2c69fff1c50460ad0cb18d96216a250fdca786373030ea394cf3e08493085c3b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[21]", - "fe_be_hex": "69e0c4daf6c37e6c659ee977e0f911703782d74a535f30c757b45b83fc865da6" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[22]", - "fe_be_hex": "728344ed27b3ebaa17612c3ffb57a14efb120656258b47fa20c25ee225ef2171" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[23]", - "fe_be_hex": "4806c4286bf892869328dc209bc46314b2759765bdcbcb99744fa0f348545a75" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[24]", - "fe_be_hex": "0af784a7a802b57c08ffaea2b29653005cac85e3332f5cee2b754ab02b5e0bb3" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[25]", - "fe_be_hex": "15d0be507d1046854082c0ae8b1d4ff812a5b0f2899cccfb3d7877f355d5b83b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[26]", - "fe_be_hex": "27f5830c86931649356219918d1fe8ed97eb4600aa3e46b4b173778d7671d50c" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[27]", - "fe_be_hex": "6e3f6903d3dc1e0e195f673938a9edfdd6dd456dcdcd560ad4d9dbfaf85f39cc" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[28]", - "fe_be_hex": "22ca4cfa850bf7487c8d7eb1cf2ec2c86a633768d9b9e2f16919b0830d3fb844" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[29]", - "fe_be_hex": "5a9e3590a4f9be93687d68104c9b6cb2cc9ed410fc07d6162dbc9f64decd7ce1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[30]", - "fe_be_hex": "4b7d002e918dddcd4be4f0623d8f9c0a9533b24af17e5dc634df24e500729dba" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[31]", - "fe_be_hex": "5d3073c3a96253693c8df7ce25e52b38b8c78afd7950a60e21cda35c3e48b78b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[32]", - "fe_be_hex": "2f1400afb1e213b69d45580bf9a0054f90e54c4dfaf340510358368d7eb037fc" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[33]", - "fe_be_hex": "54f6ff91933cf536ecfe439e01b2103b02aff564ad64cc03322803101fba3812" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[34]", - "fe_be_hex": "6bc15393109fc72220322b2e100a293d9b4d7f43828783f18f226701900cb91c" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[35]", - "fe_be_hex": "1071a9bbef3816d70ebf4b92b9755e176e2a8558a3bbb3d16ebe2570ffb34f1b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[36]", - "fe_be_hex": "700c1d9c6e073b01b5c81986fb4df82a1ec87043972046372890ca3c7b5b0928" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[37]", - "fe_be_hex": "64a7cee5b982c3b7eecc70e2a7a96e53be8effb11d1040c9a73a9ebf334b1194" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[38]", - "fe_be_hex": "0175ebff5c51ed1bc0b0ac14b7193dafdf64b1c2cec0c23de2e781fa8394362e" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "advice_eval[39]", - "fe_be_hex": "6955a4c86380c065bd0c80036c3ab85fbdb52dcced432ecd68c48d9a1b274509" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[0]", - "fe_be_hex": "094662684cbadaf67132f94dfe2511cc8aee0f2519f2540b17314664295d2d49" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[1]", - "fe_be_hex": "6249297a890a17fbc31920e6f5b33e284b17b738d945b93af9ac63581f39e926" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[2]", - "fe_be_hex": "533aa4f8f4c2d59ee7d3ef836d846c08608c2c2a0f9322f6f6b269284401b569" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[3]", - "fe_be_hex": "0a7069537b5283f647e1a5fb4510e338d37915aadcab9cee9ac42474f027159e" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[4]", - "fe_be_hex": "036721bd95b1adffb069c6f37846d0949a2541fddded7dbef574c926ea067125" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[5]", - "fe_be_hex": "151dd83de8726f4907cad27fe7c01b6a38b780c9e36a92f2b14b334c6d43d323" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[6]", - "fe_be_hex": "0d8f561572fb44d01ce808d9b57f3dea1151b1c70af6170efd615597953aab90" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[7]", - "fe_be_hex": "388498775fc06d0cede26ab654db23b72fd6d2c693ee2fcebb0c3c8943544618" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[8]", - "fe_be_hex": "3f4cd7d8912aa56d301ccaabfff5b239f7d0b375dc7e59e70ac83d1df74cea7f" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[9]", - "fe_be_hex": "0d28bbe4bb3217c71d9f55cf4e629b6fcbb9cbaf3db566064f6c14d650c3518b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[10]", - "fe_be_hex": "6ee5a51f25ab89cecb00e84084a45fc4f644603406662d4ee5e8f8c93ab72c8e" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[11]", - "fe_be_hex": "6aabae683a739b717d4284d4ce090ca897bf16439d18a537381223febcacf0d1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[12]", - "fe_be_hex": "734189390c0049b73dd5622affb5e10138229f67c4fa0f70f3acc7ac5492da03" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[13]", - "fe_be_hex": "1be1180e1778d1802e467100378415b572888ae535042e01cb3539dd1059352f" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[14]", - "fe_be_hex": "5a8998c3bdc57c8c6bb4b209ce16f2e401655f094e065e08a1b42d14091d6c1b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[15]", - "fe_be_hex": "497165498e6280b15e3e3b2b3bb1be8fafb4880013a2860237039c2872fb5a1d" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "fixed_eval[16]", - "fe_be_hex": "3969731b9c485b627553a8d5c91fee63f06759560d441480d36ab2144c382294" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[0]", - "fe_be_hex": "17e99dd0f4b34d2795f0c82eed3b8081bb499b675d2ef634a8b2b399adf374c3" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[1]", - "fe_be_hex": "434e96f8f1801a73f63d1faf599d28f3c7c9eaa3112979564c2a59a955dd3cd2" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[2]", - "fe_be_hex": "6b5e6841b8101eaa53ccb8eefb7fa79e95275028679301c335ff66f216f41b93" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[3]", - "fe_be_hex": "43752a03cf17d3d0d64777af9a39d283e7859eaac4a2da400baf3af7359a127c" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[4]", - "fe_be_hex": "4ee605378c3ccaf4e48e82fa9f5c6ef2c9bd3a44f482ef00cb170f7901cc2b8b" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[5]", - "fe_be_hex": "0fc1c5e02cfbf5d9115b7137074f6f0f6f294d7777c54d75f5b7c6cca84c03f1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[6]", - "fe_be_hex": "5492e7d49f48fdb68ab634eaf30059712aa662ac23bc8d7bb269afee222f3d93" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[7]", - "fe_be_hex": "09ede7d5f304404ce3602f10ac6c48423349268c98a5e7512c42a85e54a4a8b5" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[8]", - "fe_be_hex": "25bae27aa445934670e7660234eb483b0855de75f8e0e5c25a8859d5b5f1697a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[9]", - "fe_be_hex": "10a09e888fe4fa67cc79bcce1fb2965b6e166238799d9772d47a07b947236493" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[10]", - "fe_be_hex": "22b7aea68284a218fc8a1c85c16f2674c1f9b76494e29be3d5d1d25879b2bfe4" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[11]", - "fe_be_hex": "421d73a8c0aaf6b421264b6ac308d0ba2dd267d3f3134b006b8e440d08e2a0cd" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[12]", - "fe_be_hex": "2fcb6e506ea4f6d5a73ff93256c62ef6c08ce5cb4dfa0834f80fcebabbcd98c0" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[13]", - "fe_be_hex": "2a8865c0d658cdc019c2bad26c10b388ab8138c474013d2618a4a7a9fd78a151" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[14]", - "fe_be_hex": "256e955fd4aca98fc22078c6b2ddfa4535dde657f14193bc91a10e81c6ac8637" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[15]", - "fe_be_hex": "3b36739c2dab05e8731283b6a33ae8a8f899a4dce36067d38876c5603e6d4dfa" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[16]", - "fe_be_hex": "71de8b575f2b8d2f97d31a3877fcaec1603135e3648dc8fa91ed9a159dc72026" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_common[17]", - "fe_be_hex": "2693d914e4ec4b414043dd7cd62baad35ef74a798d63641cbb89b9adaa44b2e5" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "6f5d99221a6baa6fdd4a86dccdaecda185b900e389abb4aebe6a78dae9986dd6" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "09e25e6e94f2d464bc6dfbdb00dec371465c9e63b351f030faa16f2fc9089729" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_last", - "fe_be_hex": "6d7e57bcc8a142e541bd48302d7d5f09ced322a30f7f46f5ebd3f068934ff3c5" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "09e6e9d001c1722bef8caaa1defa9016313d4ad09c6ab2a025c873fdf217a88a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "3c4fadc803f4d688529db1ab405f4b7c87d62b8312d06b82580e09f83509eaf8" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_last", - "fe_be_hex": "35a66b9743844178d52f221e577f21da84afa99357bd796ab5ea58a44000abaa" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "0a1d4b022e51379ad4fb5e3dd2ef2d85782787f64cdc57b69e4eefdea908c5be" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "04411e7377b1c6c426bfc47ff792dc95b65276cb44d3e3742944dadd2356ecd7" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_last", - "fe_be_hex": "3a8947962b0f02550c0eb3ca374c7dad62e6a93f6beac0b717f0ecd53ddc08db" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "530b29213aa759519d799bcfced039200fdc999b52b0a3a805fe5f17a87ec4f1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "610e49e355e2d7b519059f2a05cee82ba7af81ccc8521c00c05a299b43afad72" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_last", - "fe_be_hex": "4c4ae9b7ff00533557189e0d7e3d92968a22b735511793c9b1cfd24ddb481426" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "360e366879475eaf65c181ee1ec4f7cb7505e72c2cbbf909ba9501657943c7e6" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "6793d5eb4cab328ec774e8790d4fa5f7e2eb2fcb326aaad5da8d7902bfbb0cbb" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_last", - "fe_be_hex": "61bef43e11955c61a073955b26c7a5100c70f40cbc557d72efd31d37de08eb99" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_cur", - "fe_be_hex": "4f8352e3683141c181c8b3f2c36fb3c87d307f2ce83460476b2abc3e00e367ba" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "perm_next", - "fe_be_hex": "6a3393d0a58d7eff8d70efe2ca1651abea2810066637f38d47d3fe505e6a66b1" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_m_eval", - "fe_be_hex": "5db5037d269fe3b3163aee34efa1700b945b64eea07a169e05c1beec055879b3" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_helper_eval", - "fe_be_hex": "739afd93c5d8099da5c1a1a38c3b931c0b9d78ad5ac1b0cc8a5a02fd0b9b8290" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_acc_eval", - "fe_be_hex": "3c654f49671abd7b7d5e4a2dc444a8b59ba8be9a5422bfc776344bcda00f2575" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_acc_next_eval", - "fe_be_hex": "5c73faba39c84ccb0f0a9be5f7afad39553d992f22d65e43370137f7f1748ea8" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_m_eval", - "fe_be_hex": "37b288ce777b7ad710ca063faeca1c7e830994011132bee36592dab105910e30" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_helper_eval", - "fe_be_hex": "0b10469cb107cf10a1323b9a3488bcb5070d70e57cec1012a0d832cea8f50d5a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_acc_eval", - "fe_be_hex": "1f5f07b0f2b3157e0b593ffe0cd4c03602ebba3bce94a97d1416c12c0c04cb13" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "lookup_acc_next_eval", - "fe_be_hex": "19aea3331455c4d6a7d7e0396441b078d62d74d9b13021572a4bd8f9ac98353d" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "trash_eval", - "fe_be_hex": "2c5b5194dadc6558b5486ac297257d2f731ab83bf4a6de7f1adb44611ad985be" - } - }, - { - "kind": "Challenge", - "data": { - "name": "x1", - "fe_be_hex": "13f8c24186a7be1a397c7b2859ff227b2d0920d32d545885da542bbed0ae254f" - } - }, - { - "kind": "Challenge", - "data": { - "name": "x2", - "fe_be_hex": "29175d62cd5f3b273588fab38705600a7614b67768c291d31130891f1199a011" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "f_com", - "eip2537_hex": "a6812bc568c0e0aa3aafc20385685e504c2f36778180e78ca44ab76a64b6be3b77c87f80da055bbd519aba9d5122e459" - } - }, - { - "kind": "Challenge", - "data": { - "name": "x3", - "fe_be_hex": "58a0feaa98295060f65934374141b1b8720018a665345b485d2fc89de8704cde" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "q_eval", - "fe_be_hex": "15874d911e497a01d83006b38e4b7fd697d1cbc335f9119f085739db694a5a85" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "q_eval", - "fe_be_hex": "34d56361507e3fe5e2faf8f8289d4fb0b5a83072b4fe6b898b1fab754381561d" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "q_eval", - "fe_be_hex": "4928e1876a849e1c27ee7536482186bf9359d26c1d347cde0fcf83aa529f147a" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "q_eval", - "fe_be_hex": "48ac5842c24471f2d12739e9db0f841e972ee94549e5a16531e6b0972273c8a2" - } - }, - { - "kind": "ReadScalar", - "data": { - "tag": "q_eval", - "fe_be_hex": "4ec3466b72d2b0ea1ab18978b23d2da7d01b8b040443598bbe13c6b04d404983" - } - }, - { - "kind": "Challenge", - "data": { - "name": "x4", - "fe_be_hex": "0406be097ef26b66a1727792c8300aca3e81715664c38d1284b4d40ea47edc67" - } - }, - { - "kind": "ReadPoint", - "data": { - "tag": "pi", - "eip2537_hex": "a9f4920b7a8364d6c85cfc71346be70f46dd9ecd7fd527f2593c83cb55359ae395f091c8c26289f663cbcc7bc945b923" - } - } - ] -} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/ivc/solidity_trace.json b/proofs/solidity-verifier/fixtures/ivc/solidity_trace.json deleted file mode 100644 index 287b943bc..000000000 --- a/proofs/solidity-verifier/fixtures/ivc/solidity_trace.json +++ /dev/null @@ -1 +0,0 @@ -[{"kind":"ReadPoint","tag":"advice","eip2537_hex":"b5a3ed19b468966d517a4246fc2d3dffeff693cfae260ebca3c20570b03d90bb67818f1f29fc5eed276dd931d0a8f744"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"81725d4f826bd4e1b2d44973a7cafaa3ef68635e81b756986e1e7e9c5aa88dee5b132e5675c53cabc1a6650d4f7724b5"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"8405678eca7b3899fe8a5151c2f120af233db43316370079b635b0a1b586a11f88640a51e8fbc3b828a9c5761aa247e8"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"a94e856a5ce18ebd3ac321a8e927365411113a2c94c5e318351aea31e2927b998ed7aa69f7ab94eacfec259a90944f4a"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"9927ed8349c0e9a120abd70a94b2be16d801db78a48ebf1b8e832c12bad6b1439d2449a232fb7834c9ee802a9c17b99a"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"87f25afb4c569da357338c5a8d6f6f8f9a70d3ee4c4c76112ce557120a6c6675e7a050cf6e31a3bf9810ceb691fb5939"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"a901dbf17bda36068a654324f80bbb5cb662cb3b6b6c8de00c092be6d339f7089b92885504c313ce6942de14bb465b5c"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"895a3b7f738bb1df304e5dbe3d3f053598e3eb2e421980ad161aabc942edefac85393611ec42fd40d0374bbf924f405e"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"9662fc7620753324b71ae06218c1b5e8089bb31839463665723d8fbe7a2ad245e5133c774bdf716041751bd486235a32"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"8024ab67e2e9b10695f9d9de724f8150fbe2ae651c15270b46ea58edab90cd9458f2384ba9d3b5fab6645dcda696a95b"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"940bca31980207633c105e4723789eb5f4127c41f6325b855daf2cf3180eeab52b72f734549b72b57f0a27f787a5ca07"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"8cfa3c33b295c80d24437052ff753e93461752b8ab0170a48208d445658e53f6fb76bba84de7344233644bb68650a7d8"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"a7390d9b294df6d8c18c35c3ea28883c1c4cd48fbc12e8d82928d1e2dc340cd64defac67dca106a37967fab7fa8d3cb0"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"acc0d4d4feab2c840d1eecde2bc9f96bc6d2cda78ff298ec6716da118151cf598938b6c9346d8037a02b2e241dd1a3f6"},{"kind":"ReadPoint","tag":"advice","eip2537_hex":"8af8bc94ee0178b5f139926856976d0253940d5cb5391b39b9b4cf2c06f3ba7289bd798230be9a834976ae152441886b"},{"kind":"Challenge","tag":"theta","fe_be_hex":"27e8037bbfbfd47ffd3c2ce862e3acf6b81a0a6619471248726ed6576227807f"},{"kind":"ReadPoint","tag":"lookup_mult","eip2537_hex":"8309bd912ca8cf399279d7dbc8f453e873317ebcbc5e510acb13e62904711a628ff30b61dbbf04f66ef56f464056c3f4"},{"kind":"ReadPoint","tag":"lookup_mult","eip2537_hex":"873b6c597af9d183834ecbb4ba28a741e5a76ca027e14dccb5018f7cb527c07bceae9bfbab353f064720b0df70515ff5"},{"kind":"Challenge","tag":"beta","fe_be_hex":"12bc4f0d2f0d0c27bdaf7b29adc5b364ef4eb3550618960d1e6e7699f4b71760"},{"kind":"Challenge","tag":"gamma","fe_be_hex":"32539d02a7dc77ccee137a40521fe9a0adca56d16214e383a1458d03ea2ddd15"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"868f44698ed2b1174d655ade22a1bcabde75534c1af29d4c646df25add46d276e58066d8ffc6d7a7221c93669959d1fe"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"a734a580452233277b40065adb5f3145c701f870e3f8a07cf401ac04ca5787e44b551f11f521813b76db5ab41ce56b83"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"84da1d462aee0e1f8e2b08ceea88ac0b7f60842a6ecd97428bafa652e50b722f646dcc6d5a3b41ad5a5ead0178a9775e"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"88677162fef7cc540a46813c82a43da84b476a8726e6e376faaabe15d30e379aa0a902b5c962cfcad26f0b6b7046df38"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"94ade48a9857fdd229d84e556f6e483c512cdd54dd550503686b8317d6d99fee43f79390e91b5ebd303cf0c6bf32f06a"},{"kind":"ReadPoint","tag":"perm_product","eip2537_hex":"adda51a9ad06282aeaf8d1f2d5e7e7ffdbc3e89f0748db870157cf2ba3de8053d731c204001c8063a3b4e18e0981adb0"},{"kind":"ReadPoint","tag":"lookup","eip2537_hex":"906be3c46e0499a65febbc526c0af1d4c7f0d630af1f76ff05f3da874adfa98f83d8a9d048a94b06bccd71f1d8370e22"},{"kind":"ReadPoint","tag":"lookup","eip2537_hex":"a963d0a8790cec1acad417d575f03d18963b97d0b8699fde81074471ab5ff84b6b2435e4de358646f2f8cbedd20781b9"},{"kind":"ReadPoint","tag":"lookup","eip2537_hex":"8d562aa58c17eb6a5bead6f10b62ca93dde62817d7b1c8d37d60ebc7699f07bdd4c1df103a6308381b6ce1ef62758956"},{"kind":"ReadPoint","tag":"lookup","eip2537_hex":"82aee8ae26dfa6e8cd888e145c7a221fc4b4179bfc7aadccfd173129b1ae29c28f8f8cfd0b68e00142592aaae2697662"},{"kind":"Challenge","tag":"trash_challenge","fe_be_hex":"1f4aa81eea4602bcd9a4d8c413c83d27a205f49d074b3e58d25d8202feae2b60"},{"kind":"ReadPoint","tag":"trashcan","eip2537_hex":"ac938f8075047aec1fa27b4a958db254a15380ba93ec93b19f5cf8a8be1d4078e74b1b1a0a70d907f5ce640816ff1d15"},{"kind":"Challenge","tag":"y","fe_be_hex":"4b77a617a03318cfb24402ba260a8d4b6bb38e03827db22b36d6196840061025"},{"kind":"ReadPoint","tag":"quotient_limb","eip2537_hex":"ad70a4fd9070cbb6a2dd9a302486e2158fcb67de21eb59cee8603396e869f34c7dc3eb5bdfe3cb6a99629ee6524662d0"},{"kind":"ReadPoint","tag":"quotient_limb","eip2537_hex":"a71a43c4506df681ca8112d94d5f57cdeee6c1be7c875f0cfac40e8291bb52dbb147c0655b7c15aef823e5778f764ae7"},{"kind":"ReadPoint","tag":"quotient_limb","eip2537_hex":"b1fe3bf128c226686b753e4bae0a9f7633e3ff0d3905e0dd90e926578fd1d9bfc270e4faec8ea370827f85283fb5231f"},{"kind":"ReadPoint","tag":"quotient_limb","eip2537_hex":"af288e6aba3df8ac49d2320b0d2d8e1bbf9665922e962bf2047178ba018bd55b6c6494c59f6e55db80cef009dd2ecae3"},{"kind":"Challenge","tag":"x","fe_be_hex":"480c85d8d1c587dac3891e48a29ade9ecf1674679a0b808e8d2cb5bb3491f2ea"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"09ef5b8920d5d73deec7f3a353ac34262031dbd8f12384123a56b0444384a71b"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"5492a2956c450bac44d8590d6d8802e03f4951161e6b8b638a70f02fa6f8679e"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"201b58816342ae2f3b644148e9df8dba8e4df21d31e77e57d14c192830856d0a"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"3e8973b8bec854146133131923f26a622f86d10f3729e5a033972672d2ba18aa"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"08476b87f42a92ea6faee7430cf19849167bee80d0903e2b94ff68d20d067e16"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"4a5f88276bae0a6dc5737b3acc98ef978311a886979287c51213f09e50700dbc"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"4ddbb2b1a19d17740886685096269f5cfeedecbb4f842b69e18da0667676652c"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"64b29d580131ef7df38067302827572041f0284568335e3a06a9a73b736e2acc"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"2cff007180182c6e10eb772a9093d7b2f42a77e82888711d3e74bd370b605d03"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"0eb888285a964a03a607bcfdd1ba5ad71072b5e96901bace474d434cbeb1c35a"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"1d1f55b5e070b508ff54a51106ad265408ba62256d10f0aeaed5dc756e33bf51"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"0843b40b6701ff67871c911b0bb6768586ba9c034200dea8eff3dc70bc130b66"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"3f9f6a7efb75d7c4571bc4154df2fa8c95e1df48ac1a1e3b32f0f3920d14bb16"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"19242199f60bd966b08275c90466ac422e5945d0ec08b26fb6178369d6b32abb"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"5be8a576c4e42c8e6698aa6424296843af9218bc4d67105d6c77e792fb936cf7"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"54c32b308570688ccb4a3d9877f1667393f9c775a190d15bd789be77a7872637"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"65dc496a78bb27bea7e832fb2fcc519ca0a1a0e9f1a3da01fb7842400f84596e"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"2fd8a069b0528005bad464a072e383372b46dba2bb12557d1c0620f48520d260"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"2c925fce6a934219fbbab1c5a2423edaa05d161143fc7731319d38ebab1076c0"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"4232af1b388ff9803753312c54e93a055085b754c3f051507f855cef7ab8415a"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"49b4754ff126553764ae10edbd8caa67b7ff08b4acbb4cf9403588bc12e7b9c4"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"62a4bdee4055ebda8b75c8c458c6d8ceedabc9c82abb574ca64d0d0d54265b49"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"53d0c5cdfd35487168e993afcc0857ac972e732b06d3d21e33d60b8153c73d9a"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"2c3fba7975d6454c64a7806254b583692fb94c78221cd2d50256d03e47a71146"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"596f33fe28d30a2cd4c578e366568a4fd773643713d11c317a74d1c324e7fd23"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"049d1ebe2161d109d340ae7152df2f4831ad22fec7548578283d91d7607e6474"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"6627866c47eba59bd8337e7ef01a0067277a257293e77d9cdb1f317de54c7074"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"34f29783304a2e64bdced10de519a3550478203d00d69574dad8df6020cdcd30"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"48362875e1a6d36ba286b3a5a8a538d437762d7364b33c3c3b742b2aa58913e3"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"61935b799327cf51fc7b3e9d3662669d75026e5bd83f8bb50da9c277a5903dfc"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"44c545a9fb7660541a696d877c75b4dc275948679124d98196f050c72b3bb4db"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"27a1235745ef505fe3cc0e473f494379096496a9f239282f5380ea91e9d4c0f2"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"3197b3329a543c53400fa92f0ef912fe3f12265a7a9ebfd1fad1c96c5b4b7573"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"0c6b7da40f51cefedc5e562d996225571e96da7ee9afce17a551f343f6893a21"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"1d219cb7e0e9fe1c009f965566fbfa71aa4567bcd937963627640f6eaa6cc77c"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"1d871e9691901bfd6d4089a6cf77afc86504b1521e3ecca9172bce5984df3c07"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"5ac394b97b804f3b8557fc3b67d2ea12919e27a9f1c33aa3470db54e493f30c1"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"46d90b6372812f62be94add1c0914df911e1c520e0d7776556696a3be3f2a2ee"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"461997adfb8e95e75c697a3975ea365a6667e975e90fc5520d86a2e19a8d5e79"},{"kind":"ReadScalar","tag":"advice_eval","fe_be_hex":"019b99c3d5c202bbabc76aa2a2a86c9e445988e698197b96cfb09dfd7311511b"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"14ad4103f2e8bc5ddca276f21b54e3ee4054a6e21b9d567e71a819478626cf80"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"5b6dfb1926f4165466b1c34793247660c1ecd8244a2eab3340aee10747bdf466"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"2414f225327ed2c7ddf7b77b5d10f05d6aa9634913a35c2e5138611de4a6cfc9"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"6ab6d2f1fc1ced14f72c7a8cdcf48abba1e048161d8a720c7a290853956f1e2d"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"6dbc0726ddbd4e1cbaf60f38999498eb2e8eb9d5010f2300594d3703be266658"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"37726ba651383f231e61b5cf282561273a93f0344d01d68d4dc209e724395d1c"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"3e68bc805df454fca334e1720f57a3363811f1c17808da70f205bca79f0d599c"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"3b40f386c432f27daf4e49a626e983cc4a8bb5501f0fef370a463cfed9d63aff"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"5e34b61c1b3db7f63cfd45d3e261d300e858536fd81612be8f50451447b519a2"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"2f16371f1d896cd93e2201b39fc4ba25d2f6d09d8b82ac12e46e930ed3ac21fa"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"0ea3f215bfe32c3166b8abbf36962f3ece721ff0bd303c5c59bf566521bf1791"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"004f497fb64bacf49f0e7273047e805a2c1af696595c259f8a73a49bdb41799c"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"3e06211806b4e7b4674d4a2dd409484190ecb8158387b9d9be9940e35fdf9b1e"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"1db7203289fa5f6db1e2b150057157b1da3fc8b87a54b62f42f3521edaeade54"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"01bbc3918a1edda73c72ef9c0ccc91b584fd1915e469bb9355897c37515e0075"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"1134aa786c389ecc8afb97cc1f80c6d17fe4b0a282371404e99daf7e22748a0a"},{"kind":"ReadScalar","tag":"fixed_eval","fe_be_hex":"44154629726a79952b69531e24d1c476a100c387864071ce9873ca0bd4fb8259"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"6c9e183607a90e4d710b847ed3082cb90e11430e161e180fb4cf6dff5e60f6fe"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"712d02268b3d16ffd37948ddbf54e8e0476873c1a561ed9a50983d8c3af196a6"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"5839aa24628fdcc4ee441615e0f60859084e903ca95b43bd89aee00ff1accc76"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"37f5ac771c0edd9ef214a34b33a469bbac297bc17e16ba72be1c046fe41fbaae"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"655ecb16378f8d990cf6c442d21bc690ab2ce050f6af93c8d94357f3979e72e0"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"3241d62aa2ee77caa09d00c1048f1b383f5f632cc741a71a382d6912a5f0350e"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"373f161bec38765b8579fae5f9057301be67013bb38928b5776909ea08337edb"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"0dc1d24a4f7bd8331c394efe98e5f452ff3955e737ddae522f9e2552ca79280b"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"516c5fae7051b044cc4798c9f75bbcf06c4b5910f882f1ffe15e82f329165c6c"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"3de4833d15a75e94c5fc30c03ec19c7e238058d2fa242e7caf17d6b7b57ee2a1"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"201cd483ad4dc7d466cb4e9e2ea6785b921cc491bfb23d121a8a4bd1c314af48"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"18f0620cfe7ecbea2b26b6419cf1afa321bef2146fd0e26398d201b2b68cefe7"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"0a2b19de67150f439452406bb4d962da831d80241e625cd714e9bd0a413ffc1a"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"19a2b386a5a2414caf2d9a688b4bb38364810775cc9fde2ed1804d34a1e4905e"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"4729b5d831af837bbb9f838f11dce195a82d1694320c7e9e98808e77b5fe556c"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"6307e640d82663d46481590a917b746fb0627a5f80dec829c691d0a442dead1f"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"64ff573b09c5d6897d782bb730572d1f05f73ae3c9ab8b7dcef6a8312df95c23"},{"kind":"ReadScalar","tag":"perm_common_eval","fe_be_hex":"6b8bd275e850818bc69ff00f3b8be5d689eb8cf7be45af436d2317ad048d8a73"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"69f0d9446331009d2881069dc939974374d361c058e4419736d747d753d8e4d5"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"5f14c13215ffc96f43762265ec6be1523aa467caaa8dbbed4fb5250f6f5fd26b"},{"kind":"ReadScalar","tag":"perm_last","fe_be_hex":"08c5f4d6099af4b9b425951caeec5b2034cc116c781005a1855c1b0809acca87"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"09da2149c10bf24f5398bb9ffce65639c62f5e65eb46c9088527dfb3058f0281"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"324f6ef7243ba96253f8669000cdacc095449ab947719ea8de02b57ef087da6b"},{"kind":"ReadScalar","tag":"perm_last","fe_be_hex":"503c69e9a786ddd9cc47b1a7b33c24d9125ee2d3a8a73812ddfeb2e2820c5b81"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"3b45aab1197f9e693e77ffbbf656671c103e7cc7fabd7676cfba7c0dbe1e6910"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"68038ff1509fd4631d2d8ca7a62939990052c92827ae6692656bd3c2e18ae3a5"},{"kind":"ReadScalar","tag":"perm_last","fe_be_hex":"52f75632c182416b0ec8eae43b99ba8f4cb07f305d4a23e7328433ea7cdcccaf"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"5c3379b067d47d07f0baee88a7f96a71809523bc2122e94ce803a77bd790b7d1"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"142f5d5ffedf93ab9733f1d1c1322d7a2a787636328b35c11e0e9336c9776233"},{"kind":"ReadScalar","tag":"perm_last","fe_be_hex":"1dd4d02fab4470903c87e221cbef954679ffd8190fa9df7d19ccfa9730c1d32d"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"0311fe738e056e8db829dbb795c41570a66b8de508afba7753313a8e0f64b1f2"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"5296e8474e56d657c049421b19587912eedfe9a88ac5f3167eb63ddd4bcec4fb"},{"kind":"ReadScalar","tag":"perm_last","fe_be_hex":"04263ab0bcb0df4e05cab8ad7e3033bf7a14d5b0bc2bce18deacff2e36cd1741"},{"kind":"ReadScalar","tag":"perm_cur","fe_be_hex":"24cdd547d581885a9499b9dcfeb3daf3f6b2573249433b68ae4b06e5c6e17145"},{"kind":"ReadScalar","tag":"perm_next","fe_be_hex":"066b9b55e4b6942a9bd6a7d37d970a88f38772d00e3955947d7288267dacbdc3"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"68ee03441d1c0f093ed8fabea2015e3444c1d9ae728a6fe6da42fb84a98a8e73"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"18425279cb0d2ea5a3fe6a9008003baf88d8609655e501bb056d6fac2b3a8154"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"08f9d736bb32f4fc2fd22f40ce8d737e27c02aef5ecadd2117e182ccfec4907d"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"52cf1481bbb3ab0dcb051cff44bc54d8719152970ff508290418e0c36d59ac9c"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"160dae4607f1df78f3391e992a9fe6f757227067b7ec1d0d02ec14c1d9bee8db"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"44cb92fc492e5dc73f69d9cd84c668734a36de040e2b9fd5f158fca36f9c3f10"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"1ca147219543e05d489c2a27ea73f94cff979d018a579df61bfe1fc962913b33"},{"kind":"ReadScalar","tag":"lookup_eval","fe_be_hex":"0a163b1652cc1a06d13c3d35fa936ddf34bbc1ca841c6c39204a133da267c0ac"},{"kind":"ReadScalar","tag":"trash_eval","fe_be_hex":"2236f4d3167396b4d87b1d7defd0e879ae415244035f3d7eaff75943bb4ad383"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4d84f602435f12f8f5b1345a446e1d5af680c023f2e1bec4cd2971d49c4ca92a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"38e4c99b63fb2f92a207528b53ec6384413939f13d1d5eeb6e40d401095cccf9"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6a115d18c53d7506b6e98cccbce5760ac0103a70bde718afc76d989e92a49d61"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"11e74c9d92f5333b73311b9d81ddea08e8048e9693c54c3f7f522886cd4fe73f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4b6767e53ed2bfe61aec89506ebb5d11422fa20f171424c75f1a578e5f14499b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"39df664536219ae8c6d13cd65b0542a4f37585fc551cc32da9e8bf19b619a325"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0493e84246159d9a0c5ee0b0d451fbafce46f9bb9a5c2845327628d26f1e7bbb"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1cd2ab993c64e766697fe1b0129938a0944298703452af46be29d5522aa88970"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5619c9c33806fb0e1f45ca1279b1c6303b9aa577ffceb8fd1ce85debe63e8b23"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4cc558c2bd77616304b3fb7b7d77a5e9b15abe455dde10e63182f80335a95e5f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"689634165a2730277593e57798b7c066dc5186379d0b15d4f4d7856982daffcc"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"355b92c70ec6af51ebf804e565c7d1a1228564101c77f73abe0c3c88aeaba470"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"461f03079bbefd53cecb5f46fb80476a0195d44d635ee14f9b29097c0a631efc"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2ad47773f05f81e86ad287bea69ff972ac8ff4af5fb9b08d585309ad53f6daa7"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"57261f6222454259b1b70bf4b5ddb3e090b3900b7efa6a8b536d45a4518d9724"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"057ac9a6d8cdf2c93f6014f0f10b9a2002bfd4efd1a98dda624e3b5f17e94834"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"26602d5b7d46eae0be328ee647587ffc128d4bdf58d5347e22aeb4f828c4d248"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"381b2bcb3e25616c0707c9d717197f9fd63a65037515d07211b05f3d6c9f58ae"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6ca1d68e1a6b7792a57bfb3b936070ec9e2b62dfd80edf6bb011736de7a474bf"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"42ef248d0a8c8391546451c282110ab5048f334383c82ec31b444d4ceb3096ce"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"55e21e3da2832df2f41d6a6f43a020f3f4bd2ab7bece259d999bc908f0abec2c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1c53991259ba6373b5823de5dfd510952bac4a31af688a42635a506aa3d1799f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0f79a34578494b0e29f69a1df1939463ec999607412f22275623c241bd3c3fee"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"04aa7cd595d0166bddec5fe573ed2c525ad1227e5476c7def868f5b264954750"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"18676fbe593dcd108833fc5da5423a9f916fa9a30bd080fbdd93315cd4d47dbc"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"131071e42b01b53cc5a69662719d931d7d7b9990e703a53ae84096d048f9ae3c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"01675aadf0c0d8681f8d27bc981bd2d2111820e15c7fdf04009c6e0af1f455c5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5e27b5e1f5a3260b0700251b06342a148fff1c9ac5ad4432f0d95c3c658c56fa"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2c97d440a8c86cc5ac8ae887b62f78ba1adeec390673cee6fdd679465c0794d1"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1824e3e45ab7c6b5353d2d250fdad905e8465daa05c2a47572b791f92cd14ff5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0f80a9f7bc25fe7c2b512bfd60b4823141eef42adf188c14242f8bf78f6c9fbd"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6940b7e93ea186db8c9089f0730eabea3b14a6f2bcc4d5f640e79a3df0e9629f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"17fd958f28b995d154f198bc0da303f14ca5f808705a77dfd144ed6a94d9249e"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5f0ae1389418a52788093ba4a333c9fe08f8ad8625d3d0f126ff9593deee4a98"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"48ffcedfc3681408df77ecf6e1b6b1010e76eedecb534506435060091ecd2625"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"44046630593df43fc89df0f40ef2825e44d3060718c5236f3766852e595e1d65"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"22201493963df06b078882d59ef1baf520c7f8a38e683270f208fe3f11b5da1c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"01bad9a7a03e41cd6bd172f3fc4e62fa729a77cf2e722733d242526d2f5a7ecd"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"12cc9895e0990ea45556a45312c508ae9fe8f8573484156ef20db108976a284b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"216b01ea2caf761404161e279de3dc4f62da3a8dd4f469706ce0a1bb3b61448b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1b67dd2a644fc0d81bf61ca12d534a66a0220548880d3daa2dd459dae236b0de"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"565997706ebffb9d7754038a6c62645854f0df8090e0708df29c899e99022b56"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2b6581101163f525d224824c36435408e6fc905e3f317353314613def0a07518"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"68e20f87934b626a2d5f7c209d869401839a4a118b5bb8b178ce3090d20e8487"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"21d1f9e69bd479903c92a4303b730a30323e07b9bd09962714fc3e7287beb409"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"45ba1a715ae585a0893569cf924a6b7489ae3f09e258430d2b339b8ccde77007"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"280ad47f4e418e50f544e959e498e91b26f1c0d6f52338b60acda2c062ebdf30"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6ff36b9a1df788dcb81f4f9a55e7b03fa49fc3b7e6d7c25316cdbd2014f48e81"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"04dbf5bc21710d87e2f53f794c449b940c3dbf98d0c3715039be44948efee6e3"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4cce456370593b4b2c12338ba432d7d2dc8d2b954fc27ad7839953b388c7fa40"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"245136107ff6c9127cd806b4c4d551c870e803b2d6bcac2a4b8488326f5dde50"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"037537ee588437badb0e3f22d0d7bfc2cc529bd03dd760e06bfaa4814caa14ed"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"06bdcd39f7b0136caabbea18e942888444142bde27356b103aeed6b9b10613a7"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4938b50ea746606c644b6de8e69de0a1e1864b137526b661ea92e5f23e862012"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2d42164ca6c6c3fba64ca662762968296b76eaefdbcc2f72ce6c659730f06303"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2cf5ed389a2b092dde54c11fc598b5296f3f9ea6083366b8e08af58d2424797a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5e2e4955bb1026354983477b6391a75eef3ec35abc89ca0ad9b2836bbe052efd"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"23b0a43901f778461e3e19850f0a644639f5b098404462b28f9eb11dcf5c943d"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"42c426784977ee16629b7b136ffd514988b085edb2b2b5732ff6ddef06212b65"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"510c8c528c8aaefc6e7ee6636a68fe5c0d6676244c76e739f6db4350657ee7db"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1f5fa161bd17eb5cd7f51486008198a2288cfc068de4c1d52f7ce9165dbd1a32"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"723089e8c78223ff6bcf157f5d7795048d0e5f342272e157c99fca5221b91c9f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3819ad665aa0f4079328407614b2763c879aa0688462dd1d2a054a692fd625db"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"65714decc38fc058c4c9647ba3bda9deaa1a66c396f0a2ff9882db4e4bb6eed5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"11a51b2754bd7e318250260b9e937f8097aa6f5c456ac5ba7b108dec1f4e94b6"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2b94f00cf585aac715f80005811db45996cb13a743397362fcf9c66575e11c79"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0a685a3e3bca3ff7b2c6a384a0bf9f35d9a2dcdc359de738765268cb3168cb67"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"44ea929ce6e2d770840cd5e995b12643282cae1749c019df2a74fa328152b2d1"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"45c2845b5844106df6f399279ac4df7145f86caaabafdc21285923a6a74179b3"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4d41d079b8ccb0700381f9cd3445420ce2f7216919d2476d167e16ce020ba1d3"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4050fc9352e2344291a6150266d4ee85b3f348bf9e048d5b959cb418244e9d1c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"241730773066d495dc9d78b9ae20bb88625f7db54fe8922d10cf63d3f1faeafe"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"10c4dee4dd83ea316988a170a4b5f98b098131f919bba00b0b7bd458fcd8da12"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"34fe8bc397563d8317fbe40dde895fdbffb7c69d9c11cc24330d41b9b4db242b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1186fce670c53ea565b4cc9bedd44983fa02f3ed77004f78630248955dd8fcbf"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2c9d92613f842529943da94911cd4d53d89144aad4f196d65f9282d774ebe3ef"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"43c252a5adfbab1141b203505fe28a11d67c3578e268e74ec5cea27ff466fa8a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0aec0d131103b9c6e998dbc5e145158df9b668512ea007b84c8d2a69589e57c6"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"16fdd35187250871cabce08b82f94ad2a6b5e64038cebc13c3a193b2aed03525"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"52eb863553792fd28628cbe7c673c265f0200c836e8bbb3a3ad10e9dd2788f4d"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"044b950152a054968cadfa9f38f136dafcde35c8b00c1ef07231244c04ba0a44"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"461715185e0141c19d67c47351d7ad78683e1ef1b5b1d2fb337f3e0ec3f3b249"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3a6860e88b5723601efcdd3c47c13c7cd78f0f1b718369b068c8c8dff92ef071"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6f180c1c3141f5d42fa4cdb774114b9a7032ef944c6a0199443f966f63c2a1e2"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"63c9fc3b50a25e5a8ae95997e46f3c582543251ad166556ac863ac987e6bc10f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"30d882588ee8f6959b437bccc3d6ed9d2deeca82d24a1f26e2d7f22f6914082c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"04922982ba64fdfb4fd3d8a4fa3c312ae96ed18a2070d900179d47cf710cba27"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5d7d715c6f859bff0b379c04001acc2755621cf4c9b7d83d0aea9ca516340585"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"193e3bd8a15fd20590d839dcded732f17f97d47434bffc3831de95caedd7bf8f"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"152104cd9b83bc618b92993d360b4d04e7478fff837cd4af8da3e64aa9c19f3c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"447f5f3b822ad2157c6cd978078607526759f0f312beb0cf8753ae893f7bc736"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"35a49b4552bc2897670723e74f0e687ee1cab24d67a67711f030e305d8852c51"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1c23439d4cf76c447fb8afd031d562c07f75d0e5e5c91950c0e4528ed7b3110c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5aa38b685836bb01905355f1faaa3eb0bf44091f957fedb8866431b50ca21ddb"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2fb774f3e1d4ca762d402be85203ceff34ddeab9e654270864c40042cf693ae6"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4220c142b4b52f30d04f8c217fcc3f59eebbea5780acbcb6189586b586e9f4ae"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1f72922c6c67a07ac1a4ab00aeba2a2d206a979104f8332a97a42a1ba43f1914"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"378647b10cb904a603e7d8f71c01f8ae0bb3b3625d9fa395c4bb52e839cd6efd"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"67487522f6a74fa7cef58cbb07683df5491680ef85085fa407036639950ae5d1"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"19937dfeefd2158ca0bc9e683b1d325bb5c90053db94f517c6ceb9af4eb40c86"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"58745b577b25297b5690da2a6f756362e2886372b5728c9179f4009b92597fb5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"245ab486accc627b29a57d4bb9cdc50562eb3e8f2e013c55dde35f1a4c4afc85"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"01d265e5bc50c71b71644720d66cae2fcadfcfb9cd57bad131cfd436f16b3ad4"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3cae55f63a45e8ff3ecc07611f0de9d5091af977e5a9e37b00912cc8f65450d7"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4562d249ab3ab25956c3de8943594e55aecd3e5c3415883a06ee8194a04ecd77"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3178d11e65909267934d86293bb734ac15e64585f69abfc3e8a96ac96efd5443"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6626afb1b7f2e317e5ee2112695b983b77158e0047d819aaf887ff0c6fd64422"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"353fd5b33bdaa47a4edf336f9ae45d83385458c9e6d038b44090bc38a29b05de"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"48996b81338a8b87f989f69eeacecba348d0bcd96e5b9dfc78ec615ff3f572e4"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0896da0029fc2c69b7558bb1b17603cf24b45717eb7bb1af86712ba784679e18"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"130fb75572e05392617c3f551305841d859e7e066fc9dfdad0ef4df6ebb8cb5a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"49c9e00faaa7ed77efe0a387f3c5071503e0e530e83fe1356b42aa564f62e9f4"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"60cef6247d3feb13cdd27653bf4fafa8401af878a97e0a8e5dff84f26b147588"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"07e3202eb6b6e74418ee84c8323af991b2667a27b9a3da4b3913b67a3acc3ef3"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3935077739ab4d47a0618aef4f8e5d30b10b37f9339175d9eb1b636be268b2fd"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5862cf4d8acb32ed43215038ef1c7e21aef91bb8a36a55f8109900bfb155375b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"73988db21c1f117f899f4c54bdf532c9b9cb10265f18300aec5a91a6fb107812"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1947314d727a59bfd9e04162b2e12cd90e4cd82dfd8de26e9dc97b7e78ece0be"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0ac408ecdb6ad3b5cf54c15edbfaac8e6e201db89f94a4746d3b13175cf8960d"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"48f98be7724694687e4fc7e16870109ba6aacfc31aecca40ef927bdae0c339db"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"16bbe36a02d0e3e8e9f01fb110230fbdacdb0707e98ee0a54a8eacd0d907051a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"37ab84231048f467c2b936aa5d14e2104c0d35e1ddc0abcb9e39891eaa640372"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2d354479ac28e801fc69ddbd171916a382bb4d9fe19b7d7e5e47b3993d775c29"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5289eb6c95e57d1ad45e356ff215b710b00977e6b878bac2f3666c337eff957c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3c2940471aea3e0ce4cb460fa12b31443bb9d2bcf6761b7e0d13334357c973b5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"04ec541aaf70d69c8653191b4536b5d179005c975df40ffcefa000785269d1b0"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4c0c5a5528bebc895414052346322a486f3c13ea28ab92d8f3e6895440212bee"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"457d318a5dfcd57055dfe70341eb983de6d319e2865f580cd83838c1a35d0273"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"72f512e290a77f23ffb69963813bfc2889a0708c02478b163cf1ee0589c2c059"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"289bd7dd96c36c5a2cd05941aad06beda7473df7a2e09ecc0292ca3f4191ea7a"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2124419ef77feab36d68c55819258cd114a1ee496fdb8e247bb9002e403cb392"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5682cd00f9040a55c6f616db6ee35678fe44561fad92131a134ef9f6445c9e9b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4c279fbc9241e8ffc73217187428cbf39cb29dbc86a25a8bab3153146bc562bf"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"606022c0776c73bdba99263ba976580e3fa453dbc6ed3ce321495518cfac5c97"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1b3c85c78620750db6865c0ab7c02962e13bbafd26a8f62e680a06bf099e96a3"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4e88df3cf58488335c62ab4b60010b0ad3e7ec5c6fa8e1fbc819440076f39fb2"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2128334d48bb8c736754aec52e94822a683250edbca9c64bc71763b41cb5f0db"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"2efa47645723e1b1d2aba11a901c5ae5c6a3954f2da8143eda496296461638cb"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0dedea303cf72dd88c5dd088f6b64a11ff0ad7e03808cc329077cbce08ccdcb0"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"70178e70b89186bff3ef1f778729fcf11e9909d1a341fc391cca61ae7503bea9"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5506ba47f40b9142d10acfb4678db90e687060b17d1a11c3f07fa37eb759b2f9"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"433bfa74fa0fe35dc664aa104f8618ab10eb6bb09d9e200e87a5aa78560d72fb"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5c141c77fd15a20905f40a4f46a47c782cd4b9b0246cddc79e1b5133721ec369"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"50425855791ad3327346bb7dd650c040a41e909d0e9e826d00850dcab138439b"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"0ab7145a5f012039efbd8abc425d1adc640625fe6ad438845382d418af87d0cc"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"52e36072d5fc448de5583f545c97443b9722b6fd380a0abf76bd27d629252b35"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"4513c98e9becd4d01b9d9734382c0116b59583474a487c2087da3c3498c1ffd9"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"37d2f2356a2f0eee375547232c36c6756249547d8c867acc93ec1963aa3c6d31"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"3c3fabeb32f89129ad9027840b32aa28686e0627135772c7b6b9510848947a00"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"48aa721a6af9f92c658a39617590780619dbefec5e31794a1358907354d5ff2c"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"5cc0ab5e75c80c54d6001fb4e90f302e3269c87afe4166f62fde2965e7fdf7c1"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"6abc22d42dfa2da89411aa00d98aac6a68d325dc34455a6c3cc98ceab56c5836"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"55f48be93ffd19a77df336ecf30080e84669468f92216bf063ed907addb560f5"},{"kind":"ReadScalar","tag":"dummy_eval","fe_be_hex":"1123b770b00b6e62a0f3c01c42d570127854425c70a66e004b6b7ebdeea69817"},{"kind":"Challenge","tag":"x1","fe_be_hex":"1c098ae6b043e3086251d5a69213433f867bce3584401f06632376bf4cc0d51f"},{"kind":"Challenge","tag":"x2","fe_be_hex":"450e371d88aee670e1855a0118a6ef70867a135a675e6283dc8a18cc1384add8"},{"kind":"ReadPoint","tag":"f_com","eip2537_hex":"909974aed62735634516ad3d04fb1c75814b1f04420e7ba5153e3f8ea730a425fd8cc7f4b9c06bc994f9b9fb18153ccd"},{"kind":"Challenge","tag":"x3","fe_be_hex":"2f336b860308ad449b601a9a51554f543475b39cd5647fee3fe190cf524b9693"},{"kind":"ReadScalar","tag":"q_eval","fe_be_hex":"3c84ab264411455250488b3d02d4498a82654a620a29d7f86a53d62c3b7cdd10"},{"kind":"Challenge","tag":"x4","fe_be_hex":"52e5456496a5abf0c1067996c62310f623e7d6b4972e9ae2746606965965c719"},{"kind":"ReadPoint","tag":"pi","eip2537_hex":"8b260bc8f81180fc5b6bce2401c2d81895ef4e29d8ac2300fad888e667cdbd286708865ad384a4aa27bd090951cf7660"}] \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/ivc/vk.bin b/proofs/solidity-verifier/fixtures/ivc/vk.bin deleted file mode 100644 index 670ff28e0..000000000 Binary files a/proofs/solidity-verifier/fixtures/ivc/vk.bin and /dev/null differ diff --git a/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2Verifier.sol b/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2Verifier.sol new file mode 100644 index 000000000..026c4e8a6 --- /dev/null +++ b/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2Verifier.sol @@ -0,0 +1,3659 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.24; + +/// @title Halo2 BLS12-381 KZG verifier. +/// @notice Circuit-specialized verifier for Midfall/midnight-proofs Halo2 +/// proofs rendered by this repository's Rust generator. +/// @dev This contract ports the verifier flow from +/// `midfall/proofs/src/plonk/verifier.rs`, the Keccak transcript comments from +/// `midfall/proofs/src/transcript/implementors.rs`, and the KZG multi-open +/// comments from `midfall/proofs/src/poly/kzg/mod.rs`. +/// @dev It is not a generic verifier. The proof layout, VK payload, quotient +/// identity program, memory layout, and optional quotient evaluator are all +/// generated for one `VerifyingKey>`. +/// +/// Halo2 KZG verifier for the BLS12-381 curve, midnight-proofs flavour. +/// +/// Differences vs the original BN254 / halo2 v0.4 template: +// +/// - BLS12-381 base field Fp is 381 bits and does not fit in a uint256. +/// Each Fp coord is encoded EIP-2537 padded (16 zero bytes + 48 bytes). +/// A G1 point is 128 bytes (4 words); a G2 point is 256 bytes (8). +/// - Calldata carries G1 commitments in uncompressed EIP-2537 padded +/// form (4 words = 128 bytes per point: x_hi, x_lo, y_hi, y_lo). The +/// proof bytes produced by midnight-proofs prover are repacked off +/// chain (compressed -> uncompressed) before being passed to +/// `verifyProof`. The verifier hashes the uncompressed 128-byte form into +/// the transcript verbatim, matching `Hashable for G1Projective::to_input`; +/// see `common_uncompressed_g1`. +/// - Transcript `common` absorbs raw inputs in order. `squeeze` computes one +/// Keccak digest, resets the transcript buffer to that digest, then samples +/// by interpreting the digest as a big-endian integer modulo r. +/// - Scalar inversion uses modexp(scalar, r-2, r). +/// - Constructors run deployment-time smoke tests for MCOPY and the EIP-2537 +/// precompiles using identity inputs. Compile with Solidity >=0.8.24 and +/// deploy only on chains/forks that support MCOPY and EIP-2537. +contract Halo2Verifier { + + /// @notice Verifying-key contract address authorized for this verifier. + /// @dev The runtime length and codehash are pinned by generated constants and checked at construction time. + address public immutable AUTHORIZED_VK; + // Expected VK runtime metadata. The deployed VK runtime is + // INVALID || payload, hence EXPECTED_VK_LENGTH is one byte longer than + // EXPECTED_VK_PAYLOAD_LENGTH. + uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 17024; + uint256 internal constant EXPECTED_VK_LENGTH = 17025; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0xe68d89362065c8b7107055774f1e045b69bc3bffde4704541c5bc5c91c94cf52; + bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); + + // Solidity ABI calldata cursors. The generated verifier accepts exactly + // verifyProof(bytes proof, uint256[] instances), then parses the `proof` + // bytes itself in the same order as the Rust verifier transcript. + uint256 internal constant PROOF_LEN_CPTR = 0x44; + uint256 internal constant PROOF_CPTR = 0x64; + uint256 internal constant NUM_INSTANCE_CPTR = 0x1ec4; + uint256 internal constant INSTANCE_CPTR = 0x1ee4; + // First general-purpose memory words reserved by the generated verifier. + // RETURN_MPTR is a single word set to 1 on success. + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; + + // ---------------------------------------------------------------------- + // Verifying-key memory map. The VK header lives at VK_MPTR, followed + // by the quotient VM payload and commitments. After the full VK + // runtime comes the challenge slots (challenge_mptr..) and the + // per-stage scratch (theta_mptr..). + // ---------------------------------------------------------------------- + uint256 internal constant VK_MPTR = 0x3680; + uint256 internal constant VK_DIGEST_MPTR = 0x3680; + uint256 internal constant NUM_INSTANCES_MPTR = 0x36a0; + uint256 internal constant K_MPTR = 0x36c0; + uint256 internal constant N_INV_MPTR = 0x36e0; + uint256 internal constant OMEGA_MPTR = 0x3700; + uint256 internal constant OMEGA_INV_MPTR = 0x3720; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x3740; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x3760; + uint256 internal constant ACC_OFFSET_MPTR = 0x3780; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x37a0; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x37c0; + uint256 internal constant G1_BASE_MPTR = 0x37e0; + uint256 internal constant G2_BASE_MPTR = 0x3860; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x3960; + + uint256 internal constant CHALLENGE_MPTR = 0x7900; + + // Challenge layout. Squeeze order in midnight-proofs: + // user_phase challenges (variable count) + // theta -> beta, gamma -> trash_challenge -> y -> x -> + // x1, x2 -> x3 -> x4 + uint256 internal constant THETA_MPTR = 0x7900; + uint256 internal constant BETA_MPTR = 0x7920; + uint256 internal constant GAMMA_MPTR = 0x7940; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x7960; + uint256 internal constant Y_MPTR = 0x7980; + uint256 internal constant X_MPTR = 0x79a0; + uint256 internal constant X1_MPTR = 0x79c0; + uint256 internal constant X2_MPTR = 0x79e0; + uint256 internal constant X3_MPTR = 0x7a00; + uint256 internal constant X4_MPTR = 0x7a20; + + // Batch-open commitments live in 4-word EIP-2537 padded slots. + uint256 internal constant F_COM_MPTR = 0x7a40; + uint256 internal constant PI_MPTR = 0x7ac0; + + // Accumulator (KZG IVC). + uint256 internal constant ACC_LHS_MPTR = 0x7b40; + uint256 internal constant ACC_RHS_MPTR = 0x7bc0; + + // Lagrange / linearization scratch. + uint256 internal constant X_N_MPTR = 0x7c40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x7c60; + uint256 internal constant L_LAST_MPTR = 0x7c80; + uint256 internal constant L_BLIND_MPTR = 0x7ca0; + uint256 internal constant L_0_MPTR = 0x7cc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x7ce0; + // Legacy name: this is not h(x). It stores the expected opening + // scalar for the linearized commitment, i.e. the negated y-batched + // identity numerator reconstructed from the alleged evals at x. + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x7d00; + uint256 internal constant QUOTIENT_MPTR = 0x7d20; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x7dc0; + uint256 internal constant V_MPTR = 0x7de0; + uint256 internal constant FINAL_COM_MPTR = 0x7e00; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x7e80; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x7f00; // 4 words + + // Multi-prepare scratch (sized at codegen time). + uint256 internal constant ROT_POINTS_MPTR = 0x7f80; + uint256 internal constant X1_POWERS_MPTR = 0x8300; + // Q_COM materialization is currently fused into the final MSM scratch, + // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero + // reserved capacity until a future emitter starts writing Q_COM_MPTR. + uint256 internal constant Q_COM_MPTR = 0x8b20; + uint256 internal constant Q_EVAL_SET_MPTR = 0x8b20; + + // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals + // block of the proof; we keep it as a memory slot for symmetry. + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x9220; + + // Reserved 4-word slot for the G1 identity (point at infinity) in + // EIP-2537 padded form. EVM memory is zero-initialised, and we + // never write to this region, so the four `mload`s below produce + // 0,0,0,0 which is exactly the identity encoding the EIP-2537 + // ec_add / ec_mul precompiles accept. + uint256 internal constant G1_IDENTITY_MPTR = 0x9320; + + // Decoded polynomial-eval buffer (Optimisation H3). The off-chain + // Solidity proof shim rewrites proof scalars into canonical BE words, + // so `calldataload` gives the field element directly. The transcript- + // side `evaluations` loop range-checks and spills that value here so + // downstream eval references (gate evaluator + PCS q_eval Horner) + // become 3-gas `mload(...)` instead of calldata reads. + uint256 internal constant REVERSED_EVALS_MPTR = 0x9480; + uint256 internal constant SELECTOR_ACC_MPTR = 0xb140; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0xb140; + uint256 internal constant TRACE_U256_MPTR = 0xe340; + + // ---------------------------------------------------------------------- + // Per-category bases for EIP-2537 padded G1 commitments. The proof + // calldata carries 128-byte uncompressed/padded G1s after the off-chain + // proof shim repacks midnight-proofs' native compressed stream; this + // region stores the 4-word slots used by PCS / quotient-fold sections. + // + // Cumulative offsets (in words from `comms_mptr_base`): + // ADVICE_COMMS_MPTR_BASE + 0 + // LOOKUP_M_COMMS_MPTR_BASE + 4*total_advices + // PERM_Z_COMMS_MPTR_BASE + 4*total_advices + 4*num_lookups + // LOOKUP_HELPER_COMMS_MPTR_BASE + ... + 4*num_permutation_zs + // LOOKUP_Z_COMMS_MPTR_BASE + ... + 4*lookup_helper_chunks_total + // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups + // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans + // ---------------------------------------------------------------------- + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0xa140; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0xa8c0; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0xa9c0; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0xacc0; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0xadc0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0xaec0; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0xaf40; + + // BLS12-381 scalar-field modulus, used for transcript challenges and all + // Halo2 verifier arithmetic. + uint256 internal constant FR_MODULUS = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001; + + // BLS12-381 Fp modulus minus one, split like an EIP-2537 coordinate: + // high word = 16 zero bytes || top 16 coordinate bytes, low word = + // bottom 32 coordinate bytes. + uint256 internal constant BLS_P_HI = 0x000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd7; + uint256 internal constant BLS_P_MINUS_ONE_LO = 0x64774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa; + // Packed public-accumulator sentinels for the shifted coordinate codec. + // The `_WITH_ID_FLAG` variant is used only for the first x-coordinate word. + uint256 internal constant BLS_P_MINUS_ONE_PACKED_0 = 0x00000000f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa; + uint256 internal constant BLS_P_MINUS_ONE_PACKED_0_WITH_ID_FLAG = 0x00000000f38512bf6730d2a0f6b0f6241eabfffeb153ffffbafeffffffffaaaa; + uint256 internal constant BLS_P_MINUS_ONE_PACKED_1 = 0x0000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84; + + /// @notice Smoke-check the Cancun/EIP-2537 runtime features required by the verifier. + /// @dev Exercises MCOPY and identity EIP-2537 inputs to catch incompatible chain/fork configurations at deployment. + function require_eip2537_precompiles() private view { + assembly ("memory-safe") { + // Scratch is reused for every runtime-prerequisite probe. + let scratch := 0x1000 + + // MCOPY must be available because the verifier uses it for + // proof-time point/scratch staging. Execute the opcode here so a + // non-Cancun fork fails during deployment instead of later proofs. + mstore(scratch, 0x1234) + mcopy(add(scratch, 0x20), scratch, 0x20) + if iszero(eq(mload(add(scratch, 0x20)), 0x1234)) { revert(0, 0) } + + // Start the EIP-2537 probes with the identity encoding for G1/G2: + // all-zero padded words. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + + // G1ADD(identity, identity) -> identity, 128-byte return. + // This catches chains where the precompile is missing or returns a + // non-standard success shape. + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if or(or(mload(scratch), mload(add(scratch, 0x20))), or(mload(add(scratch, 0x40)), mload(add(scratch, 0x60)))) { + revert(0, 0) + } + + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + + // Worst-case generated G1MSM with all identity/zero terms -> + // identity, 128-byte return. This exercises the largest MSM input + // length rendered by this verifier instead of only a one-pair + // smoke call. + let msm_scratch := 0xb140 + for { let off := 0 } lt(off, 0x30c0) { off := add(off, 0x20) } { + mstore(add(msm_scratch, off), 0) + } + // The production verifier uses G1MSM both for commitments and as + // the subgroup validator for absorbed proof points. + if iszero(staticcall(gas(), 0x0c, msm_scratch, 0x30c0, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if or(or(mload(scratch), mload(add(scratch, 0x20))), or(mload(add(scratch, 0x40)), mload(add(scratch, 0x60)))) { + revert(0, 0) + } + + // PAIRING_CHECK([(identity_g1, identity_g2), (identity_g1, identity_g2)]) + // -> true, 32-byte return. This matches the runtime two-pair KZG + // pairing input size and catches absent pairing precompiles, + // short return data, and obviously incompatible semantics. + if iszero(staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x20)) { revert(0, 0) } + if iszero(eq(mload(scratch), 1)) { revert(0, 0) } + } + } + + + /// @notice Create a verifier pinned to a generated verifying key. + /// @dev Checks MCOPY/EIP-2537 availability and verifies the VK runtime before storing its address. + /// @param authorizedVk Address of the generated `Halo2VerifyingKey` runtime. + constructor(address authorizedVk) { + // Embedded quotient path: only the external VK runtime needs to be + // pinned, but the runtime opcode/precompile prerequisites are still + // mandatory. + require_eip2537_precompiles(); + require( + authorizedVk.code.length == EXPECTED_VK_LENGTH + && authorizedVk.codehash == EXPECTED_VK_CODEHASH, + "invalid vk" + ); + AUTHORIZED_VK = authorizedVk; + } + + /// @notice Verify a Halo2/Midfall proof for the generated verifying key. + /// @dev This checks only that `proof` verifies for the supplied public + /// `instances` under this pinned VK/protocol. Application contracts must + /// bind the meaning of those instances separately: state roots, program + /// identifiers, expected IVC outputs, chain/domain separation, and any + /// protocol-specific authorization are outside this raw verifier ABI. + /// @dev Production renders are success-or-revert: accepted proofs return + /// `true`, while malformed calldata, invalid proof material, failed + /// precompiles, or mismatched pinned dependency code revert. Trace and gas + /// renders keep the same failure policy. + /// @dev The generated verifier uses absolute Yul memory addresses instead + /// of Solidity's free-memory pointer, but generated scratch starts at + /// `0x80` so Solidity's reserved memory prefix is preserved. The main + /// assembly block remains terminal: accepted proofs return from assembly + /// and all rejected inputs revert. Do not inline this body into Solidity + /// code that continues executing after verification without reviewing the + /// memory strategy; see `docs/MEMORY_LAYOUT.md`. + /// @param proof Solidity-facing proof bytes, with G1 elements repacked into EIP-2537 padded uncompressed form. + /// @param instances Public instance scalars encoded as canonical BLS12-381 scalar-field words. + /// @return Always `true` for accepted proofs; invalid proofs revert instead of returning `false`. + function verifyProof( + bytes calldata proof, + uint256[] calldata instances + ) external view returns (bool) { + // Cheap ABI-shape guard before any generated memory work: + // - proof head must point at the bytes payload; + // - instances head must point at the generated instance array. + // + // The verifier below is a hand-rolled calldata parser. Failing here + // keeps malformed dynamic-argument layouts from being interpreted as a + // valid Midfall proof stream. + assembly ("memory-safe") { + if iszero(and(eq(calldataload(0x04), 0x40), eq(calldataload(0x24), sub(NUM_INSTANCE_CPTR, 0x04)))) { + revert(0, 0) + } + } + // Non-embedded renders pin the VK by address and codehash. The Yul + // loader rechecks the runtime before every proof and copies the + // INVALID-prefixed payload into VK_MPTR. + address vk = AUTHORIZED_VK; + assembly ("memory-safe") { + // This block owns the call-frame memory and remains terminal. + // Generated scratch starts at TRANSCRIPT_MPTR (0x80), preserving + // Solidity's reserved scratch, free-memory-pointer, and zero-slot + // words. See docs/MEMORY_LAYOUT.md. + // =============================================================== + // Helpers: modexp, transcript, EIP-2537 calls + // =============================================================== + + // Inverse of a Fr scalar via modexp(x, r-2, r). The verifier + // calls this only after transcript absorption is complete, so it + // reuses the dead transcript buffer just below VK_MPTR instead of + // a fixed post-VK address that can collide with live PCS scratch + // when the VK payload becomes smaller. + function scalar_inv(x) -> inv { + // Zero has no multiplicative inverse in Fr; callers rely on a + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } + if iszero(x) { revert(0, 0) } + let p := 0x3580 + // EIP-198 modexp frame: + // [base_len, exp_len, mod_len, base, exponent, modulus] + mstore(add(p, 0x00), 0x20) // base len + mstore(add(p, 0x20), 0x20) // exp len + mstore(add(p, 0x40), 0x20) // mod len + mstore(add(p, 0x60), x) + mstore(add(p, 0x80), sub(FR_MODULUS, 2)) + mstore(add(p, 0xa0), FR_MODULUS) + if iszero(staticcall(gas(), 0x05, p, 0xc0, p, 0x20)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x20)) { revert(0, 0) } + inv := mload(p) + } + + // ---------- Streaming Keccak256 transcript helpers ---------- + // + // The transcript buffer lives at + // memory[TRANSCRIPT_MPTR..buf_len). On verifier entry it starts + // empty. Each common(input) appends raw bytes. squeeze_*(buf_len) + // computes one Keccak digest, reseeds the buffer with that + // 32-byte digest, and samples a Fq element as + // uint256(digest_be) mod r. + + function transcript_init() -> buf_len { + // Empty transcript buffer starts exactly at TRANSCRIPT_MPTR. + buf_len := TRANSCRIPT_MPTR + } + + // Append one 32-byte big-endian field/transcript word at the + // current end of the transcript buffer. + function common_word(buf_len, word) -> ret { + mstore(buf_len, word) + ret := add(buf_len, 32) + } + + // Absorb a BLS12-381 G1 point in EIP-2537 padded + // uncompressed form (4 calldata words = 128 bytes: + // x_hi || x_lo || y_hi || y_lo, each coord = 16 zero + // pad bytes + 48 big-endian field bytes) into the + // transcript buffer at `buf_len`. + // + // Matches the patched `Hashable for + // midnight_curves::G1Projective::to_input` in + // midnight-proofs, which now emits the same 128-byte form + // (`midfall/proofs/src/transcript/implementors.rs`). The + // previous emitter hashed the 48-byte ZCash compressed + // encoding instead and ran a 384-bit `lex(y) > lex(p − y)` + // ladder + identity flag fixup to derive the sign bit on + // the fly; switching to the uncompressed form drops that + // ladder entirely. + // + // Canonicality: reject non-zero bytes in the top 16 bytes + // of each `_hi` calldata word and reject coordinates + // outside Fp. Normalizing those bytes before hashing would + // make multiple calldata encodings share one transcript. + // + // This helper does not run an independent curve/subgroup + // check. Instead, ProtocolPlan::validate rejects generated + // plans where an absorbed proof commitment would not later be + // consumed by an EIP-2537 G1MSM or pairing path, and those + // precompiles perform the curve/subgroup validation. + // + // The point's uncompressed form remains in calldata; the + // call site is responsible for `calldatacopy`-ing it into + // memory afterwards if it needs the on-curve coordinates. + function common_uncompressed_g1(buf_len, cptr) -> ret { + let x_hi_word := calldataload(cptr) + let x_lo := calldataload(add(cptr, 0x20)) + let y_hi_word := calldataload(add(cptr, 0x40)) + let y_lo := calldataload(add(cptr, 0x60)) + if shr(128, x_hi_word) { revert(0, 0) } + if shr(128, y_hi_word) { revert(0, 0) } + + let x_hi := and(x_hi_word, 0xffffffffffffffffffffffffffffffff) + let y_hi := and(y_hi_word, 0xffffffffffffffffffffffffffffffff) + if iszero(or(lt(x_hi, BLS_P_HI), and(eq(x_hi, BLS_P_HI), iszero(gt(x_lo, BLS_P_MINUS_ONE_LO))))) { + revert(0, 0) + } + if iszero(or(lt(y_hi, BLS_P_HI), and(eq(y_hi, BLS_P_HI), iszero(gt(y_lo, BLS_P_MINUS_ONE_LO))))) { + revert(0, 0) + } + + // Memcpy the 4 calldata words (128 bytes) verbatim + // into the keccak buffer. + calldatacopy(buf_len, cptr, 0x80) + ret := add(buf_len, 0x80) + } + + // One Keccak finalization + reseed. Returns the new buffer + // cursor (= TRANSCRIPT_MPTR + 32) and stores the squeezed Fq at + // `mptr`. + function squeeze_to(buf_len, mptr) -> ret { + let h0 := keccak256(TRANSCRIPT_MPTR, sub(buf_len, TRANSCRIPT_MPTR)) + // Reseed: write the 32-byte digest at start of buffer. + mstore(TRANSCRIPT_MPTR, h0) + let r := FR_MODULUS + // Sample Fq as uint256(keccak_digest_be) mod r. + mstore(mptr, mod(h0, r)) + ret := add(TRANSCRIPT_MPTR, 32) + } + + // ---------- EC primitives (EIP-2537 wrappers) ---------- + // + // These mirror the BN254 helpers but operate on 4-word G1 + // points. They use planned memory windows above Solidity's + // reserved prefix; the streaming transcript buffer is no longer + // needed once all challenges are squeezed. + + // Invert a contiguous run of Fr words in-place using Montgomery's + // batch inversion trick: + // 1. write prefix products to scratch; + // 2. invert the total product once with modexp; + // 3. walk backward to recover each individual inverse. + // + // The function returns a boolean instead of reverting so callers + // can combine it with other `success` plumbing until a section + // boundary decides whether to fail closed. + function batch_invert(success, mptr_start, mptr_end, scratch_mptr, r) -> ret { + ret := success + if iszero(ret) { leave } + // Memory ranges must be forward and word-aligned by + // construction; a reversed range is always a codegen error. + if lt(mptr_end, mptr_start) { + ret := 0 + leave + } + + let count_bytes := sub(mptr_end, mptr_start) + // Empty batch is valid and leaves memory untouched. + if iszero(count_bytes) { leave } + + // Fast path for a single denominator: avoid prefix scratch and + // just run one modexp inverse in place. + if eq(count_bytes, 0x20) { + let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } + if iszero(x) { + ret := 0 + leave + } + + let single_scratch := scratch_mptr + mstore(add(single_scratch, 0x00), 0x20) + mstore(add(single_scratch, 0x20), 0x20) + mstore(add(single_scratch, 0x40), 0x20) + mstore(add(single_scratch, 0x60), x) + mstore(add(single_scratch, 0x80), sub(r, 2)) + mstore(add(single_scratch, 0xa0), r) + ret := staticcall(gas(), 0x05, single_scratch, 0xc0, single_scratch, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + if ret { mstore(mptr_start, mload(single_scratch)) } + leave + } + + // Forward pass: scratch stores prefix products up to, but not + // including, the final element. `gp` becomes the total product. + let gp_mptr := scratch_mptr + let gp := mload(mptr_start) + let mptr := add(mptr_start, 0x20) + for {} lt(mptr, sub(mptr_end, 0x20)) {} { + gp := mulmod(gp, mload(mptr), r) + mstore(gp_mptr, gp) + mptr := add(mptr, 0x20) + gp_mptr := add(gp_mptr, 0x20) + } + gp := mulmod(gp, mload(mptr), r) + // A zero total product means at least one denominator was + // zero, so no batch inverse exists. + if iszero(gp) { + ret := 0 + leave + } + + // Invert the total product once. + mstore(add(gp_mptr, 0x00), 0x20) + mstore(add(gp_mptr, 0x20), 0x20) + mstore(add(gp_mptr, 0x40), 0x20) + mstore(add(gp_mptr, 0x60), gp) + mstore(add(gp_mptr, 0x80), sub(r, 2)) + mstore(add(gp_mptr, 0xa0), r) + ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } + let all_inv := mload(gp_mptr) + + // Backward pass: derive each inverse from the inverted total + // product and the saved prefix products. + let first_mptr := mptr_start + let second_mptr := add(first_mptr, 0x20) + gp_mptr := sub(gp_mptr, 0x20) + for {} lt(second_mptr, mptr) {} { + let inv := mulmod(all_inv, mload(gp_mptr), r) + all_inv := mulmod(all_inv, mload(mptr), r) + mstore(mptr, inv) + mptr := sub(mptr, 0x20) + gp_mptr := sub(gp_mptr, 0x20) + } + let inv_first := mulmod(all_inv, mload(second_mptr), r) + let inv_second := mulmod(all_inv, mload(first_mptr), r) + mstore(first_mptr, inv_first) + mstore(second_mptr, inv_second) + } + + // Final EIP-2537 pairing wrapper. `lhs_mptr` and `rhs_mptr` are + // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. + function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { + ret := success + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } + // Lay out two (G1, G2) pairs at scratch..scratch+0x300: + // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] + // Cancun MCOPY (3 + 3·words gas) replaces what used to + // be a 4-step mstore chain for each G1 (~60 gas) and an + // 8-iter mstore loop for each G2 (~240 gas). Net saving + // here is ~500 gas per ec_pairing call. + let scratch := 0x0320 + mcopy(scratch, lhs_mptr, 0x80) + mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) + mcopy(add(scratch, 0x180), rhs_mptr, 0x80) + mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) + ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) + ret := and(ret, eq(returndatasize(), 0x20)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) + if iszero(ret) { revert(0, 0) } + ret := 1 + } + + // ---------- IVC accumulator public-input decoding ---------- + // + // `AssignedForeignPoint` exposes each base-field coordinate + // through `AssignedField::as_public_input`: seven radix-2^56 limbs of + // (coord - 1) are packed four-at-a-time into native field elements. + // The x coordinate's first packed word carries the identity flag by + // adding one raw radix base. Rebuild EIP-2537 padded + // (x_hi, x_lo, y_hi, y_lo) words from that encoding. + // + // Public-input layout for one coordinate: + // word 0: limb_0 | limb_1 << bits | ... up to limbs_per_word + // word 1: next limbs, if any + // + // The limbs are little-endian in the represented integer even + // though calldata words are loaded as big 256-bit values. The loop + // below extracts each limb by shifting inside the packed word and + // reconstructs the full coordinate into the two-word EIP-2537 + // representation expected by the BLS12-381 precompiles. + function load_acc_coord_shifted(src, bits, n, base, limbs_per_word, first_adjust) -> hi, lo { + // Mask for one radix limb, e.g. 2^56 - 1 for the current + // BLS12-381 self-emulation parameters. + let mask := sub(base, 1) + for { let i := 0 } lt(i, n) { i := add(i, 1) } { + // Limb words are little-endian packed inside each Fr + // public input. `first_adjust` removes the identity flag + // base from the first x word when present. + let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { + packed := sub(packed, first_adjust) + } + // Select limb i from its packed field word. The mod/div + // pair maps a limb index to an intra-word limb slot and + // the calldata word containing it. + let limb := and(shr(mul(mod(i, limbs_per_word), bits), packed), mask) + + let shift := mul(i, bits) + // Split the reconstructed 384-bit coordinate into the + // EIP-2537 high/low words expected by the precompiles. + if lt(shift, 256) { + lo := add(lo, shl(shift, limb)) + if gt(add(shift, bits), 256) { + // A limb can straddle the 256-bit low/high split. + // Move the overflow bits into hi. + hi := add(hi, shr(sub(256, shift), limb)) + } + } + if iszero(lt(shift, 256)) { + // Once shift >= 256 the whole limb belongs to hi. + hi := add(hi, shl(sub(shift, 256), limb)) + } + } + } + + // The shifted coordinate codec represents zero as p-1 before the + // final +1 below, so keep this sentinel explicit. + function is_bls_p_minus_one(hi, lo) -> yes { + yes := and(eq(hi, BLS_P_HI), eq(lo, BLS_P_MINUS_ONE_LO)) + } + + // Canonical encoded accumulator identity: + // x = p-1 plus the identity flag in the first packed word, + // y = p-1 with no identity flag. + // It decodes to the EIP-2537 point-at-infinity slot (all zeros). + // + // This fast path is deliberately stricter than "decodes to zero": + // the point at infinity has exactly one accepted public-input + // encoding. Non-canonical zero-like encodings are rejected later. + function is_acc_encoded_identity(src) -> yes { + yes := and( + and( + eq(calldataload(src), BLS_P_MINUS_ONE_PACKED_0_WITH_ID_FLAG), + eq(calldataload(add(src, 0x20)), BLS_P_MINUS_ONE_PACKED_1) + ), + and( + eq(calldataload(add(src, 0x40)), BLS_P_MINUS_ONE_PACKED_0), + eq(calldataload(add(src, 0x60)), BLS_P_MINUS_ONE_PACKED_1) + ) + ) + } + + // Reject unused high bits in the packed public-input words. This + // makes each accumulator point encoding canonical before it reaches + // the precompile-based curve/subgroup validation. + function check_acc_coord_packing(src, bits, n, limbs_per_word) -> ok { + ok := 1 + // Number of packed native-field public-input words occupied by + // one coordinate. + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + for { let word_idx := 0 } lt(word_idx, coord_words) { word_idx := add(word_idx, 1) } { + // The final word may contain fewer than limbs_per_word + // limbs. Any unused high bits must be zero, otherwise the + // same coordinate would have multiple calldata encodings. + let remaining := sub(n, mul(word_idx, limbs_per_word)) + let limbs_in_word := limbs_per_word + if lt(remaining, limbs_per_word) { + limbs_in_word := remaining + } + let used_bits := mul(limbs_in_word, bits) + if lt(used_bits, 256) { + // shl(used_bits, 1) == 2^used_bits. The packed word + // must be strictly less than that bound. + ok := and(ok, lt(calldataload(add(src, mul(word_idx, 0x20))), shl(used_bits, 1))) + } + } + } + + // Decode one shifted coordinate. `allow_id` is true only for x, + // because the identity flag lives in x's first packed word. + function load_acc_coord(src, allow_id, bits, n, base, limbs_per_word) -> ok, hi, lo, is_id { + ok := check_acc_coord_packing(src, bits, n, limbs_per_word) + if and(allow_id, iszero(lt(calldataload(src), base))) { + // Probe the x identity flag by removing one radix base and + // checking whether the adjusted coordinate is p-1. + // + // `calldataload(src) >= base` is a cheap prefilter: only x + // can carry this flag, and adding one radix base must make + // the first packed word at least base. + let adj_hi, adj_lo := load_acc_coord_shifted(src, bits, n, base, limbs_per_word, base) + is_id := is_bls_p_minus_one(adj_hi, adj_lo) + } + + // Decode again with the identity adjustment applied only when + // the canonical identity flag was actually detected. + hi, lo := load_acc_coord_shifted(src, bits, n, base, limbs_per_word, mul(is_id, base)) + ok := and( + ok, + // Coordinate must be in the BLS12-381 base field, i.e. + // <= p - 1 in split hi/lo form. + or(lt(hi, BLS_P_HI), and(eq(hi, BLS_P_HI), iszero(gt(lo, BLS_P_MINUS_ONE_LO)))) + ) + + let was_p_minus_one := is_bls_p_minus_one(hi, lo) + if was_p_minus_one { + // Shifted encoding maps p-1 back to zero. + hi := 0 + lo := 0 + } + if iszero(was_p_minus_one) { + // All other coordinates are encoded as coord - 1, so add + // one back with carry into the high word. + let next_lo := add(lo, 1) + hi := add(hi, lt(next_lo, lo)) + lo := next_lo + } + + // EIP-2537 pads each 48-byte Fp coordinate to 64 bytes, + // so the high word must fit in its low 128 bits. + // This also catches impossible reconstructions above 384 bits. + ok := and(ok, lt(hi, shl(128, 1))) + } + + // Decode a public accumulator point into an EIP-2537 4-word G1 + // slot. Non-identity points are curve/subgroup checked later by + // routing them through G1MSM. + function load_acc_point(dst, src, bits, n, base) -> ok, is_id { + // Prefer the canonical all-coordinate identity encoding before + // attempting coordinate-level shifted decoding. This accepts + // the point at infinity only in the exact form generated by the + // circuit's public-input codec. + is_id := is_acc_encoded_identity(src) + if is_id { + ok := 1 + // EIP-2537 encodes G1 identity as four zero words: + // x_hi = x_lo = y_hi = y_lo = 0. + mstore(dst, 0) + mstore(add(dst, 0x20), 0) + mstore(add(dst, 0x40), 0) + mstore(add(dst, 0x60), 0) + } + if iszero(is_id) { + // x occupies coord_words packed public-input words; y + // starts immediately after x. + let limbs_per_word := 4 + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + // Only x may carry the identity flag. y must decode as a + // normal shifted coordinate. + let x_ok, x_hi, x_lo, x_is_id := load_acc_coord(src, 1, bits, n, base, limbs_per_word) + let y_ok, y_hi, y_lo, y_id := load_acc_coord( + add(src, mul(coord_words, 0x20)), + 0, + bits, + n, + base, + limbs_per_word + ) + // y_id is always zero because allow_id was false, but the + // tuple shape is shared with x decoding. + pop(y_id) + ok := and(x_ok, y_ok) + is_id := x_is_id + + if is_id { + // If x carried the identity flag, both decoded + // coordinates must be zero after shifting. Any other y + // value would be a malformed infinity encoding. + ok := and(ok, iszero(or(or(x_hi, x_lo), or(y_hi, y_lo)))) + mstore(dst, 0) + mstore(add(dst, 0x20), 0) + mstore(add(dst, 0x40), 0) + mstore(add(dst, 0x60), 0) + } + if iszero(is_id) { + // The coordinate codec maps encoded p-1 to decoded + // zero. EIP-2537 reserves affine (0,0) for the point + // at infinity, so a decoded infinity is only valid + // when the canonical accumulator identity encoding + // was used above. + let decoded_zero := iszero(or(or(x_hi, x_lo), or(y_hi, y_lo))) + ok := and(ok, iszero(decoded_zero)) + // Store the affine point in the exact precompile input + // layout: x_hi, x_lo, y_hi, y_lo. + mstore(dst, x_hi) + mstore(add(dst, 0x20), x_lo) + mstore(add(dst, 0x40), y_hi) + mstore(add(dst, 0x60), y_lo) + } + } + } + // Validate and prepare the public accumulator equation before the + // main transcript starts. This fails malformed public inputs early + // and writes ACC_LHS_MPTR / ACC_RHS_MPTR for final pairing batching. + // + // The accumulator public input represents an equality of two G1 + // commitments used by the recursive KZG accumulator. This helper: + // 1. decodes carried public G1 points from shifted limbs; + // 2. forces every decoded point through EIP-2537 G1MSM so the + // precompile validates curve/subgroup membership; + // 3. folds the RHS carried point and fixed-base scalar tail into + // ACC_RHS_MPTR, leaving ACC_LHS_MPTR / ACC_RHS_MPTR ready for + // randomized batching in FinalPairing.yul. + function validate_public_accumulator(success, r) -> out { + out := success + let bits := 56 + let n := 7 + // The BLS12-381 self-emulation currently exposes Fp + // coordinates as 7 radix-2^56 limbs. + let limb_base := shl(bits, 1) + let limbs_per_word := 4 + let coord_words := div(add(n, sub(limbs_per_word, 1)), limbs_per_word) + // acc_offset is generated from the VK/protocol shape and + // points into the ABI `instances` array. + let acc_instance_ptr := add(INSTANCE_CPTR, 0x0160) + + // LHS layout: point limbs (x,y), then either an explicit + // scalar word or an implicit unit scalar for already-collapsed + // point-pair public inputs. + // The scalar pointer is computed unconditionally; the rendered + // branch below decides whether to read it or use scalar 1. + let lhs_scalar_ptr := add(acc_instance_ptr, mul(mul(2, coord_words), 0x20)) + let lhs_ok, lhs_is_id := load_acc_point(ACC_LHS_MPTR, acc_instance_ptr, bits, n, limb_base) + out := and(out, lhs_ok) + // Shared scratch for one-pair LHS validation and the later + // variable-length RHS MSM. + let acc_scratch := 0xb140 + { + // Already-collapsed point-pair layout: carried scalars are + // implicit one. + let lhs_scalar := 1 + // Identity status is useful for decoding checks above, but + // validation still goes through G1MSM for all points. + pop(lhs_is_id) + // Always route the decoded carried point through G1MSM, + // even for identity points and zero/one scalars. The + // precompile is the on-curve/subgroup validator for this + // public-input point; skipping it would let a malformed + // non-identity point hide behind scalar 0. + mcopy(acc_scratch, ACC_LHS_MPTR, 0x80) + mstore(add(acc_scratch, 0x80), lhs_scalar) + if out { + // Single-pair MSM output overwrites ACC_LHS_MPTR with + // lhs_scalar * decoded_lhs. If lhs_scalar is one, this + // is also a curve/subgroup validation round-trip. + out := staticcall(gas(), 0x0c, acc_scratch, 0xa0, ACC_LHS_MPTR, 0x80) + out := and(out, eq(returndatasize(), 0x80)) + } + } + // RHS layout for this generated verifier is an already + // collapsed point pair: lhs point, rhs point. Both carried + // scalars are implicit one, and there is no fixed-base scalar + // tail. + let rhs_instance_ptr := lhs_scalar_ptr + // RHS scalar, when present, immediately follows the RHS point + // limbs. The fixed-base scalar tail starts after it. + let rhs_scalar_ptr := add(rhs_instance_ptr, mul(mul(2, coord_words), 0x20)) + let rhs_ok, rhs_is_id := load_acc_point(ACC_RHS_MPTR, rhs_instance_ptr, bits, n, limb_base) + out := and(out, rhs_ok) + // acc_pair_ptr appends (G1, scalar) pairs into acc_scratch for + // one final RHS MSM. + let acc_pair_ptr := acc_scratch + { + // Implicit unit scalar for already-collapsed point pairs. + let rhs_scalar := 1 + pop(rhs_is_id) + // Keep the carried RHS point in the MSM input even when + // it is encoded as identity or has scalar 0/1, so EIP-2537 + // validates every decoded public accumulator point before + // it can affect, or be erased from, the pairing batch. + mcopy(acc_pair_ptr, ACC_RHS_MPTR, 0x80) + mstore(add(acc_pair_ptr, 0x80), rhs_scalar) + // Move to the next (G1, scalar) pair slot. + acc_pair_ptr := add(acc_pair_ptr, 0xa0) + } + // Total byte length of the appended RHS MSM input pairs. This + // is at least one pair because the carried RHS point is always + // appended; keep the guard for synthetic render configurations. + let acc_msm_len := sub(acc_pair_ptr, acc_scratch) + if acc_msm_len { + // Fold the carried RHS point and any generated fixed-base + // tail into ACC_RHS_MPTR. The later final pairing block + // randomizes this equation together with the KZG pairing. + if out { + // Output overwrites ACC_RHS_MPTR with: + // rhs_scalar * carried_rhs + // + sum_i fixed_scalar_i * fixed_base_i + // + // The precompile also validates every nonzero fixed + // base embedded by codegen and the carried RHS point. + out := staticcall( + gas(), + 0x0c, + acc_scratch, + acc_msm_len, + ACC_RHS_MPTR, + 0x80 + ) + out := and(out, eq(returndatasize(), 0x80)) + } + } + // The caller checks `out` and reverts before transcript work if + // any decode, canonicality, or precompile validation failed. + } + + + + let r := FR_MODULUS + let success := true + + + + // =============================================================== + // VK loading: either bake in the embedded VK bytes or fetch + // them from the linked AUTHORIZED_VK contract. + // + // This is the first verifier phase after helper definitions. Its + // job is to make the generated VK payload available at VK_MPTR in + // one canonical memory layout, regardless of whether this render + // embeds the VK directly or links a separate Halo2VerifyingKey + // contract. + // + // Later template partials treat VK_MPTR as already populated with: + // - header words: vk_digest, domain data, accumulator metadata; + // - BLS12-381 base points used by the final pairing; + // - compact quotient VM constants/program bytes, when enabled; + // - fixed and permutation commitments in 4-word G1 slots. + // =============================================================== + { + // Re-check the pinned VK dependency on every proof. The + // constructor check catches normal deployment mistakes, while + // this fresh check hardens forks or same-transaction edge + // cases where code at the authorized address could differ + // from the runtime originally pinned by this verifier. + // + // EXPECTED_VK_LENGTH includes the leading INVALID byte in the + // Halo2VerifyingKey runtime. EXPECTED_VK_CODEHASH_WORD is the + // full runtime hash, not only the payload hash. + if iszero(and( + eq(extcodesize(vk), EXPECTED_VK_LENGTH), + eq(extcodehash(vk), EXPECTED_VK_CODEHASH_WORD) + )) { revert(0, 0) } + // Runtime byte 0 is INVALID so direct calls cannot execute the + // payload. Copy from byte 1 into VK_MPTR to reconstruct the + // exact payload layout used by the embedded branch. + extcodecopy(vk, VK_MPTR, 0x01, EXPECTED_VK_PAYLOAD_LENGTH) + + // Cross-check loaded VK header words against the verifier + // constants used by later parser, domain, and accumulator + // paths. Codehash pinning protects the external VK address; + // these checks catch generator drift before calldata parsing + // chooses a stale schema. + success := and(success, eq(mload(NUM_INSTANCES_MPTR), 19)) + success := and(success, eq(mload(K_MPTR), 20)) + success := and(success, eq(mload(HAS_ACCUMULATOR_MPTR), 1)) + success := and(success, eq(mload(ACC_OFFSET_MPTR), 11)) + success := and(success, eq(mload(NUM_ACC_LIMBS_MPTR), 7)) + success := and(success, eq(mload(NUM_ACC_LIMB_BITS_MPTR), 56)) + if iszero(success) { revert(0, 0) } + // + // The checks below validate the dynamic ABI envelope before the + // transcript parser starts walking raw calldata: + // - proof bytes length equals the generated proof layout; + // - instance array length equals the generated public input + // count; + // - total calldata length has no missing or trailing words. + // + // `success` is folded through `and` for consistency with later + // sections, then immediately enforced at the end of this block. + // A failure here means the verifier is not looking at the proof + // shape it was generated to parse. + success := and(success, eq(0x1e60, calldataload(PROOF_LEN_CPTR))) + success := and(success, eq(19, calldataload(NUM_INSTANCE_CPTR))) + // Calldata must contain exactly the ABI selector, proof bytes, + // instance-array length, and generated number of instance + // words. Any trailing bytes fail closed. + success := and( + success, + eq(calldatasize(), add(INSTANCE_CPTR, 0x0260)) + ) + // Stop before any transcript absorption if the ABI/proof shape + // is not exactly the generated one. + if iszero(success) { revert(0, 0) } + } + // Fail malformed accumulator public inputs before transcript, + // quotient, PCS, and final pairing work. The late accumulator block + // only batches these already-validated G1 outputs into the final + // pairing equation. + // + // Accumulator validation decodes shifted public-input limbs into + // EIP-2537 G1 slots, checks canonical encodings, and routes points + // through G1MSM for curve/subgroup validation. Doing it here means + // invalid accumulator public inputs cannot influence transcript + // challenge derivation or waste gas in later quotient/PCS work. + // validate_public_accumulator returns a boolean to share the same + // success-plumbing style as other helper calls; this boundary is + // where the verifier converts failure to a revert. + success := validate_public_accumulator(success, r) + if iszero(success) { revert(0, 0) } + + // =============================================================== + // Transcript: VK digest + instances + proof. + // + // This block is the Solidity mirror of the native Midfall verifier + // transcript schedule. It does three jobs at once: + // + // 1. Absorb public data and proof bytes into the streaming + // Keccak transcript in exactly the native order. + // 2. Decode/range-check proof scalars and canonical G1 calldata. + // 3. Copy proof commitments/evaluations into planned memory + // slots consumed by Lagrange, quotient, PCS, and pairing + // blocks later in the verifier. + // + // `buf_len` is a write cursor into the transcript buffer. The + // helper functions append bytes and return the new cursor; squeeze + // helpers hash memory[TRANSCRIPT_MPTR..buf_len), reseed the buffer + // with the digest, and write the sampled Fr challenge to memory. + // =============================================================== + let buf_len := transcript_init() + // VK_DIGEST_MPTR holds the digest as a BE 32-byte word (the + // VK contract stores it via `mstore`, which matches the + // Keccak Fq transcript input). + // + // This digest commits to the verifier key / constraint system + // before any proof material is read. + buf_len := common_word(buf_len, mload(VK_DIGEST_MPTR)) + + // Absorb committed_pi = G1Affine::identity() when the + // `committed-instances` feature is on in midnight-proofs. + // Under the patched `Hashable::to_input` (see + // `midfall/proofs/src/transcript/implementors.rs`), the + // identity hashes as 128 zero bytes (EIP-2537 (0,0) + // convention), NOT the 48-byte ZCash compressed form + // 0xc0||47*0x00 that the previous emitter produced. + // Native verifier absorbs this BEFORE the instance count. + { + // 128 zero bytes: zero out 4 consecutive 32-byte words + // at buf_len. + // This is a raw transcript absorb, not a memory slot kept for + // later elliptic-curve operations. + mstore(buf_len, 0) + mstore(add(buf_len, 0x20), 0) + mstore(add(buf_len, 0x40), 0) + mstore(add(buf_len, 0x60), 0) + buf_len := add(buf_len, 0x80) + } + + { + // Native verifier absorbs a length scalar before instance + // values; Keccak Fq transcript input is canonical BE. + // The ABI length was already checked against this generated + // constant in VkLoading.yul. + buf_len := common_word(buf_len, 19) + + let instance_cptr := INSTANCE_CPTR + for { let instance_cptr_end := add(instance_cptr, 0x0260) } + lt(instance_cptr, instance_cptr_end) + { instance_cptr := add(instance_cptr, 0x20) } { + let inst_be := calldataload(instance_cptr) + // Public inputs are BLS12-381 scalar-field elements. They + // must be canonical before transcript absorption; accepting + // non-canonical encodings would admit transcript aliases. + success := and(success, lt(inst_be, r)) + // Instances are passed BE in calldata, matching the + // Keccak Fq transcript input. + buf_len := common_word(buf_len, inst_be) + } + if iszero(success) { revert(0, 0) } + } + + // =============================================================== + // Per-user-phase reads + challenge squeezes. + // + // Each proof G1 is already EIP-2537 padded in calldata. The + // verifier validates and absorbs that 128-byte form, then copies + // it into the corresponding per-category MPTR. The PCS / + // quotient-fold blocks below dereference those MPTRs. + // + // All G1 reads follow the same pattern: + // - common_uncompressed_g1 canonicalizes/range-checks the two Fp + // coordinates and appends the exact 128 calldata bytes; + // - calldatacopy stores the same 4-word G1 slot in planned + // memory for later EIP-2537 precompile calls; + // - proof_cptr advances by one G1 byte length. + // =============================================================== + // proof_cptr walks the raw proof bytes inside the ABI `bytes` + // payload. Every successful read advances it exactly once, and the + // final equality check below proves the parser consumed the whole + // generated proof layout. + let proof_cptr := PROOF_CPTR + // advice_walk mirrors proof commitment order into the contiguous + // G1 commitment memory region used by PCS and quotient folding. + let advice_walk := ADVICE_COMMS_MPTR_BASE + // ---- User phase 1 ---- + // Advice commitments for this phase are absorbed before the phase's + // challenge squeezes. The number of commitments and challenges is + // generated from the protocol plan. + for { let end := add(proof_cptr, 0x0780) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + // Store the commitment at its phase-ordered advice slot. + calldatacopy(advice_walk, proof_cptr, 0x80) + advice_walk := add(advice_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- theta ---- + // From this point onward the transcript alternates between + // squeezed challenges and proof commitments exactly as + // midnight-proofs does in `plonk/verifier.rs`. + // theta batches lookup input expressions. + buf_len := squeeze_to(buf_len, THETA_MPTR) + // ---- multiplicities (one G1 per lookup) ---- + // Lookup multiplicity commitments are absorbed after theta and + // copied into their own contiguous G1 region. + let lookup_m_walk := LOOKUP_M_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0100) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_m_walk, proof_cptr, 0x80) + lookup_m_walk := add(lookup_m_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- beta, gamma ---- + // beta and gamma are the permutation/lookup randomizers. They are + // squeezed after lookup multiplicities and before permutation + // product commitments, matching the native verifier schedule. + buf_len := squeeze_to(buf_len, BETA_MPTR) + buf_len := squeeze_to(buf_len, GAMMA_MPTR) + // ---- permutation Z products ---- + // Permutation product commitments are used by the permutation + // identities in the quotient numerator and later by PCS openings. + let perm_z_walk := PERM_Z_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0300) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(perm_z_walk, proof_cptr, 0x80) + perm_z_walk := add(perm_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // ---- lookup helpers + accumulators (per-lookup) ---- + // Each lookup contributes zero or more helper commitments followed + // by its lookup accumulator Z commitment. The generated layout keeps + // helper commitments and accumulator commitments in separate memory + // regions because the quotient/PCS schedules address them + // differently. + let lookup_helper_walk := LOOKUP_HELPER_COMMS_MPTR_BASE + let lookup_z_walk := LOOKUP_Z_COMMS_MPTR_BASE + // lookup 0: 1 helper(s) + 1 acc + // Helper commitments for lookup 0. + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_helper_walk, proof_cptr, 0x80) + lookup_helper_walk := add(lookup_helper_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // Accumulator commitment for lookup 0. This is + // always one G1 when the lookup section is present. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_z_walk, proof_cptr, 0x80) + lookup_z_walk := add(lookup_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + // lookup 1: 1 helper(s) + 1 acc + // Helper commitments for lookup 1. + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_helper_walk, proof_cptr, 0x80) + lookup_helper_walk := add(lookup_helper_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + // Accumulator commitment for lookup 1. This is + // always one G1 when the lookup section is present. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(lookup_z_walk, proof_cptr, 0x80) + lookup_z_walk := add(lookup_z_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // ---- trash_challenge ---- + // Midnight squeezes this challenge unconditionally, even when the + // circuit has no trash arguments. + // Keeping this squeeze unconditional preserves transcript + // compatibility across circuits with and without trash columns. + buf_len := squeeze_to(buf_len, TRASH_CHALLENGE_MPTR) + // ---- trashcans ---- + // Trashcan commitments are optional, but when present they are + // absorbed before y so the quotient batching challenge binds them. + let trashcan_walk := TRASHCAN_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x80) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(trashcan_walk, proof_cptr, 0x80) + trashcan_walk := add(trashcan_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- y ---- + // y batches all quotient identities. Quotient commitments are read + // only after y is sampled, matching the Rust verifier flow. + buf_len := squeeze_to(buf_len, Y_MPTR) + + // ---- quotient commitment(s) ---- + // Each uncompressed quotient commitment is calldatacopied directly to + // QUOTIENT_LIMB_COMMS_MPTR_BASE; the Horner fold below reads + // them back from memory. common_uncompressed_g1 absorbs the + // 128-byte calldata form into the transcript verbatim. + // + // Multi-limb quotient mode reads several Q_i commitments; single-H + // mode renders this loop with one limb. + let quotient_walk := QUOTIENT_LIMB_COMMS_MPTR_BASE + for { let end := add(proof_cptr, 0x0200) } + lt(proof_cptr, end) + {} { + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(quotient_walk, proof_cptr, 0x80) + quotient_walk := add(quotient_walk, 0x80) + proof_cptr := add(proof_cptr, 0x80) + } + + // ---- x ---- + // x is the main evaluation point. Values read after this point are + // alleged polynomial evaluations at x or derived PCS openings. + buf_len := squeeze_to(buf_len, X_MPTR) + + // ---- evaluations ---- + // Optimisation H3: the off-chain Solidity proof shim rewrites + // proof scalars into BE calldata words. Spill each decoded eval + // into REVERSED_EVALS_MPTR in the same iteration we range-check + // it, so downstream references can use cheap mload. + // + // The Rust verifier conceptually reads evaluations in query order. + // The lowering plan arranges REVERSED_EVALS_MPTR in the order used + // by the quotient VM/direct evaluator, hence the generated name. + { + let eval_buf := REVERSED_EVALS_MPTR + for { let end := add(proof_cptr, 0x0cc0) } + lt(proof_cptr, end) + {} { + let eval := calldataload(proof_cptr) + // Proof evaluation scalars must be canonical Fr elements + // before they are absorbed or made available to quotient + // reconstruction. + if iszero(lt(eval, r)) { revert(0, 0) } + // Spill for quotient numerator and PCS codegen. + mstore(eval_buf, eval) + eval_buf := add(eval_buf, 0x20) + // Absorb the exact BE field word used by the native + // Keccak transcript. + buf_len := common_word(buf_len, eval) + proof_cptr := add(proof_cptr, 0x20) + } + } + + // ---- x1, x2 ---- + // x1 and x2 batch the KZG multi-opening reduction. They are + // squeezed after all polynomial evaluations are absorbed. + buf_len := squeeze_to(buf_len, X1_MPTR) + buf_len := squeeze_to(buf_len, X2_MPTR) + + // ---- f_com (1 uncompressed G1) ---- + // f_com is the commitment to the batched polynomial used by the PCS + // multi-open protocol. It is both transcript material and later + // pairing/MSM input. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(F_COM_MPTR, proof_cptr, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // ---- x3 ---- + // x3 is the PCS evaluation point for f_com. + buf_len := squeeze_to(buf_len, X3_MPTR) + // truncated-challenges mirrors midnight-proofs + // proofs/src/poly/kzg/mod.rs: + // - x3 is the f_com evaluation point and is truncated + // immediately after squeeze. + // - x1 and x4 remain full squeezed Fr words, but later PCS + // batching stores truncate(x1^i) and truncate(x4^i) while + // keeping the internal power accumulators full precision. + // This direct x3 mask is therefore one part of the PCS truncation + // rule, not the only truncated value used by the verifier. + mstore(X3_MPTR, and(mload(X3_MPTR), 0xffffffffffffffffffffffffffffffff)) + + // ---- q_evals (one Fq per point set) ---- + // q_evals are not spilled into REVERSED_EVALS_MPTR because the PCS + // emitter reads them as a contiguous calldata range from the saved + // Q_EVAL_CPTR_MPTR cursor. + // + // Each q_eval is the claimed evaluation for one prepared point set + // in the KZG multi-open reduction. They are still transcript + // material and must be range-checked as Fr scalars. + mstore(Q_EVAL_CPTR_MPTR, proof_cptr) + for { let end := add(proof_cptr, 0xa0) } + lt(proof_cptr, end) + {} { + let eval := calldataload(proof_cptr) + // Canonical Fr check before transcript absorption. + if iszero(lt(eval, r)) { revert(0, 0) } + buf_len := common_word(buf_len, eval) + proof_cptr := add(proof_cptr, 0x20) + } + + // ---- x4 ---- + // x4 is the final PCS batching challenge, sampled after q_evals + // and before the opening proof point pi. + buf_len := squeeze_to(buf_len, X4_MPTR) + + // ---- pi (1 uncompressed G1) ---- + // pi is the KZG opening proof commitment. It is the last proof + // object absorbed into the transcript and later becomes one side of + // the final pairing check. + buf_len := common_uncompressed_g1(buf_len, proof_cptr) + calldatacopy(PI_MPTR, proof_cptr, 0x80) + proof_cptr := add(proof_cptr, 0x80) + + // The hand-rolled proof parser must consume exactly the ABI + // `proof` bytes before the `instances` length word. This is + // redundant with the generated proof length today, but makes + // future proof-layout drift fail closed. + // + // NUM_INSTANCE_CPTR is the calldata word immediately after the + // dynamic proof bytes payload. If proof_cptr lands anywhere else, + // some section was under-read or over-read. + if iszero(eq(proof_cptr, NUM_INSTANCE_CPTR)) { revert(0, 0) } + + // `success` carries deferred canonicality failures from public + // instance reads. G1/proof scalar helpers revert immediately. + if iszero(success) { revert(0, 0) } + + // =============================================================== + // Lagrange & instance-evaluation block (pure Fr arithmetic). + // =============================================================== + { + let k := 20 + let x := mload(X_MPTR) + // Compute x^n by repeated squaring, with n = 2^k. + let x_n := x + for { let idx := 0 } lt(idx, k) { idx := add(idx, 1) } { + x_n := mulmod(x_n, x_n, r) + } + + let omega := mload(OMEGA_MPTR) + + // First pass writes denominators (x - omega_i) for every + // Lagrange value needed below, then appends x^n - 1. The + // batch inversion pass turns all of them into inverses in one + // modexp call. + let mptr := X_N_MPTR + let mptr_end := add(mptr, 0x03a0) + for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } + lt(mptr, mptr_end) + { mptr := add(mptr, 0x20) } { + mstore(mptr, addmod(x, sub(r, pow_of_omega), r)) + pow_of_omega := mulmod(pow_of_omega, omega, r) + } + let x_n_minus_1 := addmod(x_n, sub(r, 1), r) + mstore(mptr_end, x_n_minus_1) + success := batch_invert(success, X_N_MPTR, add(mptr_end, 0x20), BATCH_INV_SCRATCH_MPTR, r) + + // Convert inverted denominators into Lagrange evaluations: + // L_i(x) = (x^n - 1) * n^-1 * omega_i / (x - omega_i). + mptr := X_N_MPTR + let l_i_common := mulmod(x_n_minus_1, mload(N_INV_MPTR), r) + for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } + lt(mptr, mptr_end) + { mptr := add(mptr, 0x20) } { + mstore(mptr, mulmod(l_i_common, mulmod(mload(mptr), pow_of_omega, r), r)) + pow_of_omega := mulmod(pow_of_omega, omega, r) + } + + // l_blind is the sum of the negative-rotation Lagrange terms + // used by the midnight-proofs blinding identity. + let l_blind := mload(add(X_N_MPTR, 0x20)) + let l_i_cptr := add(X_N_MPTR, 0x40) + for { let l_i_cptr_end := add(X_N_MPTR, 0x0140) } + lt(l_i_cptr, l_i_cptr_end) + { l_i_cptr := add(l_i_cptr, 0x20) } { + l_blind := addmod(l_blind, mload(l_i_cptr), r) + } + + // Public instance polynomial evaluation at x. Instance words + // have already been range-checked and absorbed in transcript + // order; this loop only forms the linear combination. + let instance_eval := 0 + for { + let instance_cptr := INSTANCE_CPTR + let instance_cptr_end := add(instance_cptr, 0x0260) + } + lt(instance_cptr, instance_cptr_end) + { instance_cptr := add(instance_cptr, 0x20) + l_i_cptr := add(l_i_cptr, 0x20) } { + instance_eval := addmod(instance_eval, mulmod(mload(l_i_cptr), calldataload(instance_cptr), r), r) + } + + // Persist the derived values into named memory slots consumed + // by quotient reconstruction and PCS preparation. + let x_n_minus_1_inv := mload(mptr_end) + let l_last := mload(X_N_MPTR) + let l_0 := mload(add(X_N_MPTR, 0x0140)) + + mstore(X_N_MPTR, x_n) + mstore(X_N_MINUS_1_INV_MPTR, x_n_minus_1_inv) + mstore(L_LAST_MPTR, l_last) + mstore(L_BLIND_MPTR, l_blind) + mstore(L_0_MPTR, l_0) + mstore(INSTANCE_EVAL_MPTR, instance_eval) + } + + if iszero(success) { revert(0, 0) } + + // Optional quotient helper functions. Each one is rendered only + // when the Rust lowering pass recognized the corresponding + // expression shape in this generated verifier. They are pure Fr + // helpers and share the same FR_MODULUS as the surrounding + // numerator block. + // VK-specialized identity helper for Poseidon S-box terms. + // + // Rust source shape: + // circuits/src/hash/poseidon/poseidon_chip.rs::sbox + // full_round_gate / partial_round_gate + // circuits/src/hash/poseidon/round_skips.rs::RoundId + // + // The Rust verifier only sees this as an Expression tree from + // `vk.cs.gates`; the generator emits q_pow5 after recognizing five + // equal multiplicative factors. It is a codegen shortcut for x^5, + // not a separate verifier rule. + function q_pow5(x) -> z { + let q_r := FR_MODULUS + let x2 := mulmod(x, x, q_r) + z := mulmod(x, mulmod(x2, x2, q_r), q_r) + } // =============================================================== + // Batched identity numerator / linearization target. + // + // This block does not evaluate the quotient polynomial h(x), and + // the proof does not provide an h(x) scalar to trust. Instead it: + // + // 1. Reconstructs the y-batched constraint numerator nu_y(x) + // from the alleged polynomial evaluations read after the + // transcript sampled x. + // 2. Stores -nu_y(x) as the expected opening scalar for the + // linearized commitment. + // + // The commitment side is built in the next block from the quotient + // limb commitments as (1 - x^n) * Σ_i x_split^i * Q_i, plus any + // simple-selector commitments. The PCS check later binds that + // linearized commitment to this expected scalar at x. + // + // Rust source-of-truth: + // - verifier.rs reads quotient commitments, samples x, then + // reads/computes all evaluations used below. + // - mod.rs::partially_evaluate_identities returns identities in + // gate, permutation, lookup, trash order. + // - linearization/verifier.rs::compute_linearization_commitment + // reverse-folds those identities by powers of y, sends + // simple-selector identities to selector commitment scalars, + // and subtracts fully-evaluated identities into expected_eval. + // + // This template is shared by the monolithic and external quotient + // paths. In the external path, Halo2QuotientEvaluator first copies + // the verifier memory frame into the same generated addresses. + // + // Runtime inputs expected to exist before this block starts: + // - `r` is the BLS12-381 scalar-field modulus. + // - Y_MPTR holds the quotient batching challenge y. + // - X_MPTR, L_*_MPTR, INSTANCE_EVAL_MPTR, and + // REVERSED_EVALS_MPTR hold values parsed or derived by the + // main verifier after the transcript sampled x. + // - VK_MPTR holds the pinned VK payload; in compact mode that + // payload includes the quotient constant table and bytecode. + // + // Runtime outputs written by this block: + // - QUOTIENT_EVAL_MPTR receives the scalar expected opening for + // the linearized commitment, namely -nu_y(x). + // - SELECTOR_ACC_MPTR[0..num_simple_selectors) receives one + // linearization scalar per generated simple selector. + // + // Line-by-line reading conventions used below: + // + // * Every runtime value is one canonical Fr element stored in a + // 256-bit EVM memory word. The small integer operands decoded + // from q_program are never field values; they are pointers, + // constant-table slots, selector indexes, offsets, or counts. + // + // * `mload(ptr)` is the only way the VM turns a small pointer + // operand into a real 255-bit field element. The value loaded + // from memory is then combined with `addmod(..., r)` or + // `mulmod(..., r)`, so every arithmetic line is reduced modulo + // the BLS12-381 scalar-field order. + // + // * `q_top` is the cached top of the VM operand stack. When an + // opcode needs to push while `q_top` is already live, the old + // value is written to `q_sp` and `q_sp` is advanced by one + // word. Binary `ADD`/`MUL` move `q_sp` back by one word and + // combine that spilled value with `q_top`. + // + // * Identity boundaries are explicit. Expression opcodes leave + // one value in `q_top`; `FOLD_MAIN` or `FOLD_SELECTOR` consumes + // it and advances the global y-batch position. Native callback + // opcodes are only emitted at empty-stack boundaries and run + // generated Yul that performs the same fold side effects. + // + // * The generated Solidity source intentionally emits comments + // before opcode cases. Those comments are documentation only: + // they do not affect bytecode, but they make rendered verifier + // assembly readable without jumping back to Rust codegen. + // =============================================================== + { + // Compact quotient-program mode. + // + // The largest identity expressions are not all emitted as + // unrolled Yul. Instead, most arithmetic is encoded as a small + // q_program bytecode stored in the VK payload. This block + // interprets that program, while selected heavy identities may + // still be emitted as native callbacks for gas. + // + // Compact mode is a code-size trade: short bytecode operands + // name already-planned memory slots, and the interpreter turns + // those names into Fr arithmetic. The opcode stream is fully + // generated and pinned by the VK/runtime codehash; no proof + // calldata can alter control flow. + // Load the quotient batching challenge used by every fold. + let y := mload(Y_MPTR) + + // q_const_mptr points to Fr constants used by the VM. + // q_program_mptr points to the bytecode stream. + // Constants are stored as consecutive 32-byte Fr words. + let q_const_mptr := 0x3a60 + // Program bytes are also stored in the VK payload, packed into + // 32-byte words by PackedProgramCodec. + let q_program_mptr := 0x50a0 + // Running Horner accumulator for fully evaluated identities. + // After all identities, this is nu_y(x) for the `None` + // identity group. + // Initialize A = 0 before scanning the identity stream. + mstore(0xb280, 0) + // Simple selectors are grouped into separate linearization + // buckets. They start at zero for every proof. + // q_sel_zero_off walks selector bucket byte offsets. + for { let q_sel_zero_off := 0 } lt(q_sel_zero_off, 0x0140) { q_sel_zero_off := add(q_sel_zero_off, 0x20) } { + // B_s = 0 for each simple selector bucket. + mstore(add(SELECTOR_ACC_MPTR, q_sel_zero_off), 0) + } + // Codegen knows the selector identity positions. Precompute + // the y^k powers needed for selector gap and tail updates, + // avoiding a runtime y^-1 modexp and per-identity selector + // scale maintenance. + { + // q_y_power holds y^i at the current loop index. + let q_y_power := 1 + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0xb2c0, 1) + // Start at i=1 because y^0 = 1 is written above. + for { let q_y_power_i := 1 } lt(q_y_power_i, 49) { q_y_power_i := add(q_y_power_i, 1) } { + // Advance from y^(i-1) to y^i modulo Fr. + q_y_power := mulmod(q_y_power, y, r) + // Store y^i at selector_power_mptr + 32*i. + mstore(add(0xb2c0, shl(5, q_y_power_i)), q_y_power) + } + } + + // Direct inline prefix. These identities are generated as Yul + // before entering the VM. They use the same fold snippets as + // VM/native identities, so they occupy the same y-batch order. + { + let var0 := 0x1 + let f_3 := mload(0x9ac0) + let f_4 := mload(0x99c0) + let a_0 := mload(0x94a0) + let var1 := mulmod(f_4, a_0, r) + let var2 := addmod(f_3, var1, r) + let f_5 := mload(0x99e0) + let a_1 := mload(0x94c0) + let var3 := mulmod(f_5, a_1, r) + let var4 := addmod(var2, var3, r) + let f_6 := mload(0x9a00) + let a_2 := mload(0x94e0) + let var5 := mulmod(f_6, a_2, r) + let var6 := addmod(var4, var5, r) + let f_7 := mload(0x9a20) + let a_3 := mload(0x9500) + let var7 := mulmod(f_7, a_3, r) + let var8 := addmod(var6, var7, r) + let f_8 := mload(0x9a40) + let a_4 := mload(0x9520) + let var9 := mulmod(f_8, a_4, r) + let var10 := addmod(var8, var9, r) + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var11 := mulmod(f_0, a_0_next_1, r) + let var12 := addmod(var10, var11, r) + let f_1 := mload(0x9a80) + let var13 := mulmod(f_1, a_0, r) + let var14 := mulmod(var13, a_1, r) + let var15 := addmod(var12, var14, r) + let f_2 := mload(0x9aa0) + let var16 := mulmod(f_2, a_0, r) + let var17 := mulmod(var16, a_2, r) + let var18 := addmod(var15, var17, r) + let var19 := mulmod(var0, var18, r) + mstore(0xb8e0, var19) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_1 := mload(0x94c0) + let a_2 := mload(0x94e0) + let var1 := addmod(a_1, a_2, r) + let a_3 := mload(0x9500) + let var2 := addmod(0, sub(r, a_3), r) + let var3 := addmod(var1, var2, r) + let a_4 := mload(0x9520) + let var4 := addmod(0, sub(r, a_4), r) + let var5 := addmod(var3, var4, r) + let var6 := mulmod(var0, var5, r) + mstore(0xb8e0, var6) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let f_4 := mload(0x99c0) + let var1 := addmod(a_0, f_4, r) + let a_0_next_1 := mload(0x9540) + let var2 := addmod(0, sub(r, a_0_next_1), r) + let var3 := addmod(var1, var2, r) + let var4 := mulmod(var0, var3, r) + mstore(0xb8e0, var4) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + { + let var0 := 0x1 + let a_1 := mload(0x94c0) + let f_5 := mload(0x99e0) + let var1 := addmod(a_1, f_5, r) + let a_1_next_1 := mload(0x9560) + let var2 := addmod(0, sub(r, a_1_next_1), r) + let var3 := addmod(var1, var2, r) + let var4 := mulmod(var0, var3, r) + mstore(0xb8e0, var4) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + + // VM registers: + // q_pc current bytecode pointer + // q_end end of bytecode stream + // q_sp memory stack pointer for non-top stack values + // q_top cached top-of-stack value + // q_has_top whether q_top currently holds a stack value + // + // The cached top reduces memory traffic in the interpreter. + // q_sp's registered range must cover the interpreted operand + // stack plus any native callback scratch that reuses this base + // pointer. In particular, the native permutation callback + // writes a structured scratch table at program.stack_mptr. + // q_pc starts at the first encoded instruction. + let q_pc := q_program_mptr + // q_end is an exclusive byte pointer for the VM loop. + let q_end := add(q_program_mptr, 0x11cf) + // q_sp starts at the first free stack word. + let q_sp := 0xb8e0 + // q_top is meaningless until q_has_top is set. + let q_top := 0 + // q_has_top = 0 means the VM stack is empty. + let q_has_top := 0 + + // q_program opcode summary: + // 0x01/0x09 push const 0x02/0x05 push memory + // 0x03/0x04 push token ptr 0x06 add, 0x07 mul, 0x08 neg + // 0x0a fold main identity 0x0b fold selector identity + // 0x0c..0x11 add/mul const or memory into top + // 0x12..0x16 fused add-mul runs + // 0x17/0x18 reserved + // 0x19 native permutation 0x1b native heavy identity + // 0x1c LIN7 0x1d BILIN7_ROW + // 0x1e BILIN7_PAIRWISE 0x1f native lookup + // 0x20 POW5 0x21 MODARITH7 + // 0x22 AFFINE_SUM + // + // The default IVC verifier uses one physical encoding for the + // logical VM: compact byte-oriented opcodes with variable-width + // operands, dynamic runs, and limb-aware cases. + + // Byte-oriented encoding: opcodes are one byte followed by + // variable-width operand bytes. + for { } lt(q_pc, q_end) { } { + // The bytecode table is byte-addressed, but EVM memory + // loads whole words. `byte(0, mload(q_pc))` extracts the + // opcode at the current byte cursor; each case advances + // q_pc by exactly its operand width. + let q_op := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + + switch q_op + // VM 0x05 PUSH_MEM_U16 (bytes): next two bytes are a short memory pointer. + case 0x05 { + // Operand layout: u16 absolute memory pointer. The + // memory planner keeps the hot quotient frame below + // 64 KiB when this compact form is emitted. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + if q_has_top { + mstore(q_sp, q_top) + q_sp := add(q_sp, 0x20) + } + q_top := mload(q_ptr) + q_has_top := 1 + } + // VM 0x06 ADD: pop one spilled stack word and add it to q_top. + case 0x06 { + // The safety validator guarantees a spilled operand + // exists before ADD. q_top is the right operand. + q_sp := sub(q_sp, 0x20) + q_top := addmod(mload(q_sp), q_top, r) + } + // VM 0x08 NEG: replace q_top with its Fr negation. + case 0x08 { + // addmod(0, r - x, r) maps zero back to zero and every + // nonzero scalar to its canonical additive inverse. + q_top := addmod(0, sub(r, q_top), r) + } + // VM 0x0d MUL_CONST_U8: multiply q_top by a small constant-table slot. + case 0x0d { + // One-byte constant-index multiply, used by short + // affine chains after an initial PUSH. + let qconst := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + q_top := mulmod(q_top, mload(add(q_const_mptr, shl(5, qconst))), r) + } + // VM 0x10 ADD_MEM_U16: add a short memory load into q_top. + case 0x10 { + // Operand layout: u16 pointer. The pointed word is an + // already range-checked Fr scalar in verifier memory. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + q_top := addmod(q_top, mload(q_ptr), r) + } + // VM 0x11 MUL_MEM_U16: multiply q_top by a short memory load. + case 0x11 { + // In-place multiply by a planned memory word. + let q_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + q_top := mulmod(q_top, mload(q_ptr), r) + } + // Limb-aware opcodes are opt-in compact forms for + // structurally recognized non-SHA foreign-field shapes. + // Coefficients are indexes into q_const_mptr, which is + // generated from VK/program data, never from proof + // calldata. + // + // Rust source shape: + // proofs/src/plonk/mod.rs::partially_evaluate_identities + // circuits/src/field/foreign/util.rs::{sum_exprs,pair_wise_prod} + // circuits/src/field/foreign/params.rs::{base_powers,double_base_powers} + // + // "Foreign field" means the circuit represents elements + // modulo another modulus m as 7 limbs in base + // 2^LOG2_BASE. The verifier does not switch fields; it + // evaluates the lowered identity over BLS12-381 Fr, using + // Fr coefficients equal to base^i mod m or base^(i+j) mod m. + // VM 0x21 MODARITH7: byte-only fused affine 7-limb foreign-field/ECC identity. + case 0x21 { + // MODARITH7: + // maybe_cond * ( + // c + // + sum LIN7 blocks + // + sum BILIN7_ROW blocks + // + sum BILIN7_PAIRWISE blocks + // + sum coeff[k] * mload(ptr[k]) + // + sum coeff[k] * mload(lhs[k]) * mload(rhs[k]) + // ) + // It is a dispatch/operand-load optimization only; + // all coefficients still come from the generated + // quotient constant table. + // + // Flags: + // bit 0: multiply the final affine sum by a memory + // condition word. + // bit 1: seed q_acc from a constant-table word + // before reading the counted term blocks. + let q_flags := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + let q_cond_ptr := 0 + if and(q_flags, 0x01) { + // Optional condition pointer. When present, the + // whole identity is gated by mload(q_cond_ptr). + q_cond_ptr := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + } + + let q_acc := 0 + if and(q_flags, 0x02) { + // Optional constant seed for affine identities + // with a standalone constant term. + let qconst := byte(0, mload(q_pc)) + q_pc := add(q_pc, 1) + q_acc := mload(add(q_const_mptr, shl(5, qconst))) + } + + // Five one-byte counters describe the blocks that + // follow. Each block has a fixed-width internal layout, + // so q_pc can advance without per-term tags. + let q_counts_word := mload(q_pc) + let q_lin_count := byte(0, q_counts_word) + let q_row_count := byte(1, q_counts_word) + let q_pairwise_count := byte(2, q_counts_word) + let q_mem_count := byte(3, q_counts_word) + let q_product_count := byte(4, q_counts_word) + q_pc := add(q_pc, 5) + + if q_has_top { + mstore(q_sp, q_top) + q_sp := add(q_sp, 0x20) + } + + // LIN7 blocks: q_acc += sum_i c_i * limb_i. + for { let q_lin_block := 0 } lt(q_lin_block, q_lin_count) { q_lin_block := add(q_lin_block, 1) } { + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_ptr := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod(mload(add(q_const_mptr, shl(5, qconst))), mload(q_ptr), r), + r + ) + } + } + + // BILIN7_ROW blocks: q_acc += lhs * sum_i c_i * rhs_i. + for { let q_row_block := 0 } lt(q_row_block, q_row_count) { q_row_block := add(q_row_block, 1) } { + let q_lhs := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + let q_lhs_value := mload(q_lhs) + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_rhs := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod( + mulmod(q_lhs_value, mload(q_rhs), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + } + + // BILIN7_PAIRWISE blocks: q_acc += weighted 7-by-7 + // product convolution. + for { let q_pair_block := 0 } lt(q_pair_block, q_pairwise_count) { q_pair_block := add(q_pair_block, 1) } { + let q_pair_word := mload(q_pc) + let q_lhs_base := shr(240, q_pair_word) + let q_rhs_base := and(shr(224, q_pair_word), 0xffff) + q_pc := add(q_pc, 0x04) + let q_coeff_pc := q_pc + q_pc := add(q_pc, 13) + for { let q_i := 0 } lt(q_i, 7) { q_i := add(q_i, 1) } { + let q_lhs_value := mload(add(q_lhs_base, shl(5, q_i))) + for { let q_j := 0 } lt(q_j, 7) { q_j := add(q_j, 1) } { + let qconst := byte(0, mload(add(q_coeff_pc, add(q_i, q_j)))) + q_acc := addmod( + q_acc, + mulmod( + mulmod(q_lhs_value, mload(add(q_rhs_base, shl(5, q_j))), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + } + } + + // Extra linear memory terms outside the 7-limb shapes. + for { let q_mem_block := 0 } lt(q_mem_block, q_mem_count) { q_mem_block := add(q_mem_block, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_ptr := and(shr(232, q_word), 0xffff) + q_pc := add(q_pc, 3) + q_acc := addmod( + q_acc, + mulmod(mload(add(q_const_mptr, shl(5, qconst))), mload(q_ptr), r), + r + ) + } + + // Extra binary product terms outside the 7-limb shapes. + for { let q_product_block := 0 } lt(q_product_block, q_product_count) { q_product_block := add(q_product_block, 1) } { + let q_word := mload(q_pc) + let qconst := byte(0, q_word) + let q_lhs := and(shr(232, q_word), 0xffff) + let q_rhs := and(shr(216, q_word), 0xffff) + q_pc := add(q_pc, 5) + q_acc := addmod( + q_acc, + mulmod( + mulmod(mload(q_lhs), mload(q_rhs), r), + mload(add(q_const_mptr, shl(5, qconst))), + r + ), + r + ) + } + + if and(q_flags, 0x01) { + // Apply the optional gate condition last so every + // subterm shares the same selector/condition. + q_acc := mulmod(mload(q_cond_ptr), q_acc, r) + } + // MODARITH7 pushes its fused identity value. + q_top := q_acc + q_has_top := 1 + } + // Native permutation callback. It evaluates the + // permutation identities from permutation.rs at this exact + // VM position, preserving the Rust identity order while + // avoiding a large interpreted product loop. + // VM 0x19 NATIVE_PERMUTATION: marker for the generated permutation callback. + case 0x19 { + // Native callbacks are identity-boundary opcodes. They + // must not inherit any partially evaluated VM stack + // state from the previous expression. + q_top := 0 + q_has_top := 0 + // The generated loop below uses program.stack_mptr as + // its scratch-table base, not as a conventional VM + // stack. The Rust memory planner must reserve enough + // words for structured_permutation_scratch_words(meta) + // whenever this opcode can appear. + q_sp := 0xb8e0 + // The generated lines below call the same fold snippets + // used by interpreted expressions, so trace IDs and + // y-batch positions remain contiguous. + { + let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 + let q_perm_vals := 0xb8e0 + let q_perm_sigmas := 0xbb20 + let q_perm_z_cur := 0xbd60 + let q_perm_z_next := 0xbe20 + let q_perm_z_last := 0xbee0 + let q_perm_delta_base_ptr := 0xbf80 + let q_perm_num_cols := 18 + let q_perm_num_sets := 6 + let q_perm_chunk_len := 3 + let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 + mstore(add(q_perm_vals, 0x0), mload(0x99a0)) + { + for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { + let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) + let q_perm_val_load_src_off := q_perm_val_load_dst_off + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x94a0, q_perm_val_load_src_off))) + } + } + mstore(add(q_perm_vals, 0xc0), mload(0x9480)) + mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) + { + for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 9) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { + let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) + let q_perm_val_load_src_off := q_perm_val_load_dst_off + mstore(add(add(q_perm_vals, 0x100), q_perm_val_load_dst_off), mload(add(0x95a0, q_perm_val_load_src_off))) + } + } + mstore(add(q_perm_vals, 0x220), mload(0x9980)) + { + for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 18) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { + let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) + let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x9bc0, q_perm_sigma_load_src_off))) + } + } + { + for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 6) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { + let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) + let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x9e00, q_perm_z_cur_load_src_off))) + } + } + { + for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 6) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { + let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) + let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x9e20, q_perm_z_next_load_src_off))) + } + } + { + for { let q_perm_z_last_load_i := 0 } lt(q_perm_z_last_load_i, 5) { q_perm_z_last_load_i := add(q_perm_z_last_load_i, 1) } { + let q_perm_z_last_load_dst_off := shl(5, q_perm_z_last_load_i) + let q_perm_z_last_load_src_off := mul(q_perm_z_last_load_i, 0x60) + mstore(add(add(q_perm_z_last, 0x0), q_perm_z_last_load_dst_off), mload(add(0x9e40, q_perm_z_last_load_src_off))) + } + } + let q_perm_eval := 0 + q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + let q_perm_zn := mload(add(q_perm_z_cur, 0xa0)) + q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + for { let q_perm_i := 1 } lt(q_perm_i, 6) { q_perm_i := add(q_perm_i, 1) } { + let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) + let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) + q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + } + mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) + for { let q_perm_set := 0 } lt(q_perm_set, 6) { q_perm_set := add(q_perm_set, 1) } { + let q_perm_start := mul(q_perm_set, q_perm_chunk_len) + let q_perm_end := add(q_perm_start, q_perm_chunk_len) + if gt(q_perm_end, q_perm_num_cols) { q_perm_end := q_perm_num_cols } + let q_perm_left := mload(add(q_perm_z_next, shl(5, q_perm_set))) + let q_perm_right := mload(add(q_perm_z_cur, shl(5, q_perm_set))) + let q_perm_delta_pow := mload(q_perm_delta_base_ptr) + for { let q_perm_j := q_perm_start } lt(q_perm_j, q_perm_end) { q_perm_j := add(q_perm_j, 1) } { + let q_perm_off := shl(5, q_perm_j) + let q_perm_v := mload(add(q_perm_vals, q_perm_off)) + let q_perm_s := mload(add(q_perm_sigmas, q_perm_off)) + q_perm_left := mulmod(q_perm_left, addmod(addmod(q_perm_v, mulmod(mload(BETA_MPTR), q_perm_s, r), r), mload(GAMMA_MPTR), r), r) + q_perm_right := mulmod(q_perm_right, addmod(addmod(q_perm_v, q_perm_delta_pow, r), mload(GAMMA_MPTR), r), r) + q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) + } + q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) + mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) + } + } + } + // Native lookup callback. This whole-family opcode + // evaluates the LogUp boundary, helper-chunk, and + // accumulator identities at this VM position, preserving + // the Rust y-batch order while avoiding many interpreted + // product-loop opcodes. + // VM 0x1f NATIVE_LOOKUP: marker for the generated LogUp lookup callback. + case 0x1f { + // Reset VM stack state before entering structured + // lookup Yul. Lookup callbacks own their scratch + // layout and perform all needed folds internally. + q_top := 0 + q_has_top := 0 + // The generated loop below uses program.stack_mptr as + // f+beta/prefix/suffix scratch rather than as a + // conventional VM stack. The Rust memory planner must + // reserve structured_lookup_scratch_words(meta). + q_sp := 0xb8e0 + // Generated LogUp code follows the same y-batch order + // as the Rust identity stream. + { + let q_lookup_f := 0xb8e0 + let q_lookup_prefix := 0xb960 + let q_lookup_suffix := 0xb9e0 + let q_lookup_l0 := mload(L_0_MPTR) + let q_lookup_llast := mload(L_LAST_MPTR) + let q_lookup_lblind := mload(L_BLIND_MPTR) + let q_lookup_lsum := addmod(q_lookup_l0, q_lookup_llast, r) + let q_lookup_active := addmod(1, sub(r, addmod(q_lookup_llast, q_lookup_lblind, r)), r) + let q_lookup_beta := mload(BETA_MPTR) + let q_lookup_theta := mload(THETA_MPTR) + { + { + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa060), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let f_10 := mload(0x9ae0) + let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) + let var1 := mulmod(var0, q_lookup_theta, r) + for { let q_lookup_shared_i := 0 } lt(q_lookup_shared_i, 4) { q_lookup_shared_i := add(q_lookup_shared_i, 1) } { + let q_lookup_shared_off := shl(5, q_lookup_shared_i) + let q_lookup_shared_tail := mload(add(0x94c0, q_lookup_shared_off)) + let q_lookup_shared_compressed := addmod(var1, q_lookup_shared_tail, r) + mstore(add(q_lookup_f, q_lookup_shared_off), addmod(q_lookup_shared_compressed, q_lookup_beta, r)) + } + let q_lookup_product := 1 + for { let q_lookup_prod_i := 0 } lt(q_lookup_prod_i, 4) { q_lookup_prod_i := add(q_lookup_prod_i, 1) } { + q_lookup_product := mulmod(q_lookup_product, mload(add(q_lookup_f, shl(5, q_lookup_prod_i))), r) + } + mstore(q_lookup_prefix, 1) + for { let q_lookup_pref_i := 1 } lt(q_lookup_pref_i, 4) { q_lookup_pref_i := add(q_lookup_pref_i, 1) } { + let q_lookup_pref_prev := sub(q_lookup_pref_i, 1) + mstore(add(q_lookup_prefix, shl(5, q_lookup_pref_i)), mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_pref_prev))), mload(add(q_lookup_f, shl(5, q_lookup_pref_prev))), r)) + } + mstore(add(q_lookup_suffix, 0x60), 1) + for { let q_lookup_suf_i := sub(4, 1) } gt(q_lookup_suf_i, 0) { q_lookup_suf_i := sub(q_lookup_suf_i, 1) } { + let q_lookup_suf_prev := sub(q_lookup_suf_i, 1) + mstore(add(q_lookup_suffix, shl(5, q_lookup_suf_prev)), mulmod(mload(add(q_lookup_suffix, shl(5, q_lookup_suf_i))), mload(add(q_lookup_f, shl(5, q_lookup_suf_i))), r)) + } + let q_lookup_sum := 0 + for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 4) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { + q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) + } + let q_lookup_eval := addmod(mulmod(mload(0xa040), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let q_lookup_sum_h := mload(0xa040) + let f_17 := mload(0x9b60) + let f_11 := mload(0x9b00) + let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) + let f_12 := mload(0x9b20) + let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) + let q_lookup_s_sum_h := mulmod(f_17, q_lookup_sum_h, r) + let q_lookup_diff := addmod(mload(0xa080), sub(r, addmod(mload(0xa060), q_lookup_s_sum_h, r)), r) + let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa020), r) + let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + } + { + { + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa0e0), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let a_14 := mload(0x9980) + let var0 := addmod(mulmod(0, q_lookup_theta, r), a_14, r) + let a_0 := mload(0x94a0) + let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_0, r) + let a_1 := mload(0x94c0) + let var2 := addmod(mulmod(var1, q_lookup_theta, r), a_1, r) + let a_2 := mload(0x94e0) + let var3 := addmod(mulmod(var2, q_lookup_theta, r), a_2, r) + let a_3 := mload(0x9500) + let var4 := addmod(mulmod(var3, q_lookup_theta, r), a_3, r) + let a_4 := mload(0x9520) + let var5 := addmod(mulmod(var4, q_lookup_theta, r), a_4, r) + let a_5 := mload(0x95a0) + let var6 := addmod(mulmod(var5, q_lookup_theta, r), a_5, r) + let a_6 := mload(0x95c0) + let var7 := addmod(mulmod(var6, q_lookup_theta, r), a_6, r) + let a_7 := mload(0x95e0) + let var8 := addmod(mulmod(var7, q_lookup_theta, r), a_7, r) + let a_8 := mload(0x9600) + let var9 := addmod(mulmod(var8, q_lookup_theta, r), a_8, r) + let a_9 := mload(0x9620) + let var10 := addmod(mulmod(var9, q_lookup_theta, r), a_9, r) + let a_10 := mload(0x9640) + let var11 := addmod(mulmod(var10, q_lookup_theta, r), a_10, r) + let a_11 := mload(0x9660) + let var12 := addmod(mulmod(var11, q_lookup_theta, r), a_11, r) + let a_12 := mload(0x9680) + let var13 := addmod(mulmod(var12, q_lookup_theta, r), a_12, r) + let a_13 := mload(0x96a0) + let var14 := addmod(mulmod(var13, q_lookup_theta, r), a_13, r) + let f_13 := mload(0x9b40) + let var15 := addmod(mulmod(var14, q_lookup_theta, r), f_13, r) + let q_lookup_eval := addmod(mulmod(mload(0xa0c0), addmod(var15, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + { + let q_lookup_sum_h := mload(0xa0c0) + let var0 := 0x1 + let f_24 := mload(0x9b80) + let var1 := addmod(0, sub(r, f_24), r) + let var2 := addmod(var0, var1, r) + let a_14 := mload(0x9980) + let var3 := mulmod(var2, a_14, r) + let var4 := addmod(mulmod(0, q_lookup_theta, r), var3, r) + let a_0 := mload(0x94a0) + let var5 := mulmod(var2, a_0, r) + let var6 := addmod(mulmod(var4, q_lookup_theta, r), var5, r) + let a_1 := mload(0x94c0) + let var7 := mulmod(var2, a_1, r) + let var8 := addmod(mulmod(var6, q_lookup_theta, r), var7, r) + let a_2 := mload(0x94e0) + let var9 := mulmod(var2, a_2, r) + let var10 := addmod(mulmod(var8, q_lookup_theta, r), var9, r) + let a_3 := mload(0x9500) + let var11 := mulmod(var2, a_3, r) + let var12 := addmod(mulmod(var10, q_lookup_theta, r), var11, r) + let a_4 := mload(0x9520) + let var13 := mulmod(var2, a_4, r) + let var14 := addmod(mulmod(var12, q_lookup_theta, r), var13, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var2, a_5, r) + let var16 := addmod(mulmod(var14, q_lookup_theta, r), var15, r) + let a_6 := mload(0x95c0) + let var17 := mulmod(var2, a_6, r) + let var18 := addmod(mulmod(var16, q_lookup_theta, r), var17, r) + let a_7 := mload(0x95e0) + let var19 := mulmod(var2, a_7, r) + let var20 := addmod(mulmod(var18, q_lookup_theta, r), var19, r) + let a_8 := mload(0x9600) + let var21 := mulmod(var2, a_8, r) + let var22 := addmod(mulmod(var20, q_lookup_theta, r), var21, r) + let a_9 := mload(0x9620) + let var23 := mulmod(var2, a_9, r) + let var24 := addmod(mulmod(var22, q_lookup_theta, r), var23, r) + let a_10 := mload(0x9640) + let var25 := mulmod(var2, a_10, r) + let var26 := addmod(mulmod(var24, q_lookup_theta, r), var25, r) + let a_11 := mload(0x9660) + let var27 := mulmod(var2, a_11, r) + let var28 := addmod(mulmod(var26, q_lookup_theta, r), var27, r) + let a_12 := mload(0x9680) + let var29 := mulmod(var2, a_12, r) + let var30 := addmod(mulmod(var28, q_lookup_theta, r), var29, r) + let a_13 := mload(0x96a0) + let var31 := mulmod(var2, a_13, r) + let var32 := addmod(mulmod(var30, q_lookup_theta, r), var31, r) + let f_13 := mload(0x9b40) + let var33 := mulmod(var2, f_13, r) + let var34 := addmod(mulmod(var32, q_lookup_theta, r), var33, r) + let q_lookup_s_sum_h := mulmod(var0, q_lookup_sum_h, r) + let q_lookup_diff := addmod(mload(0xa100), sub(r, addmod(mload(0xa0e0), q_lookup_s_sum_h, r)), r) + let q_lookup_t_beta := addmod(var34, q_lookup_beta, r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa0a0), r) + let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) + } + } + } + } + // Native callbacks are generated only for the heaviest + // recognized Midfall gate identities. All other gate and + // non-native identity arithmetic remains in + // the compact q_program VM above, preserving the Rust + // `partially_evaluate_identities` order. + // VM 0x1b NATIVE_IDENTITY: marker for generated heavy-gate callbacks. + case 0x1b { + // Operand layout: u16 native callback index. The + // manifest validates that callback indexes appear in + // generated order and target existing switch cases. + let q_native_idx := shr(240, mload(q_pc)) + q_pc := add(q_pc, 2) + // Heavy identities are whole expressions, so clear the + // interpreter stack before dispatching. + q_top := 0 + q_has_top := 0 + q_sp := 0xb8e0 + // Native identity sub-cases are generated from selected heavy gate identities. + switch q_native_idx + case 0 { + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let a_0_next_1 := mload(0x9540) + let var1 := mulmod(a_0, a_0_next_1, r) + let var2 := 0x100000000000000 + let a_1_next_1 := mload(0x9560) + let var3 := mulmod(a_0, a_1_next_1, r) + let var4 := mulmod(var2, var3, r) + let var5 := addmod(var1, var4, r) + let var6 := 0x10000000000000000000000000000 + let a_2_next_1 := mload(0x9580) + let var7 := mulmod(a_0, a_2_next_1, r) + let var8 := mulmod(var6, var7, r) + let var9 := addmod(var5, var8, r) + let a_1 := mload(0x94c0) + let var10 := mulmod(a_1, a_0_next_1, r) + let var11 := mulmod(var2, var10, r) + let var12 := addmod(var9, var11, r) + let var13 := mulmod(a_1, a_1_next_1, r) + let var14 := mulmod(var6, var13, r) + let var15 := addmod(var12, var14, r) + let var16 := 0x3212e00cde6d2002b119d800000347fcb8 + let a_6_next_1 := mload(0x9720) + let var17 := mulmod(a_1, a_6_next_1, r) + let var18 := mulmod(var16, var17, r) + let var19 := addmod(var15, var18, r) + let a_2 := mload(0x94e0) + let var20 := mulmod(a_2, a_0_next_1, r) + let var21 := mulmod(var6, var20, r) + let var22 := addmod(var19, var21, r) + let a_5_next_1 := mload(0x9700) + let var23 := mulmod(a_2, a_5_next_1, r) + let var24 := mulmod(var16, var23, r) + let var25 := addmod(var22, var24, r) + let var26 := 0x297784894e27525bc342b7fde37dba9366 + let var27 := mulmod(a_2, a_6_next_1, r) + let var28 := mulmod(var26, var27, r) + let var29 := addmod(var25, var28, r) + let a_3 := mload(0x9500) + let a_4_next_1 := mload(0x96e0) + let var30 := mulmod(a_3, a_4_next_1, r) + let var31 := mulmod(var16, var30, r) + let var32 := addmod(var29, var31, r) + let var33 := mulmod(a_3, a_5_next_1, r) + let var34 := mulmod(var26, var33, r) + let var35 := addmod(var32, var34, r) + let var36 := 0x340f2ebe380a0f5eff4360543988a61dc2 + let var37 := mulmod(a_3, a_6_next_1, r) + let var38 := mulmod(var36, var37, r) + let var39 := addmod(var35, var38, r) + let a_4 := mload(0x9520) + let a_3_next_1 := mload(0x96c0) + let var40 := mulmod(a_4, a_3_next_1, r) + let var41 := mulmod(var16, var40, r) + let var42 := addmod(var39, var41, r) + let var43 := mulmod(a_4, a_4_next_1, r) + let var44 := mulmod(var26, var43, r) + let var45 := addmod(var42, var44, r) + let var46 := mulmod(a_4, a_5_next_1, r) + let var47 := mulmod(var36, var46, r) + let var48 := addmod(var45, var47, r) + let var49 := 0x13af65741744bd7bb2c6872df2b800320 + let var50 := mulmod(a_4, a_6_next_1, r) + let var51 := mulmod(var49, var50, r) + let var52 := addmod(var48, var51, r) + let a_5 := mload(0x95a0) + let var53 := mulmod(a_5, a_2_next_1, r) + let var54 := mulmod(var16, var53, r) + let var55 := addmod(var52, var54, r) + let var56 := mulmod(a_5, a_3_next_1, r) + let var57 := mulmod(var26, var56, r) + let var58 := addmod(var55, var57, r) + let var59 := mulmod(a_5, a_4_next_1, r) + let var60 := mulmod(var36, var59, r) + let var61 := addmod(var58, var60, r) + let var62 := mulmod(a_5, a_5_next_1, r) + let var63 := mulmod(var49, var62, r) + let var64 := addmod(var61, var63, r) + let var65 := 0x2cb9b546d20373eaf85e8f53db883cb548 + let var66 := mulmod(a_5, a_6_next_1, r) + let var67 := mulmod(var65, var66, r) + let var68 := addmod(var64, var67, r) + let a_6 := mload(0x95c0) + let var69 := mulmod(a_6, a_1_next_1, r) + let var70 := mulmod(var16, var69, r) + let var71 := addmod(var68, var70, r) + let var72 := mulmod(a_6, a_2_next_1, r) + let var73 := mulmod(var26, var72, r) + let var74 := addmod(var71, var73, r) + let var75 := mulmod(a_6, a_3_next_1, r) + let var76 := mulmod(var36, var75, r) + let var77 := addmod(var74, var76, r) + let var78 := mulmod(a_6, a_4_next_1, r) + let var79 := mulmod(var49, var78, r) + let var80 := addmod(var77, var79, r) + let var81 := mulmod(a_6, a_5_next_1, r) + let var82 := mulmod(var65, var81, r) + let var83 := addmod(var80, var82, r) + let var84 := 0xc8557e86f90d0d89eed6eb5349a0f8820 + let var85 := mulmod(a_6, a_6_next_1, r) + let var86 := mulmod(var84, var85, r) + let var87 := addmod(var83, var86, r) + let var88 := mulmod(var2, a_1, r) + let var89 := addmod(a_0, var88, r) + let var90 := mulmod(var6, a_2, r) + let var91 := addmod(var89, var90, r) + let var92 := addmod(var87, var91, r) + let var93 := mulmod(var2, a_1_next_1, r) + let var94 := addmod(a_0_next_1, var93, r) + let var95 := mulmod(var6, a_2_next_1, r) + let var96 := addmod(var94, var95, r) + let var97 := addmod(var92, var96, r) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) + let var98 := mulmod(var2, a_8, r) + let var99 := addmod(a_7, var98, r) + let a_9 := mload(0x9620) + let var100 := mulmod(var6, a_9, r) + let var101 := addmod(var99, var100, r) + let var102 := addmod(0, sub(r, var101), r) + let var103 := addmod(var97, var102, r) + let a_7_next_1 := mload(0x9740) + let var104 := 0x241eabfffeb153ffffb9feffffffffaaab + let var105 := mulmod(a_7_next_1, var104, r) + let var106 := addmod(0, sub(r, var105), r) + let var107 := addmod(var103, var106, r) + let var108 := addmod(0, sub(r, var16), r) + let var109 := addmod(var107, var108, r) + let a_8_next_1 := mload(0x9760) + let var110 := 0x73eda753299d7d483339d80809a1d80553b9202d7ffe85d4800008bb20000001 + let var111 := addmod(a_8_next_1, var110, r) + let var112 := 0x4000000000000000000000000000000000 + let var113 := mulmod(var111, var112, r) + let var114 := addmod(0, sub(r, var113), r) + let var115 := addmod(var109, var114, r) + let var116 := mulmod(var0, var115, r) + mstore(0xb8e0, var116) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 1 { + { + let var0 := 0x1 + let a_0 := mload(0x94a0) + let var1 := 0x10000000000000000000000000000 + let var2 := addmod(a_0, var1, r) + let var3 := 0x100000000000000 + let a_1 := mload(0x94c0) + let var4 := addmod(a_1, var1, r) + let var5 := mulmod(var3, var4, r) + let var6 := addmod(var2, var5, r) + let a_2 := mload(0x94e0) + let var7 := addmod(a_2, var1, r) + let var8 := mulmod(var1, var7, r) + let var9 := addmod(var6, var8, r) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) + let var10 := mulmod(var3, a_8, r) + let var11 := addmod(a_7, var10, r) + let a_9 := mload(0x9620) + let var12 := mulmod(var1, a_9, r) + let var13 := addmod(var11, var12, r) + let var14 := addmod(0, sub(r, var13), r) + let var15 := addmod(var9, var14, r) + let var16 := addmod(0, sub(r, var1), r) + let var17 := addmod(var15, var16, r) + let a_7_next_1 := mload(0x9740) + let var18 := 0x241eabfffeb153ffffb9feffffffffaaab + let var19 := mulmod(a_7_next_1, var18, r) + let var20 := addmod(0, sub(r, var19), r) + let var21 := addmod(var17, var20, r) + let var22 := 0xd9d44a30b019261257667fde3844a8cd6 + let var23 := addmod(0, sub(r, var22), r) + let var24 := addmod(var21, var23, r) + let a_8_next_1 := mload(0x9760) + let var25 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5b6e855000003ab00002 + let var26 := addmod(a_8_next_1, var25, r) + let var27 := 0x4000000000000000000000000000000000 + let var28 := mulmod(var26, var27, r) + let var29 := addmod(0, sub(r, var28), r) + let var30 := addmod(var24, var29, r) + let var31 := mulmod(var0, var30, r) + mstore(0xb8e0, var31) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x80) + let q_selector_acc := mload(q_selector_ptr) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 2 { + { + let var0 := 0x1 + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var1 := addmod(0, sub(r, a_0_next_1), r) + let var2 := addmod(f_0, var1, r) + let var3 := 0x1b8114c381b922fd5d6d241210e2d8a68ad5744053ba9e776118de4107b51ace + let a_0 := mload(0x94a0) + let var4 := mulmod(a_0, a_0, r) + let a_3 := mload(0x9500) + let var5 := mulmod(var4, a_3, r) + let var6 := mulmod(var3, var5, r) + let var7 := addmod(var2, var6, r) + let var8 := 0x3df32e4cc4cb2ed20e5d21899cf5331775990ccaec4c09b4e3717213fcc0d763 + let a_1 := mload(0x94c0) + let var9 := mulmod(a_1, a_1, r) + let a_4 := mload(0x9520) + let var10 := mulmod(var9, a_4, r) + let var11 := mulmod(var8, var10, r) + let var12 := addmod(var7, var11, r) + let var13 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 + let a_2 := mload(0x94e0) + let var14 := mulmod(a_2, a_2, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var14, a_5, r) + let var16 := mulmod(var13, var15, r) + let var17 := addmod(var12, var16, r) + let var18 := mulmod(var0, var17, r) + mstore(0xb8e0, var18) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x120) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + case 3 { + { + let var0 := 0x1 + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) + let var1 := addmod(0, sub(r, a_1_next_1), r) + let var2 := addmod(f_1, var1, r) + let var3 := 0x404d21073985d14e432a4ad76d3fae06ca74314b950fe7b1d7f501cd31a8b374 + let a_0 := mload(0x94a0) + let var4 := mulmod(a_0, a_0, r) + let a_3 := mload(0x9500) + let var5 := mulmod(var4, a_3, r) + let var6 := mulmod(var3, var5, r) + let var7 := addmod(var2, var6, r) + let var8 := 0xb2cc8704264c6bd81bc620e9e524d4b73e9b2317679422ff7fa1603955649f1 + let a_1 := mload(0x94c0) + let var9 := mulmod(a_1, a_1, r) + let a_4 := mload(0x9520) + let var10 := mulmod(var9, a_4, r) + let var11 := mulmod(var8, var10, r) + let var12 := addmod(var7, var11, r) + let var13 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f + let a_2 := mload(0x94e0) + let var14 := mulmod(a_2, a_2, r) + let a_5 := mload(0x95a0) + let var15 := mulmod(var14, a_5, r) + let var16 := mulmod(var13, var15, r) + let var17 := addmod(var12, var16, r) + let var18 := mulmod(var0, var17, r) + mstore(0xb8e0, var18) + } + mstore(0xb280, mulmod(mload(0xb280), y, r)) + { + let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x120) + let q_selector_acc := mload(q_selector_ptr) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) + } + } + default { revert(0, 0) } + } + // VM 0x0b FOLD_SELECTOR: consume q_top into one simple-selector bucket. + case 0x0b { + // Operand layout packed into three bytes: + // high byte: selector bucket index; + // low u16 : y-power gap since this selector's + // previous contribution. + let q_selector_payload := shr(232, mload(q_pc)) + q_pc := add(q_pc, 3) + let q_sel_idx := shr(16, q_selector_payload) + let q_sel_gap := and(q_selector_payload, 0xffff) + let q_eval := q_top + q_has_top := 0 + // Simple-selector identity: keep the same y-batch + // position as main identities, then advance only this + // selector bucket by its codegen-known gap. + // + // The global fully-evaluated accumulator is still + // multiplied by y so later main identities land at the + // same y powers as Rust's reverse fold. + mstore(0xb280, mulmod(mload(0xb280), y, r)) + let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) + let q_sel_acc := mload(q_target_ptr) + if q_sel_gap { + // Selector buckets are sparse in the global + // identity stream. Precomputed y^gap advances only + // this selector's local accumulator. + q_sel_acc := mulmod(q_sel_acc, mload(add(0xb2c0, shl(5, q_sel_gap))), r) + } + mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) + } + // Invalid generated bytecode should fail closed. 0x1a intentionally lands here. + default { + revert(0, 0) + } + } + // The VK-pinned bytecode must end exactly at q_end and every + // identity must have been consumed by a fold/native callback. + // This catches malformed generator output whose final opcode + // over-reads operands or leaves a partial expression live. + if iszero(eq(q_pc, q_end)) { revert(0, 0) } + if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0xb8e0)) { revert(0, 0) } + + // Structured post-VM suffix. The current default uses this for + // regular trash constraints: it is smaller than fully unrolled + // Yul and cheaper than interpreting every trash operation. + // + // These generated blocks run after q_pc reaches q_end, but + // they still participate in the same identity order and write + // into the same numerator / selector accumulators. + { + let q_trash_tau := mload(TRASH_CHALLENGE_MPTR) + { + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) + let var0 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 + let var1 := mulmod(a_0_next_1, var0, r) + let var2 := addmod(f_0, var1, r) + let var3 := 0x590ba402032e82eb1f660ef09796c5686345a5054ed96dae8e2d233633788771 + let a_0 := mload(0x94a0) + let var4 := mulmod(var3, a_0, r) + let var5 := addmod(var2, var4, r) + let var6 := 0x52f789e4afc3801f7411102ee2f47cc5954a744e71cac98e75ea962a55a0a76f + let a_1 := mload(0x94c0) + let var7 := mulmod(var6, a_1, r) + let var8 := addmod(var5, var7, r) + let var9 := 0x3509dd2fe3aac0080783557fec090fb1cb4b2b0901253c55282024331d1fe1a8 + let a_2 := mload(0x94e0) + let var10 := q_pow5(a_2) + let var11 := mulmod(var9, var10, r) + let var12 := addmod(var8, var11, r) + let var13 := 0x333f8046ece5579cbd6872449c57f2703dfc8864cfadc06d587ff104a0d0c1f2 + let a_3 := mload(0x9500) + let var14 := q_pow5(a_3) + let var15 := mulmod(var13, var14, r) + let var16 := addmod(var12, var15, r) + let var17 := 0x412c98232b6ab8a47aa76ee814ef7ec6261987c9802f2cfc490e007951a60ca5 + let a_4 := mload(0x9520) + let var18 := q_pow5(a_4) + let var19 := mulmod(var17, var18, r) + let var20 := addmod(var16, var19, r) + let var21 := 0x53fded36d490ba6b05a5d10fd99ffe5456baec6a6a8753199d5ebdc33c99790e + let a_5 := mload(0x95a0) + let var22 := q_pow5(a_5) + let var23 := mulmod(var21, var22, r) + let var24 := addmod(var20, var23, r) + let var25 := 0x6ccb1c7d87f3c12a2bde4e68ac7f1e8b03481ba15d7f88f9a7f9b8310dd6d34 + let a_6 := mload(0x95c0) + let var26 := q_pow5(a_6) + let var27 := mulmod(var25, var26, r) + let var28 := addmod(var24, var27, r) + let var29 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 + let a_7 := mload(0x95e0) + let var30 := q_pow5(a_7) + let var31 := mulmod(var29, var30, r) + let var32 := addmod(var28, var31, r) + let var33 := addmod(mulmod(0, q_trash_tau, r), var32, r) + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) + let var34 := mulmod(a_1_next_1, var0, r) + let var35 := addmod(f_1, var34, r) + let var36 := 0x5b1fc262a28cbb8bf75d9b1a6edaa74591ec24cd9a209512213cec3a3c0f1a5d + let var37 := mulmod(var36, a_0, r) + let var38 := addmod(var35, var37, r) + let var39 := 0x4d0ea7f9c3fda06d9535b0fdafd8338bd47c2200b284fa71a325ff41ac358028 + let var40 := mulmod(var39, a_1, r) + let var41 := addmod(var38, var40, r) + let var42 := 0x26cc223e16f47c20e17cc6069605fa5a8af05ea4f6eb36029a641d23b818eb10 + let var43 := mulmod(var42, var10, r) + let var44 := addmod(var41, var43, r) + let var45 := 0x31e823a45e567484c1544e310c0fa5cd66547a8f0dde659ac61698c30e838d25 + let var46 := mulmod(var45, var14, r) + let var47 := addmod(var44, var46, r) + let var48 := 0x275a20361ea91992193920270d3e2d1f6361880ac0a439c64bef815d4469ba85 + let var49 := mulmod(var48, var18, r) + let var50 := addmod(var47, var49, r) + let var51 := 0x5f3a15bab4ce4097b1edc3a25002694b92395ce355a8a12fe557459d9633f701 + let var52 := mulmod(var51, var22, r) + let var53 := addmod(var50, var52, r) + let var54 := 0x301cf56f9b4577112cc4241cddf6484aaadedbf1bbd0f2351adf2e41c2fb2ecd + let var55 := mulmod(var54, var26, r) + let var56 := addmod(var53, var55, r) + let var57 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f + let var58 := mulmod(var57, var30, r) + let var59 := addmod(var56, var58, r) + let var60 := addmod(mulmod(var33, q_trash_tau, r), var59, r) + let f_2 := mload(0x9aa0) + let var61 := mulmod(a_3, var0, r) + let var62 := addmod(f_2, var61, r) + let var63 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 + let var64 := mulmod(var63, a_0, r) + let var65 := addmod(var62, var64, r) + let var66 := 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a + let var67 := mulmod(var66, a_1, r) + let var68 := addmod(var65, var67, r) + let var69 := 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db + let var70 := mulmod(var69, var10, r) + let var71 := addmod(var68, var70, r) + let var72 := addmod(mulmod(var60, q_trash_tau, r), var71, r) + let f_3 := mload(0x9ac0) + let var73 := mulmod(a_4, var0, r) + let var74 := addmod(f_3, var73, r) + let var75 := 0x222e83e70453dfee19b402e9fa8dfe2c4987b034d0be3ceb478b3022e97934c1 + let var76 := mulmod(var75, a_0, r) + let var77 := addmod(var74, var76, r) + let var78 := 0x26c2cc87f95726b28f33ca03409a460ec987cfe12adae32769e3565865d07191 + let var79 := mulmod(var78, a_1, r) + let var80 := addmod(var77, var79, r) + let var81 := 0x4382d0938a760120dd6cef8f3b90a0c38abae475e3d21e39365472b76d780272 + let var82 := mulmod(var81, var10, r) + let var83 := addmod(var80, var82, r) + let var84 := mulmod(var69, var14, r) + let var85 := addmod(var83, var84, r) + let var86 := addmod(mulmod(var72, q_trash_tau, r), var85, r) + let f_4 := mload(0x99c0) + let var87 := mulmod(a_5, var0, r) + let var88 := addmod(f_4, var87, r) + let var89 := 0x726df1506749848155630b86ae25a82b281ecd050fe3a52d85a181fa87202e4b + let var90 := mulmod(var89, a_0, r) + let var91 := addmod(var88, var90, r) + let var92 := 0x24822e1af9aa2887c912c87eb0f20bd332330e7e55cd784de67cb407a9f05520 + let var93 := mulmod(var92, a_1, r) + let var94 := addmod(var91, var93, r) + let var95 := 0x4e5280109d8f96b8bfb543a6b1af25fb56a9db616af85a90eedc558e3eb1ea29 + let var96 := mulmod(var95, var10, r) + let var97 := addmod(var94, var96, r) + let var98 := mulmod(var81, var14, r) + let var99 := addmod(var97, var98, r) + let var100 := mulmod(var69, var18, r) + let var101 := addmod(var99, var100, r) + let var102 := addmod(mulmod(var86, q_trash_tau, r), var101, r) + let f_5 := mload(0x99e0) + let var103 := mulmod(a_6, var0, r) + let var104 := addmod(f_5, var103, r) + let var105 := 0x2f5908b169c6cf1bd26dcf0f9e5105481f5164f3ece0582bf3098312167751a7 + let var106 := mulmod(var105, a_0, r) + let var107 := addmod(var104, var106, r) + let var108 := 0x23a6684b942d726a22e4d5b8d8ff83aeaa773f62600184efe5d033d7c7c6e827 + let var109 := mulmod(var108, a_1, r) + let var110 := addmod(var107, var109, r) + let var111 := 0x1981b4b33d6a9dab957b351d981d3323e65da39493af5bc01f7e8ffe17f98d4e + let var112 := mulmod(var111, var10, r) + let var113 := addmod(var110, var112, r) + let var114 := mulmod(var95, var14, r) + let var115 := addmod(var113, var114, r) + let var116 := mulmod(var81, var18, r) + let var117 := addmod(var115, var116, r) + let var118 := mulmod(var69, var22, r) + let var119 := addmod(var117, var118, r) + let var120 := addmod(mulmod(var102, q_trash_tau, r), var119, r) + let f_6 := mload(0x9a00) + let var121 := mulmod(a_7, var0, r) + let var122 := addmod(f_6, var121, r) + let var123 := 0x6d05a41959f539a7fc9ec0972ea1e3dbb6fc67dd51daf3414f7fbbb091c7274a + let var124 := mulmod(var123, a_0, r) + let var125 := addmod(var122, var124, r) + let var126 := 0x27e7119226c42a6d19c1541904b99ae40685511ed2e078964b74594d38340849 + let var127 := mulmod(var126, a_1, r) + let var128 := addmod(var125, var127, r) + let var129 := 0xd94c46a8456352aa44d7a885ab59e3a36664e6fb25e826f8a4cd79822f0533 + let var130 := mulmod(var129, var10, r) + let var131 := addmod(var128, var130, r) + let var132 := mulmod(var111, var14, r) + let var133 := addmod(var131, var132, r) + let var134 := mulmod(var95, var18, r) + let var135 := addmod(var133, var134, r) + let var136 := mulmod(var81, var22, r) + let var137 := addmod(var135, var136, r) + let var138 := mulmod(var69, var26, r) + let var139 := addmod(var137, var138, r) + let var140 := addmod(mulmod(var120, q_trash_tau, r), var139, r) + let f_7 := mload(0x9a20) + let a_2_next_1 := mload(0x9580) + let var141 := mulmod(a_2_next_1, var0, r) + let var142 := addmod(f_7, var141, r) + let var143 := 0x70d8f2a733a64d650faccc9b1c2a766a9544bb3ff1a11ee73cb43947ef386633 + let var144 := mulmod(var143, a_0, r) + let var145 := addmod(var142, var144, r) + let var146 := 0x40fa389feb2522bb934881ac9ed749aee2296502af592418c6b5675c0f560261 + let var147 := mulmod(var146, a_1, r) + let var148 := addmod(var145, var147, r) + let var149 := 0x1f61345b652161410c5e29f51e301ae56342af824bc110649393d2b911c50d3e + let var150 := mulmod(var149, var10, r) + let var151 := addmod(var148, var150, r) + let var152 := mulmod(var129, var14, r) + let var153 := addmod(var151, var152, r) + let var154 := mulmod(var111, var18, r) + let var155 := addmod(var153, var154, r) + let var156 := mulmod(var95, var22, r) + let var157 := addmod(var155, var156, r) + let var158 := mulmod(var81, var26, r) + let var159 := addmod(var157, var158, r) + let var160 := mulmod(var69, var30, r) + let var161 := addmod(var159, var160, r) + let var162 := addmod(mulmod(var140, q_trash_tau, r), var161, r) + let f_26 := mload(0x9ba0) + let q_trash_one_minus_selector := addmod(1, sub(r, f_26), r) + let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0xa120), r) + let q_trash_eval := addmod(var162, sub(r, q_trash_scaled), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_trash_eval, r)) + } + } + // Finish selector buckets by applying the codegen-known tail + // from each selector's last identity to the end of the global + // y-batch. + // + // After this step, every selector bucket is aligned with the + // final global y position and can be multiplied by its fixed + // selector commitment in the linearized MSM. + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0600)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x05e0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0580)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0520)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x80) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x04c0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xa0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0460)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xc0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0400)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xe0) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x03a0)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0100) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0340)), r)) + } + { + let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0120) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0280)), r)) + } + + // Fully evaluated identities are the constant-polynomial side + // of the linearization query. Rust subtracts that grouped + // scalar into expected_eval, so Solidity stores -nu_y(x). + let linearization_expected_eval := addmod(0, sub(r, mload(0xb280)), r) + mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) + pop(y) + } + + // =============================================================== + // Prepare linearization scalars for the final PCS MSM. + // + // The linearized commitment is + // (1 - x^n) * Σ_i x_split^i * Q_i + // + Σ_j sel_acc_j * S_j_com, + // where x_split = x^(n-1). Instead of materializing that point + // with a standalone G1MSM here, PCS block 5 expands the + // linearized commitment into its quotient and selector + // pairs inside the already-fused final MSM. + // + // QUOTIENT_MPTR is no longer a G1 point in this path. Its first + // two words carry: + // word 0: x_split + // word 1: one_minus_x_n + // =============================================================== + { + let x := mload(X_MPTR) + let k := 20 + // Compute both x^n and x^(n-1) with the same squaring walk: + // x_pow_2i tracks x^(2^i), while x_pow_2i_minus1 tracks + // x^(2^i - 1). + let x_pow_2i := x + let x_pow_2i_minus1 := 1 + for { let idx := 0 } lt(idx, k) { idx := add(idx, 1) } { + x_pow_2i_minus1 := mulmod( + mulmod(x_pow_2i_minus1, x_pow_2i_minus1, r), + x, + r + ) + x_pow_2i := mulmod(x_pow_2i, x_pow_2i, r) + } + let x_split := x_pow_2i_minus1 + let one_minus_x_n := addmod(1, sub(r, x_pow_2i), r) + + // PCS block 5 interprets this 2-word payload as scalar + // metadata, not as a materialized G1 point. + mstore(QUOTIENT_MPTR, x_split) + mstore(add(QUOTIENT_MPTR, 0x20), one_minus_x_n) + } + + // =============================================================== + // PCS computation (multi-prepare emitter from Step 5). + // + // The Rust lowering stage has already expanded the KZG multi-open + // equation into a sequence of generated Yul sub-blocks. Those + // blocks populate: + // - F_EVAL_MPTR / V_MPTR scalar batching values; + // - FINAL_COM_MPTR for the fused commitment MSM; + // - PAIRING_LHS_MPTR and PAIRING_RHS_MPTR for the final pairing. + // =============================================================== + { + // Generated PCS sub-block 1. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // 4 distinct rotation(s) + let x := mload(X_MPTR) + let omega := mload(OMEGA_MPTR) + let omega_inv := mload(OMEGA_INV_MPTR) + let x_pow_of_omega := x + mstore(add(ROT_POINTS_MPTR, 0x40), x_pow_of_omega) + x_pow_of_omega := mulmod(x_pow_of_omega, omega, r) + mstore(add(ROT_POINTS_MPTR, 0x60), x_pow_of_omega) + x_pow_of_omega := x + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + mstore(add(ROT_POINTS_MPTR, 0x20), x_pow_of_omega) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r) + mstore(add(ROT_POINTS_MPTR, 0x0), x_pow_of_omega) + } + // Generated PCS sub-block 2. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // pre-compute 43 x1 power(s) + let x1 := mload(X1_MPTR) + mstore(X1_POWERS_MPTR, 1) + let acc := 1 + let p := X1_POWERS_MPTR + for { let i := 0 } lt(i, 0x2a) { i := add(i, 1) } { + p := add(p, 0x20) + acc := mulmod(acc, x1, r) + mstore(p, and(acc, 0xffffffffffffffffffffffffffffffff)) + } + } + // Generated PCS sub-block 3. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[0]: 43 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x9980) + mstore(0xb2a0, 0x9480) + mstore(0xb2c0, 0xa020) + mstore(0xb2e0, 0xa040) + mstore(0xb300, 0xa0a0) + mstore(0xb320, 0xa0c0) + mstore(0xb340, 0xa120) + mstore(0xb360, 0x99a0) + mstore(0xb380, 0x99c0) + mstore(0xb3a0, 0x99e0) + mstore(0xb3c0, 0x9a00) + mstore(0xb3e0, 0x9a20) + mstore(0xb400, 0x9a40) + mstore(0xb420, 0x9a60) + mstore(0xb440, 0x9a80) + mstore(0xb460, 0x9aa0) + mstore(0xb480, 0x9ac0) + mstore(0xb4a0, 0x9ae0) + mstore(0xb4c0, 0x9b00) + mstore(0xb4e0, 0x9b20) + mstore(0xb500, 0x9b40) + mstore(0xb520, 0x9b60) + mstore(0xb540, 0x9b80) + mstore(0xb560, 0x9ba0) + mstore(0xb580, 0x9bc0) + mstore(0xb5a0, 0x9be0) + mstore(0xb5c0, 0x9c00) + mstore(0xb5e0, 0x9c20) + mstore(0xb600, 0x9c40) + mstore(0xb620, 0x9c60) + mstore(0xb640, 0x9c80) + mstore(0xb660, 0x9ca0) + mstore(0xb680, 0x9cc0) + mstore(0xb6a0, 0x9ce0) + mstore(0xb6c0, 0x9d00) + mstore(0xb6e0, 0x9d20) + mstore(0xb700, 0x9d40) + mstore(0xb720, 0x9d60) + mstore(0xb740, 0x9d80) + mstore(0xb760, 0x9da0) + mstore(0xb780, 0x9dc0) + mstore(0xb7a0, 0x9de0) + mstore(0xb7c0, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x9980) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x20) + for { let i := 1 } lt(i, 0x2b) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x20) + } + mstore(add(Q_EVAL_SET_MPTR, 0x0), q_eval_set_0) + } + // Generated PCS sub-block 4. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[1]: 3 commitment(s) + let q_eval_set_0 := mload(0x9660) + let q_eval_set_1 := mload(0x9920) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x9680), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9940), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x96a0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9960), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + mstore(add(Q_EVAL_SET_MPTR, 0x20), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x40), q_eval_set_1) + } + // Generated PCS sub-block 5. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[2]: 3 commitment(s) + let q_eval_set_0 := mload(0x9fe0) + let q_eval_set_1 := mload(0xa000) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa060), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa080), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa0e0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa100), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + mstore(add(Q_EVAL_SET_MPTR, 0x60), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x80), q_eval_set_1) + } + // Generated PCS sub-block 6. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[3]: 11 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x94a0) + mstore(0xb2a0, 0x9540) + mstore(0xb2c0, 0x97c0) + mstore(0xb2e0, 0x94c0) + mstore(0xb300, 0x9560) + mstore(0xb320, 0x97e0) + mstore(0xb340, 0x94e0) + mstore(0xb360, 0x9580) + mstore(0xb380, 0x9800) + mstore(0xb3a0, 0x9500) + mstore(0xb3c0, 0x96c0) + mstore(0xb3e0, 0x9820) + mstore(0xb400, 0x9520) + mstore(0xb420, 0x96e0) + mstore(0xb440, 0x9840) + mstore(0xb460, 0x95a0) + mstore(0xb480, 0x9700) + mstore(0xb4a0, 0x9860) + mstore(0xb4c0, 0x95c0) + mstore(0xb4e0, 0x9720) + mstore(0xb500, 0x9880) + mstore(0xb520, 0x95e0) + mstore(0xb540, 0x9740) + mstore(0xb560, 0x98a0) + mstore(0xb580, 0x9600) + mstore(0xb5a0, 0x9760) + mstore(0xb5c0, 0x98c0) + mstore(0xb5e0, 0x9620) + mstore(0xb600, 0x9780) + mstore(0xb620, 0x98e0) + mstore(0xb640, 0x9640) + mstore(0xb660, 0x97a0) + mstore(0xb680, 0x9900) + let q_eval_set_0 := mload(0x94a0) + let q_eval_set_1 := mload(0x9540) + let q_eval_set_2 := mload(0x97c0) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x60) + for { let i := 1 } lt(i, 0xb) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(mload(add(eval_p, 0x20))), pow, r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(mload(add(eval_p, 0x40))), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x60) + } + mstore(add(Q_EVAL_SET_MPTR, 0xa0), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0xc0), q_eval_set_1) + mstore(add(Q_EVAL_SET_MPTR, 0xe0), q_eval_set_2) + } + // Generated PCS sub-block 7. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // q_eval_set[4]: 5 commitment(s) (rolled, m>=4) + // stage per-(commit, rotation) eval source addresses + mstore(0xb280, 0x9e00) + mstore(0xb2a0, 0x9e20) + mstore(0xb2c0, 0x9e40) + mstore(0xb2e0, 0x9e60) + mstore(0xb300, 0x9e80) + mstore(0xb320, 0x9ea0) + mstore(0xb340, 0x9ec0) + mstore(0xb360, 0x9ee0) + mstore(0xb380, 0x9f00) + mstore(0xb3a0, 0x9f20) + mstore(0xb3c0, 0x9f40) + mstore(0xb3e0, 0x9f60) + mstore(0xb400, 0x9f80) + mstore(0xb420, 0x9fa0) + mstore(0xb440, 0x9fc0) + let q_eval_set_0 := mload(0x9e00) + let q_eval_set_1 := mload(0x9e20) + let q_eval_set_2 := mload(0x9e40) + let pow_p := add(X1_POWERS_MPTR, 0x20) + let eval_p := add(0xb280, 0x60) + for { let i := 1 } lt(i, 0x5) { i := add(i, 1) } { + let pow := mload(pow_p) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(mload(add(eval_p, 0x20))), pow, r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(mload(add(eval_p, 0x40))), pow, r), r) + pow_p := add(pow_p, 0x20) + eval_p := add(eval_p, 0x60) + } + mstore(add(Q_EVAL_SET_MPTR, 0x100), q_eval_set_0) + mstore(add(Q_EVAL_SET_MPTR, 0x120), q_eval_set_1) + mstore(add(Q_EVAL_SET_MPTR, 0x140), q_eval_set_2) + } + // Generated PCS sub-block 8. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // f_eval via Horner over 5 reversed set(s) + let x2 := mload(X2_MPTR) + let x3 := mload(X3_MPTR) + let f_eval := 0 + let Q_EVAL_CPTR := mload(Q_EVAL_CPTR_MPTR) + let rot_pt_0 := mload(add(ROT_POINTS_MPTR, 0x0)) + let rot_pt_1 := mload(add(ROT_POINTS_MPTR, 0x20)) + let rot_pt_2 := mload(add(ROT_POINTS_MPTR, 0x40)) + let rot_pt_3 := mload(add(ROT_POINTS_MPTR, 0x60)) + // --- set 4 (cardinality 3) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let dx_2 := addmod(x3, sub(r, rot_pt_0), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_0), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_0), r), r) + let lbasis_2 := 1 + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_0, sub(r, rot_pt_2), r), r) + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_0, sub(r, rot_pt_3), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, dx_2, r) + let bp_3 := mulmod(bp_2, lbasis_0, r) + let bp_4 := mulmod(bp_3, lbasis_1, r) + let bp_5 := mulmod(bp_4, lbasis_2, r) + let bq := scalar_inv(bp_5) + let lbasis_inv_2 := mulmod(bq, bp_4, r) + bq := mulmod(bq, lbasis_2, r) + let lbasis_inv_1 := mulmod(bq, bp_3, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_2 := mulmod(bq, bp_1, r) + bq := mulmod(bq, dx_2, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + den_inv := mulmod(den_inv, dx_inv_2, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x80)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x100)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x120)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + let term_2 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x140)), dx_inv_2, r), lbasis_inv_2, r) + eval := addmod(eval, sub(r, term_2), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 3 (cardinality 3) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let dx_2 := addmod(x3, sub(r, rot_pt_1), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_1), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_1), r), r) + let lbasis_2 := 1 + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_1, sub(r, rot_pt_2), r), r) + lbasis_2 := mulmod(lbasis_2, addmod(rot_pt_1, sub(r, rot_pt_3), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, dx_2, r) + let bp_3 := mulmod(bp_2, lbasis_0, r) + let bp_4 := mulmod(bp_3, lbasis_1, r) + let bp_5 := mulmod(bp_4, lbasis_2, r) + let bq := scalar_inv(bp_5) + let lbasis_inv_2 := mulmod(bq, bp_4, r) + bq := mulmod(bq, lbasis_2, r) + let lbasis_inv_1 := mulmod(bq, bp_3, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_2 := mulmod(bq, bp_1, r) + bq := mulmod(bq, dx_2, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + den_inv := mulmod(den_inv, dx_inv_2, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xa0)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xc0)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + let term_2 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0xe0)), dx_inv_2, r), lbasis_inv_2, r) + eval := addmod(eval, sub(r, term_2), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 2 (cardinality 2) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_3), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_3), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_3, sub(r, rot_pt_2), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, lbasis_0, r) + let bp_3 := mulmod(bp_2, lbasis_1, r) + let bq := scalar_inv(bp_3) + let lbasis_inv_1 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_1, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x60)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x80)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 1 (cardinality 2) --- + { + let dx_0 := addmod(x3, sub(r, rot_pt_2), r) + let dx_1 := addmod(x3, sub(r, rot_pt_1), r) + let lbasis_0 := 1 + lbasis_0 := mulmod(lbasis_0, addmod(rot_pt_2, sub(r, rot_pt_1), r), r) + let lbasis_1 := 1 + lbasis_1 := mulmod(lbasis_1, addmod(rot_pt_1, sub(r, rot_pt_2), r), r) + let bp_0 := dx_0 + let bp_1 := mulmod(bp_0, dx_1, r) + let bp_2 := mulmod(bp_1, lbasis_0, r) + let bp_3 := mulmod(bp_2, lbasis_1, r) + let bq := scalar_inv(bp_3) + let lbasis_inv_1 := mulmod(bq, bp_2, r) + bq := mulmod(bq, lbasis_1, r) + let lbasis_inv_0 := mulmod(bq, bp_1, r) + bq := mulmod(bq, lbasis_0, r) + let dx_inv_1 := mulmod(bq, bp_0, r) + bq := mulmod(bq, dx_1, r) + let dx_inv_0 := bq + let den_inv := dx_inv_0 + den_inv := mulmod(den_inv, dx_inv_1, r) + let eval := mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), den_inv, r) + let term_0 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x20)), dx_inv_0, r), lbasis_inv_0, r) + eval := addmod(eval, sub(r, term_0), r) + let term_1 := mulmod(mulmod(mload(add(Q_EVAL_SET_MPTR, 0x40)), dx_inv_1, r), lbasis_inv_1, r) + eval := addmod(eval, sub(r, term_1), r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + // --- set 0 (cardinality 1) --- + { + let dx0 := addmod(x3, sub(r, rot_pt_2), r) + let dx0_inv := scalar_inv(dx0) + let eval := mulmod(addmod(calldataload(add(Q_EVAL_CPTR, 0x0)), sub(r, mload(add(Q_EVAL_SET_MPTR, 0x0))), r), dx0_inv, r) + f_eval := addmod(mulmod(f_eval, x2, r), eval, r) + } + mstore(F_EVAL_MPTR, f_eval) + } + // Generated PCS sub-block 9. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // build final_com and v (KZG single-opening proof, fused MSM) + // final MSM input length from circuit/VK shape: 78 term(s) + let x4 := mload(X4_MPTR) + let lin_x_split := mload(QUOTIENT_MPTR) + let lin_one_minus_x_n := mload(add(QUOTIENT_MPTR, 0x20)) + let Q_EVAL_CPTR := mload(Q_EVAL_CPTR_MPTR) + let x4_pow_full := 1 + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_1 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_2 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_3 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_4 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_5 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + let v := calldataload(Q_EVAL_CPTR) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), x4_pow_1, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), x4_pow_3, r), r) + v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x80)), x4_pow_4, r), r) + v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_5, r), r) + mcopy(0xb280, 0xa840, 0x80) + mstore(0xb300, 1) + mcopy(0xb320, 0xa8c0, 0x80) + mstore(0xb3a0, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0xb3c0, 0xacc0, 0x80) + mstore(0xb440, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0xb460, 0xa940, 0x80) + mstore(0xb4e0, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0xb500, 0xad40, 0x80) + mstore(0xb580, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0xb5a0, 0xaec0, 0x80) + mstore(0xb620, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0xb640, 0x6700, 0x80) + mstore(0xb6c0, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0xb6e0, 0x6480, 0x80) + mstore(0xb760, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0xb780, 0x6500, 0x80) + mstore(0xb800, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0xb820, 0x6580, 0x80) + mstore(0xb8a0, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0xb8c0, 0x6600, 0x80) + mstore(0xb940, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0xb960, 0x6680, 0x80) + mstore(0xb9e0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0xba00, 0x6280, 0x80) + mstore(0xba80, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0xbaa0, 0x6300, 0x80) + mstore(0xbb20, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0xbb40, 0x6380, 0x80) + mstore(0xbbc0, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0xbbe0, 0x6400, 0x80) + mstore(0xbc60, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0xbc80, 0x6780, 0x80) + mstore(0xbd00, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0xbd20, 0x6800, 0x80) + mstore(0xbda0, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0xbdc0, 0x6880, 0x80) + mstore(0xbe40, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0xbe60, 0x6900, 0x80) + mstore(0xbee0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0xbf00, 0x6b00, 0x80) + mstore(0xbf80, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0xbfa0, 0x6e80, 0x80) + mstore(0xc020, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0xc040, 0x6f80, 0x80) + mstore(0xc0c0, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0xc0e0, 0x7000, 0x80) + mstore(0xc160, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0xc180, 0x7080, 0x80) + mstore(0xc200, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0xc220, 0x7100, 0x80) + mstore(0xc2a0, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0xc2c0, 0x7180, 0x80) + mstore(0xc340, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0xc360, 0x7200, 0x80) + mstore(0xc3e0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0xc400, 0x7280, 0x80) + mstore(0xc480, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0xc4a0, 0x7300, 0x80) + mstore(0xc520, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0xc540, 0x7380, 0x80) + mstore(0xc5c0, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0xc5e0, 0x7400, 0x80) + mstore(0xc660, mload(add(X1_POWERS_MPTR, 0x400))) + mcopy(0xc680, 0x7480, 0x80) + mstore(0xc700, mload(add(X1_POWERS_MPTR, 0x420))) + mcopy(0xc720, 0x7500, 0x80) + mstore(0xc7a0, mload(add(X1_POWERS_MPTR, 0x440))) + mcopy(0xc7c0, 0x7580, 0x80) + mstore(0xc840, mload(add(X1_POWERS_MPTR, 0x460))) + mcopy(0xc860, 0x7600, 0x80) + mstore(0xc8e0, mload(add(X1_POWERS_MPTR, 0x480))) + mcopy(0xc900, 0x7680, 0x80) + mstore(0xc980, mload(add(X1_POWERS_MPTR, 0x4a0))) + mcopy(0xc9a0, 0x7700, 0x80) + mstore(0xca20, mload(add(X1_POWERS_MPTR, 0x4c0))) + mcopy(0xca40, 0x7780, 0x80) + mstore(0xcac0, mload(add(X1_POWERS_MPTR, 0x4e0))) + mcopy(0xcae0, 0x7800, 0x80) + mstore(0xcb60, mload(add(X1_POWERS_MPTR, 0x500))) + mcopy(0xcb80, 0x7880, 0x80) + mstore(0xcc00, mload(add(X1_POWERS_MPTR, 0x520))) + let lin_query_scalar_41 := mload(add(X1_POWERS_MPTR, 0x540)) + let lin_cur_scalar_41 := mulmod(lin_query_scalar_41, lin_one_minus_x_n, r) + mcopy(0xcc20, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0xcca0, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xccc0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0xcd40, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xcd60, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0xcde0, lin_cur_scalar_41) + lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) + mcopy(0xce00, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0xce80, lin_cur_scalar_41) + mcopy(0xcea0, 0x6980, 0x80) + mstore(0xcf20, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0xcf40, 0x6a00, 0x80) + mstore(0xcfc0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0xcfe0, 0x6a80, 0x80) + mstore(0xd060, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0xd080, 0x6b80, 0x80) + mstore(0xd100, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0xd120, 0x6c00, 0x80) + mstore(0xd1a0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) + mcopy(0xd1c0, 0x6c80, 0x80) + mstore(0xd240, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) + mcopy(0xd260, 0x6d00, 0x80) + mstore(0xd2e0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) + mcopy(0xd300, 0x6d80, 0x80) + mstore(0xd380, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) + mcopy(0xd3a0, 0x6e00, 0x80) + mstore(0xd420, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) + mcopy(0xd440, 0x6f00, 0x80) + mstore(0xd4c0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) + mcopy(0xd4e0, 0xa6c0, 0x80) + mstore(0xd560, x4_pow_1) + mcopy(0xd580, 0xa740, 0x80) + mstore(0xd600, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0xd620, 0xa7c0, 0x80) + mstore(0xd6a0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0xd6c0, 0xac40, 0x80) + mstore(0xd740, x4_pow_2) + mcopy(0xd760, 0xadc0, 0x80) + mstore(0xd7e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0xd800, 0xae40, 0x80) + mstore(0xd880, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) + mcopy(0xd8a0, 0xa140, 0x80) + mstore(0xd920, x4_pow_3) + mcopy(0xd940, 0xa1c0, 0x80) + mstore(0xd9c0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) + mcopy(0xd9e0, 0xa240, 0x80) + mstore(0xda60, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_3, r)) + mcopy(0xda80, 0xa2c0, 0x80) + mstore(0xdb00, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_3, r)) + mcopy(0xdb20, 0xa340, 0x80) + mstore(0xdba0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_3, r)) + mcopy(0xdbc0, 0xa3c0, 0x80) + mstore(0xdc40, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_3, r)) + mcopy(0xdc60, 0xa440, 0x80) + mstore(0xdce0, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_3, r)) + mcopy(0xdd00, 0xa4c0, 0x80) + mstore(0xdd80, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_3, r)) + mcopy(0xdda0, 0xa540, 0x80) + mstore(0xde20, mulmod(mload(add(X1_POWERS_MPTR, 0x100)), x4_pow_3, r)) + mcopy(0xde40, 0xa5c0, 0x80) + mstore(0xdec0, mulmod(mload(add(X1_POWERS_MPTR, 0x120)), x4_pow_3, r)) + mcopy(0xdee0, 0xa640, 0x80) + mstore(0xdf60, mulmod(mload(add(X1_POWERS_MPTR, 0x140)), x4_pow_3, r)) + mcopy(0xdf80, 0xa9c0, 0x80) + mstore(0xe000, x4_pow_4) + mcopy(0xe020, 0xaa40, 0x80) + mstore(0xe0a0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_4, r)) + mcopy(0xe0c0, 0xaac0, 0x80) + mstore(0xe140, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_4, r)) + mcopy(0xe160, 0xab40, 0x80) + mstore(0xe1e0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_4, r)) + mcopy(0xe200, 0xabc0, 0x80) + mstore(0xe280, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_4, r)) + mcopy(0xe2a0, F_COM_MPTR, 0x80) + mstore(0xe320, x4_pow_5) + if success { + success := staticcall(gas(), 0x0c, 0xb280, 0x30c0, FINAL_COM_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mstore(V_MPTR, v) + } + // Generated PCS sub-block 10. These lines are + // emitted by the multi-prepare lowering pass and are kept + // grouped so gas checkpoints can attribute their cost. + { + // Scale z*pi - vG before the final pairing check + // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) + mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) + if success { + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(0x1080, FINAL_COM_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) + if success { + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + if success { + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) + } + } + + // Batch the prevalidated public IVC accumulator pairing equation + // into the final KZG pairing. + // + // We do not simply multiply the two pairing equations together: + // two bad equations could cancel. Instead, after all four G1 + // pairing inputs are fixed, derive a verifier-local randomizer + // alpha and check: + // + // e(kzg_rhs + alpha * acc_rhs, G2_BASE) + // * e(kzg_lhs + alpha * acc_lhs, NEG_S_G2_BASE) == 1 + // + // If either original equation is bad, this combined equation + // holds for at most one alpha in Fr. + { + let batch_ptr := 0x0100 + + // Domain || KZG rhs/lhs || accumulator rhs/lhs. + mstore(batch_ptr, 0x70616972696e672d62617463682d6163632d6b7a670000000000000000) + mcopy(add(batch_ptr, 0x20), PAIRING_RHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0xa0), PAIRING_LHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0x0120), ACC_RHS_MPTR, 0x80) + mcopy(add(batch_ptr, 0x01a0), ACC_LHS_MPTR, 0x80) + // alpha is Fiat-Shamir over the fully materialized pairing + // inputs. Replace the negligible zero draw with one so the + // accumulator equation cannot be accidentally dropped. + let acc_pair_alpha := mod(keccak256(batch_ptr, 0x0220), r) + if iszero(acc_pair_alpha) { acc_pair_alpha := 1 } + + // PAIRING_RHS_MPTR += alpha * ACC_RHS_MPTR. + // First compute alpha * ACC_RHS with a one-pair G1MSM, then + // add it into the KZG RHS point. + mcopy(batch_ptr, ACC_RHS_MPTR, 0x80) + mstore(add(batch_ptr, 0x80), acc_pair_alpha) + if success { + success := staticcall(gas(), 0x0c, batch_ptr, 0xa0, batch_ptr, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(add(batch_ptr, 0x80), PAIRING_RHS_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, batch_ptr, 0x0100, PAIRING_RHS_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + + // PAIRING_LHS_MPTR += alpha * ACC_LHS_MPTR. + // Mirror the same randomized batching on the KZG LHS point. + mcopy(batch_ptr, ACC_LHS_MPTR, 0x80) + mstore(add(batch_ptr, 0x80), acc_pair_alpha) + if success { + success := staticcall(gas(), 0x0c, batch_ptr, 0xa0, batch_ptr, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + mcopy(add(batch_ptr, 0x80), PAIRING_LHS_MPTR, 0x80) + if success { + success := staticcall(gas(), 0x0b, batch_ptr, 0x0100, PAIRING_LHS_MPTR, 0x80) + success := and(success, eq(returndatasize(), 0x80)) + } + } + + // The Yul `ec_pairing` helper checks + // e(arg0, G2_BASE) * e(arg1, NEG_S_G2_BASE) == 1 + // i.e. e(arg0, [1]_2) = e(arg1, [s]_2). + // + // The KZG pairing identity is + // e(final_com - v*G + x3*pi, [1]_2) = e(pi, [s]_2), + // so arg0 must be (final_com - v*G + x3*pi) and arg1 must be + // pi. The PAIRING_*_MPTR slots store + // PAIRING_LHS_MPTR := pi + // PAIRING_RHS_MPTR := final_com - v*G + x3*pi + // -- the historical "LHS"/"RHS" naming follows the dual MSM + // accumulator (left = pi, right = combined) and *not* the + // pairing argument order. Pass them swapped to ec_pairing. + if iszero(success) { revert(0, 0) } + success := ec_pairing(success, PAIRING_RHS_MPTR, PAIRING_LHS_MPTR) + + + + // Success path is terminal. Invalid inputs have already reverted, + // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } + mstore(RETURN_MPTR, 1) + return(RETURN_MPTR, 0x20) + } + } +} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2VerifyingKey.sol b/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2VerifyingKey.sol new file mode 100644 index 000000000..8f0b378f8 --- /dev/null +++ b/proofs/solidity-verifier/fixtures/moonlight-wrap/Halo2VerifyingKey.sol @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity ^0.8.24; + +/// @title Halo2 BLS12-381 verifying-key payload. +/// @notice Contract whose deployed runtime is `INVALID || generated verifier-key payload`. +/// @dev Byte 0 is an unconditional INVALID opcode so direct calls cannot execute payload bytes as code. The linked verifier pins the full runtime by length/codehash and copies the payload starting at byte 1. +/// @dev The layout follows the verifier inputs derived from +/// `midfall/proofs/src/plonk/mod.rs::VerifyingKey` and the transcript +/// `vk.hash_into` behavior used by `midfall/proofs/src/plonk/verifier.rs`. +/// +/// Layout (in 32-byte words, big-endian). The header slots are generated from +/// Rust's `VkHeaderLayout`; the byte offsets are absolute from the start of the +/// VK payload, not from byte 0 of the runtime. Runtime byte 0 is the INVALID +/// prefix; the verifier loads the payload via +/// `extcodecopy(vk, VK_MPTR, 0x01, vk_payload_len)` and then references each +/// slot by `VK_MPTR + i`. +/// +/// word 0 : vk_digest (Fq, transcript_repr of the CS) +/// word 1 : num_instances +/// word 2 : k (log2 of the domain size) +/// word 3 : n_inv (1/n in Fr) +/// word 4 : omega (n-th primitive root of unity) +/// word 5 : omega_inv +/// word 6 : omega_inv_to_l (omega_inv ^ |rotation_last|) +/// word 7 : has_accumulator (0 or 1) +/// word 8 : acc_offset (instance index of the accumulator) +/// word 9 : num_acc_limbs +/// word 10 : num_acc_limb_bits +/// word 11..14 : G1_BASE (4 words, EIP-2537 padded) +/// word 15..22 : G2_BASE (8 words, EIP-2537 padded) +/// word 23..30 : NEG_S_G2_BASE (8 words, EIP-2537 padded) +/// word 31..30 + Q_PAYLOAD : quotient VM constants + packed bytecode +/// word 31 + Q_PAYLOAD .. : fixed_comms (4 words each) +/// word 31 + Q_PAYLOAD + 4*N_FIXED .. +/// : permutation_comms (4 words each) +/// +/// Notes: +/// - `extcodehash` of this contract is pinned by the linked verifier via +/// `EXPECTED_VK_CODEHASH`, so any byte tweak is detected at deploy time. +/// - The quotient identity interpreter's static program is stored in this +/// pinned VK runtime. The verifier reads it from memory after `extcodecopy`, +/// avoiding verifier-side PUSH32/mstore immediates while keeping the program +/// covered by `EXPECTED_VK_CODEHASH`. +/// - The midnight-proofs migration bakes the per-lookup chunk counts, trashcan +/// structure, and `num_simple_selectors` into the generated verifier code. +contract Halo2VerifyingKey { + /// @notice Deploy the verifying-key payload as this contract's runtime bytecode. + /// @dev The constructor writes an INVALID byte followed by generated words into memory and returns that prefixed runtime. + /// @dev The transient construction buffer starts at `0x80`, preserving Solidity's reserved memory words. + constructor() { + assembly { + // Runtime layout: + // byte 0 : INVALID, so the payload cannot be executed + // byte 1..end : generated VK payload copied by Halo2Verifier + // + // `runtime` includes the INVALID prefix; `payload` points to word + // zero of the verifier-key data described in the contract NatSpec. + let runtime := 0x80 + let payload := add(runtime, 0x01) + mstore8(runtime, 0xfe) + // Header, base-point, and quotient-program words generated from + // VkPayloadLayout. The inline names on each mstore identify the + // exact slot in the rendered source. + mstore(add(payload, 0x0000), 0x56c0824fcff237dd8dc7b15f527346d9e1647d191815acb142500b0293e84f66) // vk_digest + mstore(add(payload, 0x0020), 0x0000000000000000000000000000000000000000000000000000000000000013) // num_instances + mstore(add(payload, 0x0040), 0x0000000000000000000000000000000000000000000000000000000000000014) // k + mstore(add(payload, 0x0060), 0x73eda0144f284aae5b6554d46c21576b363d4ec725be2bff1a400fff00001001) // n_inv + mstore(add(payload, 0x0080), 0x03e1c54bcb947035a57a6e07cb98de4a2f69e02d265e09d9fece7e0e39898d4b) // omega + mstore(add(payload, 0x00a0), 0x6c39442eade0092768ac033fa6f608750624a1bb17dbc026ef97c3573a28fc8c) // omega_inv + mstore(add(payload, 0x00c0), 0x2a0ccbaa0613f093f2bb6e97859513f0b613d8587eaa92db9e5604b8d6b68d45) // omega_inv_to_l + mstore(add(payload, 0x00e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // has_accumulator + mstore(add(payload, 0x0100), 0x000000000000000000000000000000000000000000000000000000000000000b) // acc_offset + mstore(add(payload, 0x0120), 0x0000000000000000000000000000000000000000000000000000000000000007) // num_acc_limbs + mstore(add(payload, 0x0140), 0x0000000000000000000000000000000000000000000000000000000000000038) // num_acc_limb_bits + mstore(add(payload, 0x0160), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) // g1_x_hi + mstore(add(payload, 0x0180), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) // g1_x_lo + mstore(add(payload, 0x01a0), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) // g1_y_hi + mstore(add(payload, 0x01c0), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) // g1_y_lo + mstore(add(payload, 0x01e0), 0x00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051) // g2_x_c0_hi + mstore(add(payload, 0x0200), 0xc6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8) // g2_x_c0_lo + mstore(add(payload, 0x0220), 0x0000000000000000000000000000000013e02b6052719f607dacd3a088274f65) // g2_x_c1_hi + mstore(add(payload, 0x0240), 0x596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e) // g2_x_c1_lo + mstore(add(payload, 0x0260), 0x000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351a) // g2_y_c0_hi + mstore(add(payload, 0x0280), 0xadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801) // g2_y_c0_lo + mstore(add(payload, 0x02a0), 0x000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99) // g2_y_c1_hi + mstore(add(payload, 0x02c0), 0xcb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be) // g2_y_c1_lo + mstore(add(payload, 0x02e0), 0x000000000000000000000000000000000632aaf712568f19c297802268a7ad9d) // neg_s_g2_x_c0_hi + mstore(add(payload, 0x0300), 0xceea6ef7ab6f75a7d26781c8e90c7432bc5e99dcc219ba64010f3052123983ab) // neg_s_g2_x_c0_lo + mstore(add(payload, 0x0320), 0x00000000000000000000000000000000191ff4920e077a2f8cb3969ba8f05bc2) // neg_s_g2_x_c1_hi + mstore(add(payload, 0x0340), 0xaa9da8c95d640b1e051be7cf344ee7f01996df2568bf0e7ccd9eb70978820045) // neg_s_g2_x_c1_lo + mstore(add(payload, 0x0360), 0x0000000000000000000000000000000005f434ebf45460a864ad5b17497c7903) // neg_s_g2_y_c0_hi + mstore(add(payload, 0x0380), 0x71820c70c83aa186029536d22dff54373251152c28bc43269f95281eba1b012e) // neg_s_g2_y_c0_lo + mstore(add(payload, 0x03a0), 0x0000000000000000000000000000000004d1c747141bcac15e77e3e1d3853254) // neg_s_g2_y_c1_hi + mstore(add(payload, 0x03c0), 0xc8687afdad35345a04f79d9c2759007f6640676eb44aee7011ce5ad80744bb23) // neg_s_g2_y_c1_lo + mstore(add(payload, 0x03e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // quotient_const + mstore(add(payload, 0x0400), 0x00bbe1fbe9ef1e2d62490b03a82bf9ef10f5e9b2323033669cf6c50481f63e05) // quotient_const + mstore(add(payload, 0x0420), 0x0000000000000000000000000000000000000000000000000100000000000000) // quotient_const + mstore(add(payload, 0x0440), 0x0000000000000000000000000000000000010000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0460), 0x0000000000000000000000000000000000000000000000000000000400000000) // quotient_const + mstore(add(payload, 0x0480), 0x0000000000000000000000000000000000000000040000000000000000000000) // quotient_const + mstore(add(payload, 0x04a0), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x04c0), 0x0000000000000000000000000000000000000000000000100000000000000000) // quotient_const + mstore(add(payload, 0x04e0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000) // quotient_const + mstore(add(payload, 0x0500), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefeffffff00000001) // quotient_const + mstore(add(payload, 0x0520), 0x73eda753299d7d483339d80809a1d80553bca402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x0540), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffb00000001) // quotient_const + mstore(add(payload, 0x0560), 0x73eda753299d7d483339d80809a1d80553bda402fbfe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x0580), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffefffff001) // quotient_const + mstore(add(payload, 0x05a0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5beeffffffff00000001) // quotient_const + mstore(add(payload, 0x05c0), 0x00000000000000000000000000000010ff0726c5de281020ad8016cf6f691213) // quotient_const + mstore(add(payload, 0x05e0), 0x0000000000000000000000000000002c64068790f917282347187665718b04c8) // quotient_const + mstore(add(payload, 0x0600), 0x00000000000000000000000000000027241bb5338dce8a77499428839473bf3a) // quotient_const + mstore(add(payload, 0x0620), 0x0000000000000000000000000000002b7c4a26a1c7ae6fc4b499d04e4a463c4b) // quotient_const + mstore(add(payload, 0x0640), 0x000000000000000000000000000000274bc40fcf526be95333a8c22c79465298) // quotient_const + mstore(add(payload, 0x0660), 0x0000000000000000000000000000002a5ee6db49930276e2939d1c43ac82f744) // quotient_const + mstore(add(payload, 0x0680), 0x73eda753299d7d483339d80809a1d7edd77e26c51c38afb5debf8afa00c15cc3) // quotient_const + mstore(add(payload, 0x06a0), 0x73eda753299d7d483339d80809a1d7c553bda402fffe5bfeffffffff00000002) // quotient_const + mstore(add(payload, 0x06c0), 0x73eda753299d7d483339d80809a1d80553bda402fffe53ebc627fffef6280001) // quotient_const + mstore(add(payload, 0x06e0), 0x0000000000000000000001000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0700), 0x0000000100000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0720), 0x6bc66e553973f396854f5626172ba135587d41e37a68209402355093fdcaaf6c) // quotient_const + mstore(add(payload, 0x0740), 0x63f31e3f446953960c9d6964474300df43ab29179970f642a28e39d6c883c74b) // quotient_const + mstore(add(payload, 0x0760), 0x73eda753299d7d483339d70809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x0780), 0x73eda752299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x07a0), 0x082738fdf02989b1adea81e1f27636cffb40621f85963b6afdcaaf6b02355095) // quotient_const + mstore(add(payload, 0x07c0), 0x0ffa8913e53429b2269c6ea3c25ed72610127aeb668d65bc5d71c628377c38b6) // quotient_const + mstore(add(payload, 0x07e0), 0x01ec1c0519185dfe86132d479c76d0786e1e037d0b05ca47648a1c5d29492c9b) // quotient_const + mstore(add(payload, 0x0800), 0x1e179025ca2470882b34e63940ccbd7ad9090bf414d43b696e093b5a8782528f) // quotient_const + mstore(add(payload, 0x0820), 0x4298bfee9a84c8ef8e83702075cb1abeb576f146636342e3db9ea6b0a4adf29d) // quotient_const + mstore(add(payload, 0x0840), 0x3c83b078e9abed278d042acc8f3bd21e228716f04be96af8025da860e2d1bba9) // quotient_const + mstore(add(payload, 0x0860), 0x427868260f487d1ef07edaadf37f5dbe705bd1318290f2577ae756b009c24f11) // quotient_const + mstore(add(payload, 0x0880), 0x03020e6a35e595abd22838beeadc45cfcb0545d85ca0ab2c59d44c203fac84a7) // quotient_const + mstore(add(payload, 0x08a0), 0x000000000000000000000000000000000000000000000000d201000000010000) // quotient_const + mstore(add(payload, 0x08c0), 0x0000000100001b7c3f8d3fe3c5b448f1bdeb2ae34698b72d6ce966fc208c05ed) // quotient_const + mstore(add(payload, 0x08e0), 0x73eda753299d7d483339d80809a1d7fd4057a4c12f26d1c1778e3360a6820001) // quotient_const + mstore(add(payload, 0x0900), 0x057797fa7060856f215654ff11006fe0acf6a437e9477bf6f782dfac86f2cf75) // quotient_const + mstore(add(payload, 0x0920), 0x0000000000000000000000000000000000000000000000000000000000000002) // quotient_const + mstore(add(payload, 0x0940), 0x0000000000000000000000000000000000000000000000000200000000000000) // quotient_const + mstore(add(payload, 0x0960), 0x0000000000000000000000000000000000020000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0980), 0x73eda753299d7d483339d80809a1d7e13511a4044eaa5bff4600ffff00005556) // quotient_const + mstore(add(payload, 0x09a0), 0x73eda753299d7d483339d80809a1d7c553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x09c0), 0x73eda753299d7d483339d80809a1d7d340dd972492de594de627fffefcb80349) // quotient_const + mstore(add(payload, 0x09e0), 0x73eda753299d7d483339d80809a1d7dbdc391ab4d8ac003bbd48021b82456c9b) // quotient_const + mstore(add(payload, 0x0a00), 0x73eda753299d7d483339d80809a1d7d1448ee5caf5eefcffbc9fabc57759e23f) // quotient_const + mstore(add(payload, 0x0a20), 0x73eda753299d7d483339d80809a1d80418c74cc18bb28443d3978d1fd47ffce1) // quotient_const + mstore(add(payload, 0x0a40), 0x0000000000000000000000000000006425c019bcda40056233b00000068ff970) // quotient_const + mstore(add(payload, 0x0a60), 0x00000000000000000000000000000052ef09129c4ea4b786856ffbc6fb7526cc) // quotient_const + mstore(add(payload, 0x0a80), 0x73eda753299d7d483339d80809a1d7d89a085d30fc8a7106a170ac2377c34ab9) // quotient_const + mstore(add(payload, 0x0aa0), 0x73eda753299d7d483339d80809a1d7f8ce65bb936f2d836012914aca65f077e1) // quotient_const + mstore(add(payload, 0x0ac0), 0x000000000000000000000000000000681e5d7c70141ebdfe86c0a873114c3b84) // quotient_const + mstore(add(payload, 0x0ae0), 0x000000000000000000000000000000297784894e27525bc342b7fde37dba9366) // quotient_const + mstore(add(payload, 0x0b00), 0x0000000000000000000000000000000275ecae82e897af7658d0e5be57000640) // quotient_const + mstore(add(payload, 0x0b20), 0x000000000000000000000000000000013af65741744bd7bb2c6872df2b800320) // quotient_const + mstore(add(payload, 0x0b40), 0x00000000000000000000000000000059736a8da406e7d5f0bd1ea7b710796a90) // quotient_const + mstore(add(payload, 0x0b60), 0x0000000000000000000000000000000c8557e86f90d0d89eed6eb5349a0f8820) // quotient_const + mstore(add(payload, 0x0b80), 0x0453ae02a5f228d8f956b5eab4fc92bbeea5eb26b6ae4b42b4fdfcfdf026aa22) // quotient_const + mstore(add(payload, 0x0ba0), 0x0000000000000000000000000000000000000000000000000000000800000000) // quotient_const + mstore(add(payload, 0x0bc0), 0x0000000000000000000000000000000000000000080000000000000000000000) // quotient_const + mstore(add(payload, 0x0be0), 0x0000000000000000000000000000000000000000000000000000000000002000) // quotient_const + mstore(add(payload, 0x0c00), 0x0000000000000000000000000000000000000000000000200000000000000000) // quotient_const + mstore(add(payload, 0x0c20), 0x73eda753299d7d483339d80809a1d7f454b67d3d21d64bde527fe92f9096edee) // quotient_const + mstore(add(payload, 0x0c40), 0x73eda753299d7d483339d80809a1d7d8efb71c7206e733dbb8e789998e74fb39) // quotient_const + mstore(add(payload, 0x0c60), 0x73eda753299d7d483339d80809a1d7de2fa1eecf722fd187b66bd77b6b8c40c7) // quotient_const + mstore(add(payload, 0x0c80), 0x73eda753299d7d483339d80809a1d7d9d7737d61384fec3a4b662fb0b5b9c3b6) // quotient_const + mstore(add(payload, 0x0ca0), 0x73eda753299d7d483339d80809a1d7de07f99433ad9272abcc573dd286b9ad69) // quotient_const + mstore(add(payload, 0x0cc0), 0x73eda753299d7d483339d80809a1d7daf4d6c8b96cfbe51c6c62e3bb537d08bd) // quotient_const + mstore(add(payload, 0x0ce0), 0x00000000000000000000000000000021fe0e4d8bbc5020415b002d9eded22426) // quotient_const + mstore(add(payload, 0x0d00), 0x00000000000000000000000000000058c80d0f21f22e50468e30eccae3160990) // quotient_const + mstore(add(payload, 0x0d20), 0x0000000000000000000000000000004e48376a671b9d14ee9328510728e77e74) // quotient_const + mstore(add(payload, 0x0d40), 0x00000000000000000000000000000056f8944d438f5cdf896933a09c948c7896) // quotient_const + mstore(add(payload, 0x0d60), 0x0000000000000000000000000000004e97881f9ea4d7d2a667518458f28ca530) // quotient_const + mstore(add(payload, 0x0d80), 0x73eda753299d7d4833351088b4af7508df8b737010b26e15294bfcbb9194fffd) // quotient_const + mstore(add(payload, 0x0da0), 0x0000000000000000000002000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0dc0), 0x0000000200000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x0de0), 0x639f3557494a69e4d764d44424b56a655d3cdfc3f4d1e529046aa128fb955ed7) // quotient_const + mstore(add(payload, 0x0e00), 0x53f8952b5f3529e3e600fac084e429b93398ae2c32e39086451c73ae91078e95) // quotient_const + mstore(add(payload, 0x0e20), 0x72018b4e10851f49ad26aac06d2b078ce59fa085f4f891b79b75e3a1d6b6d366) // quotient_const + mstore(add(payload, 0x0e40), 0x55d6172d5f790cc00804f1cec8d51a8a7ab4980eeb2a209591f6c4a4787dad72) // quotient_const + mstore(add(payload, 0x0e60), 0x3154e7648f18b458a4b667e793d6bd469e46b2bc9c9b191b2461594e5b520d64) // quotient_const + mstore(add(payload, 0x0e80), 0x3769f6da3ff19020a635ad3b7a6605e731368d12b414f106fda2579e1d2e4458) // quotient_const + mstore(add(payload, 0x0ea0), 0x31753f2d1a55002942bafd5a16227a46e361d2d17d6d69a78518a94ef63db0f0) // quotient_const + mstore(add(payload, 0x0ec0), 0x70eb98e8f3b7e79c61119f491ec5923588b85e2aa35db0d2a62bb3dec0537b5a) // quotient_const + mstore(add(payload, 0x0ee0), 0x03d8380a3230bbfd0c265a8f38eda0f0dc3c06fa160b948ec91438ba52925936) // quotient_const + mstore(add(payload, 0x0f00), 0x3c2f204b9448e1105669cc7281997af5b21217e829a876d2dc1276b50f04a51e) // quotient_const + mstore(add(payload, 0x0f20), 0x1143d88a0b6c1496e9cd0838e1f45d7817303e89c6c829c8b73d4d62495be539) // quotient_const + mstore(add(payload, 0x0f40), 0x0519b99ea9ba5d06e6ce7d9114d5cc36f15089dd97d479f104bb50c2c5a37751) // quotient_const + mstore(add(payload, 0x0f60), 0x110328f8f4f37cf5adc3dd53dd5ce3778cf9fe60052388aff5cead6113849e21) // quotient_const + mstore(add(payload, 0x0f80), 0x057797fa7060856f215655ff11006fee9a1697597c277945ddaadfac83aad2c0) // quotient_const + mstore(add(payload, 0x0fa0), 0x0000000000000000000000000000003212e00cde6d2002b119d800000347fcb8) // quotient_const + mstore(add(payload, 0x0fc0), 0x000000000000000000000000000000340f2ebe380a0f5eff4360543988a61dc2) // quotient_const + mstore(add(payload, 0x0fe0), 0x0000000000000000000000000000002cb9b546d20373eaf85e8f53db883cb548) // quotient_const + mstore(add(payload, 0x1000), 0x0453ae02a5f228d8f956b6eab50092aaff9ec460d8863b22077de62a80bd8812) // quotient_const + mstore(add(payload, 0x1020), 0x73eda753299d7d4833351088b4af7508df8b737010b26601ef73fcbb87bd0000) // quotient_const + mstore(add(payload, 0x1040), 0x0aef2ff4e0c10ade42aca9fe2200e00159ed486fd28ef7edef05bf590de59ef3) // quotient_const + mstore(add(payload, 0x1060), 0x0000000000000000000000000000000000000000000000000000000000000006) // quotient_const + mstore(add(payload, 0x1080), 0x0000000000000000000000000000000000000000000000000600000000000000) // quotient_const + mstore(add(payload, 0x10a0), 0x0000000000000000000000000000000000060000000000000000000000000000) // quotient_const + mstore(add(payload, 0x10c0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffffff) // quotient_const + mstore(add(payload, 0x10e0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefdffffff00000001) // quotient_const + mstore(add(payload, 0x1100), 0x73eda753299d7d483339d80809a1d80553bba402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1120), 0x0000000000000000000000000000000000000000000000000000000000000003) // quotient_const + mstore(add(payload, 0x1140), 0x0000000000000000000000000000000000030000000000000000000000000000) // quotient_const + mstore(add(payload, 0x1160), 0x0000000000000000000000000000012c71404d368ec010269b10000013afec50) // quotient_const + mstore(add(payload, 0x1180), 0x000000000000000000000000000000f8cd1b37d4ebee2693904ff354f25f7464) // quotient_const + mstore(add(payload, 0x11a0), 0x000000000000000000000000000001385b1875503c5c39fb9441f95933e4b28c) // quotient_const + mstore(add(payload, 0x11c0), 0x0000000000000000000000000000007c668d9bea75f71349c827f9aa792fba32) // quotient_const + mstore(add(payload, 0x11e0), 0x0000000000000000000000000000000761c60b88b9c70e630a72b13b050012c0) // quotient_const + mstore(add(payload, 0x1200), 0x73eda753299d7d483339d80809a1d7a12dfd8a4625be569ccc4ffffef9700691) // quotient_const + mstore(add(payload, 0x1220), 0x73eda753299d7d483339d80809a1d7b264b49166b159a4787a900438048ad935) // quotient_const + mstore(add(payload, 0x1240), 0x00000000000000000000000000000003b0e305c45ce387318539589d82800960) // quotient_const + mstore(add(payload, 0x1260), 0x0000000000000000000000000000010c5a3fa8ec14b781d2375bf725316c3fb0) // quotient_const + mstore(add(payload, 0x1280), 0x000000000000000000000000000000259007b94eb27289dcc84c1f9dce2e9860) // quotient_const + mstore(add(payload, 0x12a0), 0x73eda753299d7d483339d80809a1d79d35602792ebdf9e00793f578beeb3c47d) // quotient_const + mstore(add(payload, 0x12c0), 0x73eda753299d7d483339d80809a1d802ddd0f5801766ac88a72f1a40a8fff9c1) // quotient_const + mstore(add(payload, 0x12e0), 0x73eda753299d7d483339d80809a1d7abe053165ef916860e42e15847ef869571) // quotient_const + mstore(add(payload, 0x1300), 0x73eda753299d7d483339d80809a1d7ec490dd323de5caac125229595cbe0efc1) // quotient_const + mstore(add(payload, 0x1320), 0x08a75c054be451b1f2ad6bd569f92577dd4bd64d6d5c968569fbf9fbe04d544d) // quotient_const + mstore(add(payload, 0x1340), 0x0000000000000000000000000000000000000000000000000000001800000000) // quotient_const + mstore(add(payload, 0x1360), 0x0000000000000000000000000000000000000000180000000000000000000000) // quotient_const + mstore(add(payload, 0x1380), 0x0000000000000000000000000000000000000000000000000000000000006000) // quotient_const + mstore(add(payload, 0x13a0), 0x0000000000000000000000000000000000000000000000600000000000000000) // quotient_const + mstore(add(payload, 0x13c0), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffff700000001) // quotient_const + mstore(add(payload, 0x13e0), 0x73eda753299d7d483339d80809a1d80553bda402f7fe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1400), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffe001) // quotient_const + mstore(add(payload, 0x1420), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bdeffffffff00000001) // quotient_const + mstore(add(payload, 0x1440), 0x73eda753299d7d483339d80809a1d7e355af567743ae3bbda4ffd260212ddbdb) // quotient_const + mstore(add(payload, 0x1460), 0x73eda753299d7d483339d80809a1d7ac8bb094e10dd00bb871cf13341ce9f671) // quotient_const + mstore(add(payload, 0x1480), 0x73eda753299d7d483339d80809a1d7b70b86399be46147106cd7aef7d718818d) // quotient_const + mstore(add(payload, 0x14a0), 0x73eda753299d7d483339d80809a1d7ae5b2956bf70a17c7596cc5f626b73876b) // quotient_const + mstore(add(payload, 0x14c0), 0x73eda753299d7d483339d80809a1d7b6bc3584645b26895898ae7ba60d735ad1) // quotient_const + mstore(add(payload, 0x14e0), 0x73eda753299d7d483339d80809a1d7b095efed6fd9f96e39d8c5c777a6fa1179) // quotient_const + mstore(add(payload, 0x1500), 0x00000000000000000000000000000065fa2ae8a334f060c4110088dc9c766c72) // quotient_const + mstore(add(payload, 0x1520), 0x00000000000000000000000000000000000000000c0000000000000000000000) // quotient_const + mstore(add(payload, 0x1540), 0x0000000000000000000000000000010a58272d65d68af0d3aa92c660a9421cb0) // quotient_const + mstore(add(payload, 0x1560), 0x0000000000000000000000000000000000000000000000300000000000000000) // quotient_const + mstore(add(payload, 0x1580), 0x000000000000000000000000000000ead8a63f3552d73ecbb978f3157ab67b5c) // quotient_const + mstore(add(payload, 0x15a0), 0x000000000000000000000000000000852c1396b2eb457869d549633054a10e58) // quotient_const + mstore(add(payload, 0x15c0), 0x00000000000000000000000000000104e9bce7caae169e9c3b9ae1d5bda569c2) // quotient_const + mstore(add(payload, 0x15e0), 0x0000000000000000000000000000008274de73e5570b4f4e1dcd70eaded2b4e1) // quotient_const + mstore(add(payload, 0x1600), 0x000000000000000000000000000000ebc6985edbee8777f335f48d0ad7a5ef90) // quotient_const + mstore(add(payload, 0x1620), 0x0000000000000000000000000000007f1cb491dcb90764a7bad754cb0588e5cc) // quotient_const + mstore(add(payload, 0x1640), 0x73eda753299d7d48333049095fbd120c6b5942dd2166802b5297f978232a0002) // quotient_const + mstore(add(payload, 0x1660), 0x0000000000000000000006000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x1680), 0x0000000600000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x16a0), 0x4302515f88a4431e1fbaccbc5adc8f25703b5745de78f77d0d3fe37cf2c01c83) // quotient_const + mstore(add(payload, 0x16c0), 0x140e70dbca64831b4b8f40317b68cd20f34ec27e98adf994cf555b0db316abbd) // quotient_const + mstore(add(payload, 0x16e0), 0x73eda753299d7d483339d60809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1700), 0x73eda751299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001) // quotient_const + mstore(add(payload, 0x1720), 0x104e71fbe05313635bd503c3e4ec6d9ff680c43f0b2c76d5fb955ed6046aa12a) // quotient_const + mstore(add(payload, 0x1740), 0x1ff51227ca6853644d38dd4784bdae4c2024f5d6cd1acb78bae38c506ef8716c) // quotient_const + mstore(add(payload, 0x1760), 0x70156f48f76cc14b27137d78d0b4371477819d08e9f2c77036ebc744ad6da6cb) // quotient_const + mstore(add(payload, 0x1780), 0x37be870795549c37dcd00b9588085d0fa1ab8c1ad655e52c23ed8949f0fb5ae3) // quotient_const + mstore(add(payload, 0x17a0), 0x62a9cec91e3168b1496ccfcf27ad7a8d3c8d65793936323648c2b29cb6a41ac8) // quotient_const + mstore(add(payload, 0x17c0), 0x6ed3edb47fe320414c6b5a76f4cc0bce626d1a256829e20dfb44af3c3a5c88b0) // quotient_const + mstore(add(payload, 0x17e0), 0x62ea7e5a34aa00528575fab42c44f48dc6c3a5a2fadad34f0a31529dec7b61e0) // quotient_const + mstore(add(payload, 0x1800), 0x6de98a7ebdd251f08ee9668a33e94c65bdb3185246bd05a64c5767be80a6f6b3) // quotient_const + mstore(add(payload, 0x1820), 0x0b88a81e969233f724730fadaac8e2d294b414ee4222bdac5b3caa2ef7b70ba2) // quotient_const + mstore(add(payload, 0x1840), 0x0000000300000000000000000000000000000000000000000000000000000000) // quotient_const + mstore(add(payload, 0x1860), 0x409fb98f933d25e8d0038d4f7b2a98dbc278a3b57cfb0879943764202d0def59) // quotient_const + mstore(add(payload, 0x1880), 0x43fe0c177a010031bf648c1cc285529323863340cc562ac9e7aaad86598b55df) // quotient_const + mstore(add(payload, 0x18a0), 0x33cb899e22443dc4bd6718aaa5dd18684590bb9d54587d5a25b7e826dc13afab) // quotient_const + mstore(add(payload, 0x18c0), 0x5a46b0715e6d5198819eb2abc26638708b1b23dc3e7cb23c4a1bb20f9686f7ad) // quotient_const + mstore(add(payload, 0x18e0), 0x0f4d2cdbfd2f1714b46b78b33e8164a4d3f19d98c77d6dd30e31f24850ea65f3) // quotient_const + mstore(add(payload, 0x1900), 0x419d6a1793664a2e73d2a85da4119e5513d7a0cde3bde4e90718f923a87532fa) // quotient_const + mstore(add(payload, 0x1920), 0x33097aeadeda76e1094b97fb9816aa66a6edfb200f6a9a0fe16c08233a8dda63) // quotient_const + mstore(add(payload, 0x1940), 0x09062b3ea1b0c1037678aa3cc094d16f610fd18915e201850d7ce460bf058df5) // quotient_const + mstore(add(payload, 0x1960), 0x0456a29a706afacf2158850711006fe0acf6a437e9477bf6f782dfac86f2cf7b) // quotient_const + mstore(add(payload, 0x1980), 0x0397cc06bc030aab970dabe70cd498bbeea8daaea65607bb6a872125fec74a10) // quotient_const + mstore(add(payload, 0x19a0), 0x73eda753299d7d4833351088b4af7508df8b737010b26e15294bfcbb91950003) // quotient_const + mstore(add(payload, 0x19c0), 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140) // quotient_const + mstore(add(payload, 0x19e0), 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a) // quotient_const + mstore(add(payload, 0x1a00), 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db) // quotient_const + mstore(add(payload, 0x1a20), 0x0594e0109a0005958008060d000b0200011b000021020103070002000094a002) // quotient_program + mstore(add(payload, 0x1a40), 0x94c00394e00495000595200095400295600395800695a00795c00895e0099600) // quotient_program + mstore(add(payload, 0x1a60), 0x0a96200b96400c96600d96800e96a00496c00596e006970007972094a0009540) // quotient_program + mstore(add(payload, 0x1a80), 0x0295600395800496c00596e006970007972094c00295400395600495800596c0) // quotient_program + mstore(add(payload, 0x1aa0), 0x0696e00797000f972094e00395400495600595800696c00796e00f9700109720) // quotient_program + mstore(add(payload, 0x1ac0), 0x95000495400595600695800796c00f96e0109700119720952005954006956007) // quotient_program + mstore(add(payload, 0x1ae0), 0x95800f96c01096e011970012972095a00695400795600f95801096c01196e012) // quotient_program + mstore(add(payload, 0x1b00), 0x970013972095c00795400f95601095801196c01296e013970014972015974016) // quotient_program + mstore(add(payload, 0x1b20), 0x97800b03000121021703070001000094a00294c00394e0189500199520009540) // quotient_program + mstore(add(payload, 0x1b40), 0x0295600395801a95a01b95c00895e00996000a96201c96401d96601e96801f96) // quotient_program + mstore(add(payload, 0x1b60), 0xa01896c01996e01a97001b972094a00095400295600395801896c01996e01a97) // quotient_program + mstore(add(payload, 0x1b80), 0x001b972094c00295400395601895801996c01a96e01b970020972094e0039540) // quotient_program + mstore(add(payload, 0x1ba0), 0x1895601995801a96c01b96e020970021972095001895401995601a95801b96c0) // quotient_program + mstore(add(payload, 0x1bc0), 0x2096e021970022972095201995401a95601b95802096c02196e0229700239720) // quotient_program + mstore(add(payload, 0x1be0), 0x95a01a95401b95602095802196c02296e023970024972095c01b954020956021) // quotient_program + mstore(add(payload, 0x1c00), 0x95802296c02396e02497002597202697400b0300011b00012102270100000900) // quotient_program + mstore(add(payload, 0x1c20), 0x0695a00795c00895e00996000a96200b96400c96600094a00294c00394e00495) // quotient_program + mstore(add(payload, 0x1c40), 0x000595200d96800e96a01597401697800b04000121022801000008001a95a01b) // quotient_program + mstore(add(payload, 0x1c60), 0x95c00895e00996000a96201c96401d96600094a00294c00394e0189500199520) // quotient_program + mstore(add(payload, 0x1c80), 0x1e96801f96a02697400b040001210397a0290000000b2b0894a00994c00a94e0) // quotient_program + mstore(add(payload, 0x1ca0), 0x2a95402b95602c95800895e00996000a96202d97402e97600894a095e00994a0) // quotient_program + mstore(add(payload, 0x1cc0), 0x96000a94a096200994c095e00a94c096002f94c096a00a94e095e02f94e09680) // quotient_program + mstore(add(payload, 0x1ce0), 0x3094e096a02f95009660309500968031950096a02f9520964030952096603195) // quotient_program + mstore(add(payload, 0x1d00), 0x20968032952096a000954095402b954095602c95409580039560956033956097) // quotient_program + mstore(add(payload, 0x1d20), 0x20339580970034958097202f95a096203095a096403195a096603295a0968035) // quotient_program + mstore(add(payload, 0x1d40), 0x95a096a02f95c096003095c096203195c096403295c096603595c096803695c0) // quotient_program + mstore(add(payload, 0x1d60), 0x96a03396c096e03496c097003796c097203896e096e03796e097003996e09720) // quotient_program + mstore(add(payload, 0x1d80), 0x3a970097003b970097203c972097200d000b050000210397a03d030800021508) // quotient_program + mstore(add(payload, 0x1da0), 0x94a00994c00a94e00b95000c95202a95402b95602c95800d95a00e95c00895e0) // quotient_program + mstore(add(payload, 0x1dc0), 0x0996000a96200b96400c96600d96800e96a03e96c03f96e040970041972094a0) // quotient_program + mstore(add(payload, 0x1de0), 0x0895e00996000a96200b96400c96600d96800e96a094c00995e00a96000b9620) // quotient_program + mstore(add(payload, 0x1e00), 0x0c96400d96600e96804296a094e00a95e00b96000c96200d96400e9660429680) // quotient_program + mstore(add(payload, 0x1e20), 0x4396a095000b95e00c96000d96200e96404296604396804496a095200c95e00d) // quotient_program + mstore(add(payload, 0x1e40), 0x96000e96204296404396604496804596a095400095402b95602c95803e96c03f) // quotient_program + mstore(add(payload, 0x1e60), 0x96e040970041972095a00d95e00e96004296204396404496604596804696a095) // quotient_program + mstore(add(payload, 0x1e80), 0xc00e95e04296004396204496404596604696804796a015974016978003956095) // quotient_program + mstore(add(payload, 0x1ea0), 0x603e956095803f956096c040956096e041956097004895609720059580958040) // quotient_program + mstore(add(payload, 0x1ec0), 0x958096c041958096e0489580970049958097200796c096c04896c096e04996c0) // quotient_program + mstore(add(payload, 0x1ee0), 0x97004a96c097201096e096e04a96e097004b96e0972012970097004c97009720) // quotient_program + mstore(add(payload, 0x1f00), 0x14972097200d000b050001210397a04d03080001150894a00994c00a94e01c95) // quotient_program + mstore(add(payload, 0x1f20), 0x001d95202a95402b95602c95801e95a01f95c00895e00996000a96201c96401d) // quotient_program + mstore(add(payload, 0x1f40), 0x96601e96801f96a04e96c04f96e050970051972094a00895e00996000a96201c) // quotient_program + mstore(add(payload, 0x1f60), 0x96401d96601e96801f96a094c00995e00a96001c96201d96401e96601f968052) // quotient_program + mstore(add(payload, 0x1f80), 0x96a094e00a95e01c96001d96201e96401f96605296805396a095001c95e01d96) // quotient_program + mstore(add(payload, 0x1fa0), 0x001e96201f96405296605396805496a095201d95e01e96001f96205296405396) // quotient_program + mstore(add(payload, 0x1fc0), 0x605496805596a095400095402b95602c95804e96c04f96e050970051972095a0) // quotient_program + mstore(add(payload, 0x1fe0), 0x1e95e01f96005296205396405496605596805696a095c01f95e0529600539620) // quotient_program + mstore(add(payload, 0x2000), 0x5496405596605696805796a026974003956095604e956095804f956096c05095) // quotient_program + mstore(add(payload, 0x2020), 0x6096e051956097005895609720199580958050958096c051958096e058958097) // quotient_program + mstore(add(payload, 0x2040), 0x0059958097201b96c096c05896c096e05996c097005a96c097202196e096e05a) // quotient_program + mstore(add(payload, 0x2060), 0x96e097005b96e0972023970097005c9700972025972097200d000b0500012103) // quotient_program + mstore(add(payload, 0x2080), 0x97a05d0000000c390894a00994c00a94e02d97402e97600097a00097c00297e0) // quotient_program + mstore(add(payload, 0x20a0), 0x0398000898a00998c00a98e000954097c002954097e0039540980008954098a0) // quotient_program + mstore(add(payload, 0x20c0), 0x09954098c00a954098e002956097c003956097e05e9560988009956098a00a95) // quotient_program + mstore(add(payload, 0x20e0), 0x6098c02f9560996003958097c05e9580986038958098800a958098a02f958099) // quotient_program + mstore(add(payload, 0x2100), 0x4030958099600095e097a002960097a003962097a05e96c098403896c098605f) // quotient_program + mstore(add(payload, 0x2120), 0x96c098802f96c099203096c099403196c099605e96e098203896e098405f96e0) // quotient_program + mstore(add(payload, 0x2140), 0x98603a96e098802f96e099003096e099203196e099403296e099605e97009800) // quotient_program + mstore(add(payload, 0x2160), 0x38970098205f970098403a9700986060970098802f970098e030970099003197) // quotient_program + mstore(add(payload, 0x2180), 0x009920329700994035970099605e972097e038972098005f972098203a972098) // quotient_program + mstore(add(payload, 0x21a0), 0x4060972098603c972098802f972098c030972098e03197209900329720992035) // quotient_program + mstore(add(payload, 0x21c0), 0x9720994036972099600d000b060000210397a061020f000a001697800097a000) // quotient_program + mstore(add(payload, 0x21e0), 0x97c00297e00398000498200598400698600798800898a00998c00a98e00b9900) // quotient_program + mstore(add(payload, 0x2200), 0x0c992097a00095e00296000396200496400596600696800796a097c000954002) // quotient_program + mstore(add(payload, 0x2220), 0x95600395800496c00596e006970007972097e00295400395600495800596c006) // quotient_program + mstore(add(payload, 0x2240), 0x96e00797000f972098000395400495600595800696c00796e00f970010972098) // quotient_program + mstore(add(payload, 0x2260), 0x200495400595600695800796c00f96e010970011972098400595400695600795) // quotient_program + mstore(add(payload, 0x2280), 0x800f96c01096e011970012972098600695400795600f95801096c01196e01297) // quotient_program + mstore(add(payload, 0x22a0), 0x0013972098800795400f95601095801196c01296e013970014972095400898a0) // quotient_program + mstore(add(payload, 0x22c0), 0x0998c00a98e00b99000c99200d99400e996095600998a00a98c00b98e00c9900) // quotient_program + mstore(add(payload, 0x22e0), 0x0d99200e994042996095800a98a00b98c00c98e00d99000e9920429940439960) // quotient_program + mstore(add(payload, 0x2300), 0x96c00b98a00c98c00d98e00e990042992043994044996096e00c98a00d98c00e) // quotient_program + mstore(add(payload, 0x2320), 0x98e042990043992044994045996097000d98a00e98c04298e043990044992045) // quotient_program + mstore(add(payload, 0x2340), 0x994046996097200e98a04298c04398e04499004599204699404799600894a009) // quotient_program + mstore(add(payload, 0x2360), 0x94c00a94e00b95000c95200d95a00e95c01597400d99400e99600d000b060001) // quotient_program + mstore(add(payload, 0x2380), 0x210397a062020f0009000097a00097c00297e00398001898201998401a98601b) // quotient_program + mstore(add(payload, 0x23a0), 0x98800898a00998c00a98e01c99001d99201e994097a00095e002960003962018) // quotient_program + mstore(add(payload, 0x23c0), 0x96401996601a96801b96a097c00095400295600395801896c01996e01a97001b) // quotient_program + mstore(add(payload, 0x23e0), 0x972097e00295400395601895801996c01a96e01b970020972098000395401895) // quotient_program + mstore(add(payload, 0x2400), 0x601995801a96c01b96e020970021972098201895401995601a95801b96c02096) // quotient_program + mstore(add(payload, 0x2420), 0xe021970022972098401995401a95601b95802096c02196e02297002397209860) // quotient_program + mstore(add(payload, 0x2440), 0x1a95401b95602095802196c02296e023970024972098801b9540209560219580) // quotient_program + mstore(add(payload, 0x2460), 0x2296c02396e024970025972095400898a00998c00a98e01c99001d99201e9940) // quotient_program + mstore(add(payload, 0x2480), 0x1f996095600998a00a98c01c98e01d99001e99201f994052996095800a98a01c) // quotient_program + mstore(add(payload, 0x24a0), 0x98c01d98e01e99001f992052994053996096c01c98a01d98c01e98e01f990052) // quotient_program + mstore(add(payload, 0x24c0), 0x992053994054996096e01d98a01e98c01f98e052990053992054994055996097) // quotient_program + mstore(add(payload, 0x24e0), 0x001e98a01f98c05298e053990054992055994056996097201f98a05298c05398) // quotient_program + mstore(add(payload, 0x2500), 0xe05499005599205699405799600894a00994c00a94e01c95001d95201e95a01f) // quotient_program + mstore(add(payload, 0x2520), 0x95c02697401f99600d000b060001210397a0630000000b2b6494a06594c06694) // quotient_program + mstore(add(payload, 0x2540), 0xe06795406895606995806795e06896006996202d97402e97606a94a094a06594) // quotient_program + mstore(add(payload, 0x2560), 0xa094c06694a094e06b94c094c06c94c095c06c94e095a06d94e095c06c950095) // quotient_program + mstore(add(payload, 0x2580), 0x206d950095a06e950095c06f952095206e952095a070952095c067954095e068) // quotient_program + mstore(add(payload, 0x25a0), 0x95409600699540962068956095e0699560960071956096a069958095e0719580) // quotient_program + mstore(add(payload, 0x25c0), 0x968072958096a07395a095a07495a095c07595c095c071960097207196209700) // quotient_program + mstore(add(payload, 0x25e0), 0x729620972071964096e07296409700769640972071966096c072966096e07696) // quotient_program + mstore(add(payload, 0x2600), 0x609700779660972072968096c076968096e0779680970078968097207696a096) // quotient_program + mstore(add(payload, 0x2620), 0xc07796a096e07896a097007996a097200d000b070000210397a07a0308000215) // quotient_program + mstore(add(payload, 0x2640), 0x6494a06594c06694e07b95007c95206795406895606995807d95a07e95c06795) // quotient_program + mstore(add(payload, 0x2660), 0xe06896006996207f96408096608196808296a07f96c08096e081970082972094) // quotient_program + mstore(add(payload, 0x2680), 0xa06a94a06594c06694e07b95007c95207d95a07e95c095406795e06896006996) // quotient_program + mstore(add(payload, 0x26a0), 0x207f96408096608196808296a095606895e06996007f96208096408196608296) // quotient_program + mstore(add(payload, 0x26c0), 0x808396a095806995e07f96008096208196408296608396808496a096c07f95e0) // quotient_program + mstore(add(payload, 0x26e0), 0x8096008196208296408396608496808596a096e08095e0819600829620839640) // quotient_program + mstore(add(payload, 0x2700), 0x8496608596808696a097008195e08296008396208496408596608696808796a0) // quotient_program + mstore(add(payload, 0x2720), 0x97208295e08396008496208596408696608796808896a01597401697806b94c0) // quotient_program + mstore(add(payload, 0x2740), 0x94c07b94c094e07c94c095007d94c095207e94c095a08994c095c08a94e094e0) // quotient_program + mstore(add(payload, 0x2760), 0x7d94e095007e94e095208994e095a08b94e095c08c9500950089950095208b95) // quotient_program + mstore(add(payload, 0x2780), 0x0095a08d950095c08e952095208d952095a08f952095c09095a095a09195a095) // quotient_program + mstore(add(payload, 0x27a0), 0xc09295c095c00d000b070001210397a09303080001156494a06594c06694e094) // quotient_program + mstore(add(payload, 0x27c0), 0x95009595206795406895606995809695a09795c06795e0689600699620989640) // quotient_program + mstore(add(payload, 0x27e0), 0x9996609a96809b96a09896c09996e09a97009b972094a06a94a06594c06694e0) // quotient_program + mstore(add(payload, 0x2800), 0x9495009595209695a09795c095406795e06896006996209896409996609a9680) // quotient_program + mstore(add(payload, 0x2820), 0x9b96a095606895e06996009896209996409a96609b96809c96a095806995e098) // quotient_program + mstore(add(payload, 0x2840), 0x96009996209a96409b96609c96809d96a096c09895e09996009a96209b96409c) // quotient_program + mstore(add(payload, 0x2860), 0x96609d96809e96a096e09995e09a96009b96209c96409d96609e96809f96a097) // quotient_program + mstore(add(payload, 0x2880), 0x009a95e09b96009c96209d96409e96609f9680a096a097209b95e09c96009d96) // quotient_program + mstore(add(payload, 0x28a0), 0x209e96409f9660a09680a196a02697406b94c094c09494c094e09594c0950096) // quotient_program + mstore(add(payload, 0x28c0), 0x94c095209794c095a0a294c095c0a394e094e09694e095009794e09520a294e0) // quotient_program + mstore(add(payload, 0x28e0), 0x95a0a494e095c0a595009500a295009520a4950095a0a6950095c0a795209520) // quotient_program + mstore(add(payload, 0x2900), 0xa6952095a0a8952095c0a995a095a0aa95a095c0ab95c095c00d000b07000121) // quotient_program + mstore(add(payload, 0x2920), 0x0397a0ac0000000e100094a00294c00394e06795406895606995800095e00296) // quotient_program + mstore(add(payload, 0x2940), 0x000396202d97402e97600097c00297e003980008954095406895409560699540) // quotient_program + mstore(add(payload, 0x2960), 0x95800a956095607195609720719580970072958097207196c096e07296c09700) // quotient_program + mstore(add(payload, 0x2980), 0x7696c097203096e096e07696e097007796e09720329700970078970097203697) // quotient_program + mstore(add(payload, 0x29a0), 0x2097200d000b080000210397a0ad04010002150094a00294c00394e004950005) // quotient_program + mstore(add(payload, 0x29c0), 0x95206795406895606995800695a00795c00095e0029600039620049640059660) // quotient_program + mstore(add(payload, 0x29e0), 0x0696800796a07f96c08096e08197008297200097c00297e00398000498200598) // quotient_program + mstore(add(payload, 0x2a00), 0x4006986007988095400895406895606995807f96c08096e08197008297201597) // quotient_program + mstore(add(payload, 0x2a20), 0x401697800a956095607f9560958080956096c081956096e08295609700839560) // quotient_program + mstore(add(payload, 0x2a40), 0x97200c9580958081958096c082958096e0839580970084958097200e96c096c0) // quotient_program + mstore(add(payload, 0x2a60), 0x8396c096e08496c097008596c097204396e096e08596e097008696e097204597) // quotient_program + mstore(add(payload, 0x2a80), 0x009700879700972047972097200d000b080001210397a0ae04010001150094a0) // quotient_program + mstore(add(payload, 0x2aa0), 0x0294c00394e01895001995206795406895606995801a95a01b95c00095e00296) // quotient_program + mstore(add(payload, 0x2ac0), 0x000396201896401996601a96801b96a09896c09996e09a97009b97200097c002) // quotient_program + mstore(add(payload, 0x2ae0), 0x97e00398001898201998401a98601b988095400895406895606995809896c099) // quotient_program + mstore(add(payload, 0x2b00), 0x96e09a97009b97202697400a95609560989560958099956096c09a956096e09b) // quotient_program + mstore(add(payload, 0x2b20), 0x956097009c956097201d958095809a958096c09b958096e09c958097009d9580) // quotient_program + mstore(add(payload, 0x2b40), 0x97201f96c096c09c96c096e09d96c097009e96c097205396e096e09e96e09700) // quotient_program + mstore(add(payload, 0x2b60), 0x9f96e097205597009700a09700972057972097200d000b0800010594a01194a0) // quotient_program + mstore(add(payload, 0x2b80), 0x1194a005950008060d000b0900000594c01194c01194c005952008060d000b09) // quotient_program + mstore(add(payload, 0x2ba0), 0x00010594e01194e01194e00595a008060d000b0900011b00021b000305958008) // quotient_program + mstore(add(payload, 0x2bc0), 0x109aa00594a01194a01195000daf060594c01194c01195200db0060594e01194) // quotient_program + mstore(add(payload, 0x2be0), 0xe01195a00db1060d000b090001191f0000000000000000000000000000000000) // quotient_program + // Fixed-column commitment 0, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2c00), 0x0000000000000000000000000000000016e98a681dca730dcbe651aec5493976) // fixed_comms[0].x_hi + mstore(add(payload, 0x2c20), 0x11944be61932e7d19a63786871335939b0eefa7e32507b68b4ad6fd89a5c1028) // fixed_comms[0].x_lo + mstore(add(payload, 0x2c40), 0x0000000000000000000000000000000010e6ae8d3becb251e60db7d788be7029) // fixed_comms[0].y_hi + mstore(add(payload, 0x2c60), 0x8dbcdfdbb394da2ca9725e07485d6b630569a66ec867aa0d605573ae82d550df) // fixed_comms[0].y_lo + // Fixed-column commitment 1, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2c80), 0x000000000000000000000000000000000fb2ef3076aa1a8068bec7ef35167c1c) // fixed_comms[1].x_hi + mstore(add(payload, 0x2ca0), 0xf1f26a24abaa3b4dc8c6d218021c08429e4c175654b5996eb4de74d17a5c188d) // fixed_comms[1].x_lo + mstore(add(payload, 0x2cc0), 0x00000000000000000000000000000000000f94fa3dcc144ffe6a4ace1de5f956) // fixed_comms[1].y_hi + mstore(add(payload, 0x2ce0), 0x76ad02ffc32c0345767b466f064aec3f1101ecd9eaf91ba5d4c145f792d1285a) // fixed_comms[1].y_lo + // Fixed-column commitment 2, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2d00), 0x0000000000000000000000000000000016909a26057dec4152b5bd207c37f9a8) // fixed_comms[2].x_hi + mstore(add(payload, 0x2d20), 0xc3af2c8cfa63dfd83e34704255392445fae3c47d749e435e0291232659a8caeb) // fixed_comms[2].x_lo + mstore(add(payload, 0x2d40), 0x0000000000000000000000000000000000b9edb176686ec391a3684928dc0842) // fixed_comms[2].y_hi + mstore(add(payload, 0x2d60), 0x80c60eca091b8a3925e31ef1032793f2ef4e31225ab560bf721f17525dd476c8) // fixed_comms[2].y_lo + // Fixed-column commitment 3, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2d80), 0x0000000000000000000000000000000014861e20b064f637d60cc250ff3b8804) // fixed_comms[3].x_hi + mstore(add(payload, 0x2da0), 0x9b9697b5245d031c784398bdf950e99d7968c86e92c4ec1cb3821a69686dfb09) // fixed_comms[3].x_lo + mstore(add(payload, 0x2dc0), 0x000000000000000000000000000000000c117b524f84a06b024a40003e1c3aab) // fixed_comms[3].y_hi + mstore(add(payload, 0x2de0), 0x25253a3cf5b53d2f77d1f9328a9c0b32bea4f591eb470dbb599b5fbdfa7df46b) // fixed_comms[3].y_lo + // Fixed-column commitment 4, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2e00), 0x000000000000000000000000000000001420bb2d8c182dda6c982eb820e035f6) // fixed_comms[4].x_hi + mstore(add(payload, 0x2e20), 0xa4a5c6c69376dfda17ac5588d34a0b313800b8d18ecd818f3e4e2671589ec691) // fixed_comms[4].x_lo + mstore(add(payload, 0x2e40), 0x0000000000000000000000000000000000855eb6333071d5bb90a0351dbf958c) // fixed_comms[4].y_hi + mstore(add(payload, 0x2e60), 0x13e5648bcafb55776c78e46031e4620cfda02728e26bb97f067a9a0cdf72aa9f) // fixed_comms[4].y_lo + // Fixed-column commitment 5, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2e80), 0x000000000000000000000000000000000707698990b767903c9aaf028c98b46a) // fixed_comms[5].x_hi + mstore(add(payload, 0x2ea0), 0xc4925e4e30db42105aade1a5cd7dd9b3d2222bd7085071b5c2212709cfcc2d46) // fixed_comms[5].x_lo + mstore(add(payload, 0x2ec0), 0x000000000000000000000000000000000a88a7d4f1148bd3efffaedb5be62777) // fixed_comms[5].y_hi + mstore(add(payload, 0x2ee0), 0x27fad92f1005d28d12ef861b5dcf8b580a1322048d469da5f84b26fa7f8d2e4d) // fixed_comms[5].y_lo + // Fixed-column commitment 6, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2f00), 0x000000000000000000000000000000001495975f8f8c0cb6c54e1163fe045732) // fixed_comms[6].x_hi + mstore(add(payload, 0x2f20), 0x2be3eb4af919cb0eb6126028ebeab81d075dff85aa522989166bf6e01880c1be) // fixed_comms[6].x_lo + mstore(add(payload, 0x2f40), 0x0000000000000000000000000000000018bdd4eef361f4a6d23a6511b48e20fb) // fixed_comms[6].y_hi + mstore(add(payload, 0x2f60), 0x4080898637660f3f3371adfb48585dd2ad89c29cc260d604ce9cd68a31ff427d) // fixed_comms[6].y_lo + // Fixed-column commitment 7, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x2f80), 0x00000000000000000000000000000000166c1cd47b07ac0aee553f0b4277cf37) // fixed_comms[7].x_hi + mstore(add(payload, 0x2fa0), 0xb4822422a23ea90486c60a5b3b9052dff113041d51718304e781a5a7bd93bdc2) // fixed_comms[7].x_lo + mstore(add(payload, 0x2fc0), 0x00000000000000000000000000000000016c91bfb3ce38ba23df779d69abeaa4) // fixed_comms[7].y_hi + mstore(add(payload, 0x2fe0), 0x8aaa142a59667ce4eabfaf36c5453af4aaec6e596ad923693a2a7d1483c2fd20) // fixed_comms[7].y_lo + // Fixed-column commitment 8, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3000), 0x000000000000000000000000000000001262dde7fc4aeca256572897f5d601ef) // fixed_comms[8].x_hi + mstore(add(payload, 0x3020), 0x5b8b5586b8593d43cbcd11d2fd80fe2a2214269e5479b5d976cf507d5262c273) // fixed_comms[8].x_lo + mstore(add(payload, 0x3040), 0x0000000000000000000000000000000012bd810c9b8eaaf899f161e2d8c26762) // fixed_comms[8].y_hi + mstore(add(payload, 0x3060), 0xc3f299919bfa2b2809a027cc9bcac1799ef2d48b11403ce47a5341f7307d2cd4) // fixed_comms[8].y_lo + // Fixed-column commitment 9, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3080), 0x00000000000000000000000000000000096d06362268ed80162468c5e664e2bb) // fixed_comms[9].x_hi + mstore(add(payload, 0x30a0), 0x1a1a3b60a6c419cb6edb470b646e9ed4e021686e93f82d0c3ee2448368fe81be) // fixed_comms[9].x_lo + mstore(add(payload, 0x30c0), 0x0000000000000000000000000000000015ceb98a36a3576ad6db1163a0a98d2a) // fixed_comms[9].y_hi + mstore(add(payload, 0x30e0), 0x0535a28ca330834bb07c6682d32b20cd8171f4c0d9f063d768b6e3185b6ae44e) // fixed_comms[9].y_lo + // Fixed-column commitment 10, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3100), 0x0000000000000000000000000000000019731a2f803950b978082f57fdfffb38) // fixed_comms[10].x_hi + mstore(add(payload, 0x3120), 0x0de3c6bef55492c30e409d77ac9acb523958dea2856b12cb4e87608584ab499a) // fixed_comms[10].x_lo + mstore(add(payload, 0x3140), 0x0000000000000000000000000000000005f057d8c91984630d36a0df6c2ebb07) // fixed_comms[10].y_hi + mstore(add(payload, 0x3160), 0x3881713eb294d8cdf7bb0374cb722b7bb5080101a54638216ab4886f0276a475) // fixed_comms[10].y_lo + // Fixed-column commitment 11, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3180), 0x0000000000000000000000000000000004d63c6ffcf22c5a45cbc67e299c2b3d) // fixed_comms[11].x_hi + mstore(add(payload, 0x31a0), 0xcfd98199c525785d127006aca0f82774876a94e120b9bde61b9227ee96dc6841) // fixed_comms[11].x_lo + mstore(add(payload, 0x31c0), 0x0000000000000000000000000000000002385e4a7b9ab571a73f07d9574152e5) // fixed_comms[11].y_hi + mstore(add(payload, 0x31e0), 0xa45de038325c587523413d98b5feb6d2071c293abbeab53bf941d76b264f3369) // fixed_comms[11].y_lo + // Fixed-column commitment 12, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3200), 0x000000000000000000000000000000000eea9ee53f693242e50678f2ac4a5053) // fixed_comms[12].x_hi + mstore(add(payload, 0x3220), 0x6bc4aa60deae574c46e3cf56a8d14af17d603f8c222728fb09300eee5c5b326b) // fixed_comms[12].x_lo + mstore(add(payload, 0x3240), 0x0000000000000000000000000000000014aa3a043bb1340472428a1756ad82bb) // fixed_comms[12].y_hi + mstore(add(payload, 0x3260), 0x49fa2fee5ff945799006e851969947efb6351c7642b2683deb31537ea6209643) // fixed_comms[12].y_lo + // Fixed-column commitment 13, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3280), 0x000000000000000000000000000000000b7028c202ee5b6a74d6ccfe5990dc98) // fixed_comms[13].x_hi + mstore(add(payload, 0x32a0), 0x4381d45aa22ccb019d848829dd72d16143e5af158899fcc009d5ca17c97c3414) // fixed_comms[13].x_lo + mstore(add(payload, 0x32c0), 0x0000000000000000000000000000000006c6ca7119058dbb54d5302f125f9cef) // fixed_comms[13].y_hi + mstore(add(payload, 0x32e0), 0xdc6b68957c7b970427a4b5a3bd0aa7ff5fa033687afdc2a4e150a408b0a5f4d2) // fixed_comms[13].y_lo + // Fixed-column commitment 14, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3300), 0x0000000000000000000000000000000008a540ee790774da565637ec30ced988) // fixed_comms[14].x_hi + mstore(add(payload, 0x3320), 0x563c90d314d01996465fa3f893d5fb010b617a4edcabc5c8f8141584792a0067) // fixed_comms[14].x_lo + mstore(add(payload, 0x3340), 0x00000000000000000000000000000000071e81991f7c8f62be7df37cd485cb5e) // fixed_comms[14].y_hi + mstore(add(payload, 0x3360), 0x1f941d942cc15f814350867bcdf1521ef6e7e76b6928827688b0d3d14a6cf1a3) // fixed_comms[14].y_lo + // Fixed-column commitment 15, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3380), 0x0000000000000000000000000000000005a4ed142d6c05e535a91ff8244ca5cb) // fixed_comms[15].x_hi + mstore(add(payload, 0x33a0), 0xdda66355e156c19ea759a1f5aecd1225e16cb3f18397a66b055a2918a2a74d54) // fixed_comms[15].x_lo + mstore(add(payload, 0x33c0), 0x000000000000000000000000000000000ce5eb9724134cce6feb1c0ff1b66ab3) // fixed_comms[15].y_hi + mstore(add(payload, 0x33e0), 0x5e79b2b4087200f1d9a76ee4a2b3b5424266da9d518653e9391e9a94196f1d7f) // fixed_comms[15].y_lo + // Fixed-column commitment 16, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3400), 0x0000000000000000000000000000000009b07c1df13d348c1ed7cb560fcdd682) // fixed_comms[16].x_hi + mstore(add(payload, 0x3420), 0x55221aadb75410fa4526857c95cea6bf3a0efa98e31898de56ea7ff30f7c514a) // fixed_comms[16].x_lo + mstore(add(payload, 0x3440), 0x0000000000000000000000000000000011b2d20f8b12527aba78064c809753e2) // fixed_comms[16].y_hi + mstore(add(payload, 0x3460), 0x21cebde9bb41aa67abae156e3a179655388e51211ba5614fb696a7f0cc212560) // fixed_comms[16].y_lo + // Fixed-column commitment 17, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3480), 0x0000000000000000000000000000000010ce280498c90ca7501427caf6bcd6d9) // fixed_comms[17].x_hi + mstore(add(payload, 0x34a0), 0xb4987380f8d9e8ef710b81034a460c8da32362568e1e9fd6b2cb5297805c98ad) // fixed_comms[17].x_lo + mstore(add(payload, 0x34c0), 0x000000000000000000000000000000000292967ae2e91be4d3555cddb84538dd) // fixed_comms[17].y_hi + mstore(add(payload, 0x34e0), 0x0edcb077ce0626f751bee2673ff6b24c182fd2ae99a0cd83881a81a3facfcb6e) // fixed_comms[17].y_lo + // Fixed-column commitment 18, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3500), 0x0000000000000000000000000000000017cf661d855e771f61cab52918bc03e5) // fixed_comms[18].x_hi + mstore(add(payload, 0x3520), 0x329761bebc6704041c0430c64bc2d589210a4250f57e9dec51a807d4d6926f31) // fixed_comms[18].x_lo + mstore(add(payload, 0x3540), 0x000000000000000000000000000000000f50cacb58bf9101f3d7926b2e26675d) // fixed_comms[18].y_hi + mstore(add(payload, 0x3560), 0x036c7cf34f335946dfc1d1a6e148de3da13cabfe8e7507f10baca11e07231413) // fixed_comms[18].y_lo + // Fixed-column commitment 19, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3580), 0x0000000000000000000000000000000016caf868eda0aeb753fd8ec3bee96cbe) // fixed_comms[19].x_hi + mstore(add(payload, 0x35a0), 0x3217eade0bae8e908b6cfe1f144518cbaec5bbbbc6e6641d82702b3dd5997985) // fixed_comms[19].x_lo + mstore(add(payload, 0x35c0), 0x000000000000000000000000000000000a35da0b540c40574d639e726b9c7e6d) // fixed_comms[19].y_hi + mstore(add(payload, 0x35e0), 0xb45e6cbefb614f14a1721bdbca2dfcb20aaeb64926032522ba3de23420dc1d87) // fixed_comms[19].y_lo + // Fixed-column commitment 20, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3600), 0x000000000000000000000000000000000b7f1691be78a38c579c2ac6bef18d54) // fixed_comms[20].x_hi + mstore(add(payload, 0x3620), 0xf2d63e634590931fda91cce7af80f7c589cc37a98870e127c93e059ff020df07) // fixed_comms[20].x_lo + mstore(add(payload, 0x3640), 0x0000000000000000000000000000000012582699af0164d4335252425d0251c4) // fixed_comms[20].y_hi + mstore(add(payload, 0x3660), 0xa1af4e259a3f65f0065b2b05127538a8434de1326c4920ee45da346bbbc7d293) // fixed_comms[20].y_lo + // Fixed-column commitment 21, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3680), 0x000000000000000000000000000000000e004abc0ac243147c480dca32186a34) // fixed_comms[21].x_hi + mstore(add(payload, 0x36a0), 0x78642aaafe922adde54e02ae93479fa0ae45b75ed0229c2f358a0473d17c09a5) // fixed_comms[21].x_lo + mstore(add(payload, 0x36c0), 0x0000000000000000000000000000000013e0dac8358fff676678a7b7d854aee3) // fixed_comms[21].y_hi + mstore(add(payload, 0x36e0), 0x3fda118cfb39d426f3418966ed06283adbd804fbe8cf51fd89c2f5c410919f48) // fixed_comms[21].y_lo + // Fixed-column commitment 22, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3700), 0x00000000000000000000000000000000168c0d6883d0d5b67d3a5944ee0616a3) // fixed_comms[22].x_hi + mstore(add(payload, 0x3720), 0xce7ac537685a26b678bab6b1b50115c32a5f90b6ca56cb8560a40964840893ce) // fixed_comms[22].x_lo + mstore(add(payload, 0x3740), 0x00000000000000000000000000000000197d467f656fc49a1c5119d7cd826cc2) // fixed_comms[22].y_hi + mstore(add(payload, 0x3760), 0xbe6345c67f108e7e44f8ebfcebea5366fdee85d69947c8f74f623165dc1029b5) // fixed_comms[22].y_lo + // Fixed-column commitment 23, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3780), 0x00000000000000000000000000000000133f78d1d5ad537d358f421d115bdb2f) // fixed_comms[23].x_hi + mstore(add(payload, 0x37a0), 0x44eff93e623017fb4a06006877bd8504b8b614c6c877d73de7cdc2c2e69a8998) // fixed_comms[23].x_lo + mstore(add(payload, 0x37c0), 0x00000000000000000000000000000000067b406b38e3895daddca2f64de3ee21) // fixed_comms[23].y_hi + mstore(add(payload, 0x37e0), 0x5c7961f1d2f1904f7e2aa60bc20559610c868810183505527e28eea6089d6bf8) // fixed_comms[23].y_lo + // Fixed-column commitment 24, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3800), 0x000000000000000000000000000000000bd679e559d6bf9f498928a3074a0709) // fixed_comms[24].x_hi + mstore(add(payload, 0x3820), 0x23c4cf1d5823b59316852fd6653fe003a4a2da9ba443e4640993524c79a6f56d) // fixed_comms[24].x_lo + mstore(add(payload, 0x3840), 0x000000000000000000000000000000000821077b092335b9f70fb79a424cca13) // fixed_comms[24].y_hi + mstore(add(payload, 0x3860), 0x4e823d96a4fdeaf4154ab7c5ef023844695a10e9bfdf314847ccaa40205f980a) // fixed_comms[24].y_lo + // Fixed-column commitment 25, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3880), 0x00000000000000000000000000000000041f7c88095a1bbf5759d88a7d109853) // fixed_comms[25].x_hi + mstore(add(payload, 0x38a0), 0x4b1661f3b1b9012866e17c7550a5b52861ac915beba38767c7aeb04c1df330ac) // fixed_comms[25].x_lo + mstore(add(payload, 0x38c0), 0x000000000000000000000000000000000f2e8cc95c27bf0235efa88fa70bb3eb) // fixed_comms[25].y_hi + mstore(add(payload, 0x38e0), 0x59a5ef2602a9b70c5af7b0db755f10a8e0ffc380b61a580c3300fda8d6077854) // fixed_comms[25].y_lo + // Fixed-column commitment 26, stored as one + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3900), 0x000000000000000000000000000000001983933b9a69c960c9eda1f5942af11a) // fixed_comms[26].x_hi + mstore(add(payload, 0x3920), 0x22de694e7c8b2a5f8d920b50d6c90aca30d149ace5ff9feebf84a136abdfa9f4) // fixed_comms[26].x_lo + mstore(add(payload, 0x3940), 0x00000000000000000000000000000000162f68d07437f5dd432392f6a244044d) // fixed_comms[26].y_hi + mstore(add(payload, 0x3960), 0x4605b4457384fb65577df046c667a744f851fbdb0e8751aaeeb334bf32fbd3e0) // fixed_comms[26].y_lo + // Permutation commitment 0, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3980), 0x0000000000000000000000000000000014669fe860d72f984d085b33bfffa399) // permutation_comms[0].x_hi + mstore(add(payload, 0x39a0), 0x12a9b0d0acb1ff8fd119eee9d0870eab9324293b88df67b9e1ebd180db262c72) // permutation_comms[0].x_lo + mstore(add(payload, 0x39c0), 0x000000000000000000000000000000000f3292f0f7b5707fe0874c56fdae2e88) // permutation_comms[0].y_hi + mstore(add(payload, 0x39e0), 0x2a2ba44cc1a14f736d255905838f8438fb8dbfd65ad84bcbca748a749c58955e) // permutation_comms[0].y_lo + // Permutation commitment 1, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3a00), 0x000000000000000000000000000000000ee7e9d684b203f28dd5424f3a511695) // permutation_comms[1].x_hi + mstore(add(payload, 0x3a20), 0xff88a9d696976adca8c54262e0f553756ec45b8e103453f8c18a07c3abcef5fd) // permutation_comms[1].x_lo + mstore(add(payload, 0x3a40), 0x00000000000000000000000000000000177029152e262c258c987bfbeeee14c8) // permutation_comms[1].y_hi + mstore(add(payload, 0x3a60), 0xe945c114efada4abdeff8610bea0cf199ef3f375cf15f6b437de67f3379c5801) // permutation_comms[1].y_lo + // Permutation commitment 2, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3a80), 0x000000000000000000000000000000000e2ead1ec1acc411940e7bea5d0f2114) // permutation_comms[2].x_hi + mstore(add(payload, 0x3aa0), 0x6b39327e36ebab84013dd8f5c2b3f8b576a125db88ff28db45a79a28df414b66) // permutation_comms[2].x_lo + mstore(add(payload, 0x3ac0), 0x00000000000000000000000000000000199b4ea033c01e1d83627c054ba6f2dc) // permutation_comms[2].y_hi + mstore(add(payload, 0x3ae0), 0x2a61b1277d07469ad65c413a1970c9cf179bedfc4cf2f61c1ee8ca5e4a01ab90) // permutation_comms[2].y_lo + // Permutation commitment 3, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3b00), 0x0000000000000000000000000000000011af8f4e99c419588e05414f664ae8f6) // permutation_comms[3].x_hi + mstore(add(payload, 0x3b20), 0xdded731799d69d8f5a72c124a5c6df33153e15767000ef54648a8dd23687140a) // permutation_comms[3].x_lo + mstore(add(payload, 0x3b40), 0x0000000000000000000000000000000017ba99d9323e45fe54ec6ac4daaad8d3) // permutation_comms[3].y_hi + mstore(add(payload, 0x3b60), 0x0c0dbc53d3f5f54c05667eb0e4f28da8973657bf62915fcdfc551698f56aa574) // permutation_comms[3].y_lo + // Permutation commitment 4, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3b80), 0x000000000000000000000000000000000ed68fa806cd3171cb061afebf65799d) // permutation_comms[4].x_hi + mstore(add(payload, 0x3ba0), 0xf9014492a08de2420f1234158d2f1d214b5354b7ad8cfc753d2712e6bb9b20a5) // permutation_comms[4].x_lo + mstore(add(payload, 0x3bc0), 0x0000000000000000000000000000000013f05c6e0393baeec023451082007353) // permutation_comms[4].y_hi + mstore(add(payload, 0x3be0), 0x55211ac5dd3c2267dcf706d18263fa7b7916e44ab1d5309430542f75717187c4) // permutation_comms[4].y_lo + // Permutation commitment 5, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3c00), 0x000000000000000000000000000000000a925e4ad4c2d71f096de0e53baea720) // permutation_comms[5].x_hi + mstore(add(payload, 0x3c20), 0x7a90226e678437d20f2ec9ae630075a12ae88a186bcfba1bc0444e5bc40245e7) // permutation_comms[5].x_lo + mstore(add(payload, 0x3c40), 0x0000000000000000000000000000000006ff1ce2b5e6c2c3cedd7a8465c5ed3a) // permutation_comms[5].y_hi + mstore(add(payload, 0x3c60), 0x72e4496eead09ba2cd0ef56fdd94942ed738b42b71caea3d1bd78b0ae3ec3f96) // permutation_comms[5].y_lo + // Permutation commitment 6, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3c80), 0x000000000000000000000000000000000190af4fe845524352a9f624463ae7ca) // permutation_comms[6].x_hi + mstore(add(payload, 0x3ca0), 0x406fa14f4319031fe5a7630ab2bd66c107d6cb05fa65b98ee0c5f0234c29d189) // permutation_comms[6].x_lo + mstore(add(payload, 0x3cc0), 0x0000000000000000000000000000000013036a5f7dca90b530955bf735da2920) // permutation_comms[6].y_hi + mstore(add(payload, 0x3ce0), 0x29b8759dc69e53d6c830a3bee081165d4d59442dacb50c2a06aded3193e88387) // permutation_comms[6].y_lo + // Permutation commitment 7, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3d00), 0x0000000000000000000000000000000012b6e22b800c1a2dfde554f3312f4b50) // permutation_comms[7].x_hi + mstore(add(payload, 0x3d20), 0x1b75400de7c8c476dcc9fd72dcef80d9384da4e4af8f928a07e4a9fa485da8bc) // permutation_comms[7].x_lo + mstore(add(payload, 0x3d40), 0x00000000000000000000000000000000053da19ebda674bbc95d3d10ad04cb96) // permutation_comms[7].y_hi + mstore(add(payload, 0x3d60), 0xa410aecead2c7059df1e12a1a56117a830f0008a91551cec88f1ad548a379dfe) // permutation_comms[7].y_lo + // Permutation commitment 8, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3d80), 0x0000000000000000000000000000000016038223f354e7d7c75614116c58b1c9) // permutation_comms[8].x_hi + mstore(add(payload, 0x3da0), 0x866a8a39d6b30ea560b263c68da7e78210b2f2a7f9b19564388f2f2370d7cd6a) // permutation_comms[8].x_lo + mstore(add(payload, 0x3dc0), 0x000000000000000000000000000000000bdb1d40f473070bfa087ff87bc9c86d) // permutation_comms[8].y_hi + mstore(add(payload, 0x3de0), 0x0d57398e71f45845ebcd0aa24a9ea829718dfce700af80fab46b51ad73fb58f6) // permutation_comms[8].y_lo + // Permutation commitment 9, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3e00), 0x00000000000000000000000000000000085c579504429306bbf0ec28b07f91a6) // permutation_comms[9].x_hi + mstore(add(payload, 0x3e20), 0x7326fabf9ec866a45ef4d294f390a7c497c7b990a62b602f5999fcaee0e7e9bc) // permutation_comms[9].x_lo + mstore(add(payload, 0x3e40), 0x000000000000000000000000000000000b042dab28398e8c0670ee6f04cca93a) // permutation_comms[9].y_hi + mstore(add(payload, 0x3e60), 0xa41ecc7205c8216513d027b4c1213d0e452c16898d7b2c811027598cf2715dc3) // permutation_comms[9].y_lo + // Permutation commitment 10, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3e80), 0x00000000000000000000000000000000093b6ec41dd5c2455b9d2dca635c472d) // permutation_comms[10].x_hi + mstore(add(payload, 0x3ea0), 0x386496c219c45db9f1a5d96c179d1028694626c4a1845aef384efaaa8053284b) // permutation_comms[10].x_lo + mstore(add(payload, 0x3ec0), 0x000000000000000000000000000000000386d1128886a4a879e8a93aa84b6525) // permutation_comms[10].y_hi + mstore(add(payload, 0x3ee0), 0x1061026f4dae08952816961f9d6fc0b8153784a5961f831b68f1e2ddc567efb7) // permutation_comms[10].y_lo + // Permutation commitment 11, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3f00), 0x0000000000000000000000000000000000b6ed6814a099c4e435e42fc8ee4250) // permutation_comms[11].x_hi + mstore(add(payload, 0x3f20), 0x0e16b88c78069cebe20e4d647c8bb44acd6b9c6712d53a3ebd014e54a1a75e89) // permutation_comms[11].x_lo + mstore(add(payload, 0x3f40), 0x0000000000000000000000000000000001ae3805b59b2f2d53a59e2c19001e97) // permutation_comms[11].y_hi + mstore(add(payload, 0x3f60), 0xaace57ac2ba8b58550a740e897ba6278e409291e5c87466a01edd7cbe3b99f88) // permutation_comms[11].y_lo + // Permutation commitment 12, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x3f80), 0x000000000000000000000000000000000ca3af5a65ec51141f694db29736dd57) // permutation_comms[12].x_hi + mstore(add(payload, 0x3fa0), 0xa1cdbfa79f45aa80a6cdda8de3d51c4b16c95361b412c6f47e132eae7b69f7f7) // permutation_comms[12].x_lo + mstore(add(payload, 0x3fc0), 0x0000000000000000000000000000000018dc16284e57fccd755058b259b913c7) // permutation_comms[12].y_hi + mstore(add(payload, 0x3fe0), 0x7a54da0d0e206b7906878fd3e994ad04c66f48e19cd982d5651f99b48b992064) // permutation_comms[12].y_lo + // Permutation commitment 13, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4000), 0x00000000000000000000000000000000197560c82bf47486bee750469d626ce4) // permutation_comms[13].x_hi + mstore(add(payload, 0x4020), 0x2d40ce7489ff61a64a0eb55e6672926a40f06348ab575f926fbe4d87b672fcd7) // permutation_comms[13].x_lo + mstore(add(payload, 0x4040), 0x0000000000000000000000000000000007b4f39a579a01d4f01f4ab9bc841a7d) // permutation_comms[13].y_hi + mstore(add(payload, 0x4060), 0xe70541d6b79945e9569fd59388739f5b4ce57794044d287d7b0b58e35198caab) // permutation_comms[13].y_lo + // Permutation commitment 14, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4080), 0x0000000000000000000000000000000004e1b539a3a123291eaa8c85be3395e7) // permutation_comms[14].x_hi + mstore(add(payload, 0x40a0), 0x9c6845178d995313daf42680692210e1ec79278ec8b6e2da1d6f289609100404) // permutation_comms[14].x_lo + mstore(add(payload, 0x40c0), 0x0000000000000000000000000000000010d56b62afe35890b4f98b946daa8936) // permutation_comms[14].y_hi + mstore(add(payload, 0x40e0), 0xd83e014595585e2892e21bfc5e86f84d404b435dc1a689539d74773aba55dad4) // permutation_comms[14].y_lo + // Permutation commitment 15, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4100), 0x000000000000000000000000000000000f227bcaef796cabf0a93289fb8321e9) // permutation_comms[15].x_hi + mstore(add(payload, 0x4120), 0x1d43880fa5fcebab73c77c6bb406cc5e9386a81774635b5a6144b75ef10cd994) // permutation_comms[15].x_lo + mstore(add(payload, 0x4140), 0x000000000000000000000000000000000a849d95d37e501cc6272f325f88d677) // permutation_comms[15].y_hi + mstore(add(payload, 0x4160), 0xb25be83ce57eb9c32d382613930bbc0ef7ec349d44ac74a9383c111efcb73783) // permutation_comms[15].y_lo + // Permutation commitment 16, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4180), 0x000000000000000000000000000000000b77ada25b279742ec75d2014583ad79) // permutation_comms[16].x_hi + mstore(add(payload, 0x41a0), 0x427bfa99ab8a941648e1a11084fec2a6fdb8192f498cf3e272a5e786597273a1) // permutation_comms[16].x_lo + mstore(add(payload, 0x41c0), 0x00000000000000000000000000000000056788c108fd40583888b8771a70d3f6) // permutation_comms[16].y_hi + mstore(add(payload, 0x41e0), 0x364290f978d1fc201c0a76561a4c1cdde7643f7d76448dad2586c3eac2e34104) // permutation_comms[16].y_lo + // Permutation commitment 17, also a 4-word + // EIP-2537 padded uncompressed G1 slot. + mstore(add(payload, 0x4200), 0x0000000000000000000000000000000002ecf71916494d46153fdd513ead1917) // permutation_comms[17].x_hi + mstore(add(payload, 0x4220), 0x06401b48839713727c920f4591fe09ac3e8f4140ef1cd9e39b0d0bbe0b5b87d4) // permutation_comms[17].x_lo + mstore(add(payload, 0x4240), 0x000000000000000000000000000000000e7bab8cc3c714f06826c8f3695dc63c) // permutation_comms[17].y_hi + mstore(add(payload, 0x4260), 0xe7e931ff0dbe009cbb84fef0b99124905d90cdd6f5356a7ee7673548f6f0692c) // permutation_comms[17].y_lo + + // Return exactly the INVALID prefix plus the generated payload. The + // linked verifier pins this byte length and the resulting codehash. + return(runtime, 0x4281) + } + } +} \ No newline at end of file diff --git a/proofs/solidity-verifier/fixtures/moonlight-wrap/README.md b/proofs/solidity-verifier/fixtures/moonlight-wrap/README.md new file mode 100644 index 000000000..0eaeca341 --- /dev/null +++ b/proofs/solidity-verifier/fixtures/moonlight-wrap/README.md @@ -0,0 +1,69 @@ +# Moonlight Wrap point_pair Replay Fixture + +Pre-rendered artifacts for the `point_pair` accumulator arm of +`tests/ivc_accumulator_replay.rs`. The IVC fixture next door covers +`AccumulatorEncoding::new` (explicit lhs/rhs scalars); this one covers +`AccumulatorEncoding::point_pair`, whose +`expected_acc_has_carried_scalars = false` template arms were otherwise only +ever compiled, never executed against a real proof. + +This is a single-contract render, so there is no `Halo2QuotientEvaluator.sol`; +the replay deploys the verifier with the verifying key alone. + +## Provenance + +| Field | Value | +| --- | --- | +| Source commit | `9bf8c51` | +| Rendered by | Moonlight `wrap_circuit_composes_two_fold_children_from_four_dummy_fold_proofs` | +| Moonlight revision | `origin/codex/wrap-bench-cherry-picks` | +| Accumulator | `AccumulatorEncoding::point_pair(offset=11, num_limbs=7, num_limb_bits=56)` | +| Public inputs | 19 (accumulator occupies the trailing 8 words) | +| Verified on-chain | yes, 1,277,811 gas under revm Prague | +| Native/Solidity trace | 244 trace points matched | + +## Regenerating + +Needs a Moonlight checkout on `origin/codex/wrap-bench-cherry-picks` placed as +a **sibling of this repository** -- its `aggregation/Cargo.toml` refers to +`../../midfall/proofs/solidity-verifier` by relative path, so the bench renders +with local codegen. + +That branch pins the Midfall crates to a fixed git revision, which would link a +second copy of `midnight-proofs` alongside the path-dependency one. Add a patch +to the Moonlight workspace `Cargo.toml` to unify them (do not commit it): + +```toml +[patch."https://github.com/EYBlockchain/midfall.git"] +midnight-circuits = { path = "/path/to/midfall/circuits" } +midnight-curves = { path = "/path/to/midfall/curves" } +midnight-proofs = { path = "/path/to/midfall/proofs" } +midnight-zk-stdlib = { path = "/path/to/midfall/zk_stdlib" } +blake2b_halo2 = { path = "/path/to/midfall/third_party/blake2b_halo2" } +``` + +Then, from the Moonlight checkout (needs `midnight-srs-2p19`/`2p20` in +`SRS_DIR`; takes several minutes): + +```bash +MOONLIGHT_RUN_WRAP_SOLIDITY_BENCH=1 \ +MOONLIGHT_RUN_WRAP_SOLIDITY_TRACE=1 \ +MOONLIGHT_WRAP_SOLIDITY_DUMP_DIR=/path/to/midfall/proofs/solidity-verifier/target/moonlight-wrap-solidity-dump \ +SRS_DIR=/path/to/midfall/zk_stdlib/examples/assets \ + cargo test --release --lib \ + wrap_circuit_composes_two_fold_children_from_four_dummy_fold_proofs \ + -- --ignored --nocapture + +cp target/moonlight-wrap-solidity-dump/{Halo2Verifier.sol,Halo2VerifyingKey.sol,\ +calldata.bin} fixtures/moonlight-wrap/ +``` + +Then update the source commit above. + +## Staleness + +A snapshot of the codegen that produced it. The artifacts are self-consistent, +so the replay keeps passing after a codegen change -- it just stops exercising +current output. Tracked by the commit stamp rather than an assertion, because +detecting drift means re-rendering, which needs the SRS and the Moonlight +checkout again. diff --git a/proofs/solidity-verifier/fixtures/moonlight-wrap/calldata.bin b/proofs/solidity-verifier/fixtures/moonlight-wrap/calldata.bin new file mode 100644 index 000000000..b7b53e880 Binary files /dev/null and b/proofs/solidity-verifier/fixtures/moonlight-wrap/calldata.bin differ diff --git a/proofs/solidity-verifier/src/api.rs b/proofs/solidity-verifier/src/api.rs index bbe20325a..e2213b9eb 100644 --- a/proofs/solidity-verifier/src/api.rs +++ b/proofs/solidity-verifier/src/api.rs @@ -389,6 +389,13 @@ pub enum RepackError { /// Hex encoding of the failing 48-byte compressed G1. bytes_hex: String, }, + /// One proof scalar is not a canonical Fr element (`>= r`). + NonCanonicalScalar { + /// Byte offset of the failing scalar in the native proof. + offset: usize, + /// Big-endian hex encoding of the failing 32-byte scalar. + bytes_hex: String, + }, } impl fmt::Display for RepackError { @@ -410,6 +417,11 @@ impl fmt::Display for RepackError { "invalid compressed G1 at compressed[{offset}..{}]: bytes = 0x{bytes_hex}", offset + layout::G1_COMPRESSED_BYTES ), + Self::NonCanonicalScalar { offset, bytes_hex } => write!( + f, + "non-canonical Fr scalar at compressed[{offset}..{}]: bytes = 0x{bytes_hex}", + offset + layout::WORD_BYTES + ), } } } @@ -446,6 +458,38 @@ pub enum GeneratorError { num_instances: usize, reason: &'static str, }, + /// The accumulator's fixed-base scalar tail asks for more bases than this + /// verifying key can supply. + /// + /// The tail must be empty (fully collapsed accumulator), or cover `-G` plus + /// every permutation commitment plus at most one scalar per fixed + /// commitment. + AccumulatorFixedBaseTailMismatch { + /// Tail scalars implied by `num_instances` and the encoding. + fixed_scalar_count: usize, + /// Smallest non-empty tail this verifying key supports (`-G` plus every + /// permutation commitment). + min_fixed_scalar_count: usize, + /// Largest tail this verifying key supports. + max_fixed_scalar_count: usize, + /// Fixed commitments in the verifying key. + num_fixed_comms: usize, + /// Permutation commitments in the verifying key. + num_permutation_comms: usize, + }, + /// The public instance count pushes the Lagrange batch-inversion input run + /// past the verifier memory that is already live when Lagrange executes. + /// + /// The run is written in place from `X_N_MPTR` and is + /// `num_instances + abs(rotation_last) + 1` words long. + TooManyInstances { + /// Requested public instance count. + num_instances: usize, + /// Largest public instance count this constraint system can support. + max_num_instances: usize, + /// Negative-row denominators contributed by `rotation_last`. + rotation_last_words: usize, + }, /// Internal render/layout planning failed before Solidity was emitted. Planning { /// Planning stage. @@ -498,6 +542,31 @@ impl fmt::Display for GeneratorError { f, "unsupported accumulator encoding: offset={offset}, num_limbs={num_limbs}, num_limb_bits={num_limb_bits}, num_instances={num_instances}; {reason}" ), + Self::AccumulatorFixedBaseTailMismatch { + fixed_scalar_count, + min_fixed_scalar_count, + max_fixed_scalar_count, + num_fixed_comms, + num_permutation_comms, + } => write!( + f, + "accumulator fixed-base scalar tail of {fixed_scalar_count} is not supported by \ + this verifying key ({num_fixed_comms} fixed, {num_permutation_comms} permutation \ + commitments): expected 0 (fully collapsed) or \ + {min_fixed_scalar_count}..={max_fixed_scalar_count}; adjust num_instances or the \ + accumulator offset" + ), + Self::TooManyInstances { + num_instances, + max_num_instances, + rotation_last_words, + } => write!( + f, + "public instance count {num_instances} is too large: the Lagrange \ + batch-inversion run writes num_instances + {rotation_last_words} + 1 words from \ + X_N_MPTR and would overrun verifier memory that is live at that point; \ + the maximum for this constraint system is {max_num_instances}" + ), Self::Planning { stage, message } => { write!(f, "generator planning failed during {stage}: {message}") } diff --git a/proofs/solidity-verifier/src/builder/api.rs b/proofs/solidity-verifier/src/builder/api.rs index 497a59f81..7afc789f9 100644 --- a/proofs/solidity-verifier/src/builder/api.rs +++ b/proofs/solidity-verifier/src/builder/api.rs @@ -6,6 +6,7 @@ //! stable caller-facing knobs before lower-level generation starts. use super::*; +use crate::lowering::layout::{memory::batch_invert_input_words, theta_window}; impl<'a> SolidityGenerator<'a> { /// Number of committed instance columns supported by the generated ABI. @@ -48,7 +49,39 @@ impl<'a> SolidityGenerator<'a> { )?; if let Some(acc_encoding) = config.accumulator { acc_encoding.validate_for_num_instances(config.num_instances)?; + // The fixed-base scalar tail is derived from the *instance* length, + // but the bases it multiplies come from this verifying key: `-G`, + // then `fixed_comm_mptr + i * G1_BYTES` per fixed base, then the + // permutation commitments. Two tail lengths break that mapping: + // + // * Too long: the generated fixed-base pointers run past the end + // of the fixed-commitment region, silently aliasing permutation + // commitments and then arbitrary VK payload words as G1 bases. + // * Shorter than `-G` plus the permutation commitments: the base + // count underflows in the artifact emitter. + // + // A tail inside the range is left alone -- it covers a prefix of + // the fixed commitments, which keeps every pointer in region. + let fixed_scalar_count = acc_encoding.fixed_scalar_count(config.num_instances)?; + let num_fixed_comms = vk.fixed_commitments().len(); + let num_permutation_comms = vk.permutation().commitments().len(); + let min_fixed_scalar_count = 1 + num_permutation_comms; + let max_fixed_scalar_count = min_fixed_scalar_count + num_fixed_comms; + if fixed_scalar_count != 0 + && !(min_fixed_scalar_count..=max_fixed_scalar_count).contains(&fixed_scalar_count) + { + return Err(GeneratorError::AccumulatorFixedBaseTailMismatch { + fixed_scalar_count, + min_fixed_scalar_count, + max_fixed_scalar_count, + num_fixed_comms, + num_permutation_comms, + }); + } } + // Non-committed instance evaluations are reconstructed once from the + // public-input polynomial at the current rotation. Reject rotated + // instance queries until that path is keyed by `(column, rotation)`. if let Some((column, rotation)) = vk .cs() .instance_queries() @@ -61,7 +94,34 @@ impl<'a> SolidityGenerator<'a> { }); } - let meta = ConstraintSystemMeta::new(vk.cs(), config.num_committed_instances); + // Fallible: `ProtocolPlan::validate` rejects a range of unsupported + // constraint-system shapes -- an advice column that is absorbed but + // never opened by a PCS query being the most reachable authoring + // mistake. Panicking here would break this constructor's contract of + // reporting such shapes as a typed error. + let meta = ConstraintSystemMeta::try_new(vk.cs(), config.num_committed_instances).map_err( + |message| GeneratorError::Planning { + stage: "constraint system", + message, + }, + )?; + + // Bound the public instance count here rather than leaving it to + // `VerifierMemoryLayout::validate`. The Lagrange block writes its + // batch-inversion input run in place from `X_N_MPTR`, and that run + // grows with `num_instances`; the layout does catch an overrun, but + // only much later and phrased in theta-word offsets. `rotation_last` + // is only known once `meta` is built, hence the position of this check. + let run_words = batch_invert_input_words(&meta, config.num_instances); + if run_words > theta_window::LAGRANGE_RUN_CAP_WORDS { + let rotation_last_words = meta.rotation_last.unsigned_abs() as usize; + return Err(GeneratorError::TooManyInstances { + num_instances: config.num_instances, + max_num_instances: theta_window::LAGRANGE_RUN_CAP_WORDS + .saturating_sub(rotation_last_words + 1), + rotation_last_words, + }); + } Ok(Self { params, diff --git a/proofs/solidity-verifier/src/evm.rs b/proofs/solidity-verifier/src/evm.rs index 5a3ae7fec..7f929275e 100644 --- a/proofs/solidity-verifier/src/evm.rs +++ b/proofs/solidity-verifier/src/evm.rs @@ -231,6 +231,78 @@ pub(crate) mod test { } } + /// Read the free-memory-pointer initializer from a runtime bytecode prefix. + /// + /// solc opens every contract by storing the initial free-memory pointer to + /// slot `0x40`. With `memoryguard` active -- which the verifier's + /// `assembly ("memory-safe")` annotation enables -- that value is raised + /// above `0x80` to reserve via-IR stack-to-memory spill slots, so the + /// returned value is the top of the region solc has claimed for itself. + /// + /// Recognises the two prologue encodings solc emits: + /// `PUSH1 v` / `PUSH2 v` followed by `PUSH1 0x40 MSTORE`, allowing an + /// optional `DUP1` between them (used when the value is reused). + /// Returns `None` if the prefix does not match, so callers can distinguish + /// "no reservation found" from "reservation is 0x80". + #[must_use] + pub fn runtime_free_memory_pointer_init(runtime: &[u8]) -> Option { + // PUSH1 v | PUSH2 v_hi v_lo + let (value, mut i) = match *runtime.first()? { + 0x60 => (usize::from(*runtime.get(1)?), 2), + 0x61 => ( + (usize::from(*runtime.get(1)?) << 8) | usize::from(*runtime.get(2)?), + 3, + ), + _ => return None, + }; + // Optional DUP1 when solc reuses the value. + if runtime.get(i) == Some(&0x80) { + i += 1; + } + // PUSH1 0x40 MSTORE + if runtime.get(i) == Some(&0x60) + && runtime.get(i + 1) == Some(&0x40) + && runtime.get(i + 2) == Some(&0x52) + { + return Some(value); + } + None + } + + /// Compile `solidity` and return the deployed runtime bytecode. + /// + /// # Panics + /// Panics under the same conditions as [`compile_solidity`]. + pub fn compile_solidity_runtime(solidity: impl AsRef<[u8]>) -> Vec { + let solc = require_pinned_solc(); + let mut process = Command::new(&solc) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("--bin-runtime") + .arg("--optimize") + .arg("--optimize-runs") + .arg(DEFAULT_OPTIMIZE_RUNS.to_string()) + .arg("--via-ir") + .arg("--evm-version") + .arg("cancun") + .arg("--no-cbor-metadata") + .arg("-") + .spawn() + .unwrap_or_else(|err| panic!("Failed to spawn process with command '{solc}':\n{err}")); + process.stdin.take().unwrap().write_all(solidity.as_ref()).unwrap(); + let output = process.wait_with_output().unwrap(); + let stdout = str::from_utf8(&output.stdout).unwrap(); + let marker = "Binary of the runtime part:"; + let start = stdout.find(marker).unwrap_or_else(|| { + panic!( + "Runtime compilation fails:\n{}", + str::from_utf8(&output.stderr).unwrap() + ) + }) + marker.len(); + hex::decode(stdout[start..].trim()).expect("solc runtime output should be hex") + } + /// Extract creation bytecode from solc's text `--bin` output. fn find_binary(stdout: &str) -> Option> { let start = stdout.find("Binary:")? + 8; diff --git a/proofs/solidity-verifier/src/lib.rs b/proofs/solidity-verifier/src/lib.rs index 011af0a58..6cf2f30cf 100644 --- a/proofs/solidity-verifier/src/lib.rs +++ b/proofs/solidity-verifier/src/lib.rs @@ -58,8 +58,9 @@ pub const OUTER_SINGLE_H_COMMITMENT_ENABLED: bool = cfg!(feature = "outer-single #[cfg(feature = "evm")] pub use evm::test::{ - compile_solidity, compile_solidity_with_runs, pinned_solc_available, revm, solc_version, - CallOutcome, Evm, ALLOW_UNPINNED_SOLC_ENV, DEFAULT_OPTIMIZE_RUNS, PINNED_SOLC_VERSION, + compile_solidity, compile_solidity_runtime, compile_solidity_with_runs, pinned_solc_available, + revm, runtime_free_memory_pointer_init, solc_version, CallOutcome, Evm, + ALLOW_UNPINNED_SOLC_ENV, DEFAULT_OPTIMIZE_RUNS, PINNED_SOLC_VERSION, }; /// Test-only helper that exposes the internal BLS12-381 G1 to EIP-2537 diff --git a/proofs/solidity-verifier/src/lowering/abi/proof.rs b/proofs/solidity-verifier/src/lowering/abi/proof.rs index 3e085d1cb..4a8d061d4 100644 --- a/proofs/solidity-verifier/src/lowering/abi/proof.rs +++ b/proofs/solidity-verifier/src/lowering/abi/proof.rs @@ -307,21 +307,53 @@ pub(crate) struct TranscriptBufferLayout { impl TranscriptBufferLayout { /// Derive transcript-buffer bounds from proof calldata shape and instances. - pub(crate) fn from_proof_layout(proof: &ProofCalldataLayout, num_instances: usize) -> Self { + /// + /// `phase_challenge_counts[i]` is the number of Fiat-Shamir challenges the + /// native schedule squeezes for user phase `i` (i.e. `num_user_challenges`). + /// It selects which advice commitments share the pre-first-squeeze run: a + /// phase that owns no challenge does not trigger a squeeze, so its advices + /// accumulate into the same run as the following phase's. An empty slice is + /// treated as "no phase owns a challenge", which sums every advice phase as + /// a safe upper bound. + pub(crate) fn from_proof_layout( + proof: &ProofCalldataLayout, + num_instances: usize, + phase_challenge_counts: &[usize], + ) -> Self { let word_absorb = layout::transcript::WORD_ABSORB_BYTES; let g1_absorb = layout::transcript::G1_ABSORB_BYTES; let squeeze_cushion = layout::transcript::POST_SQUEEZE_CUSHION_WORDS * WORD_BYTES; - let phase_1_advices = - proof.advice_phases.first().map(|section| section.item_count).unwrap_or(0); + // Advices absorbed before the first challenge squeeze. The native + // verifier squeezes a challenge only for phases that own one, and the + // unconditional `theta` squeeze follows every user phase, so all advice + // phases up to and INCLUDING the first challenge-bearing phase are + // absorbed into a single streaming run before the first squeeze. + // Counting only the first phase under-sized the buffer for the valid + // "advice in an early phase, challenge in a later phase" shape + // (e.g. a SecondPhase RLC column) and let the G1 absorb loop overrun + // `VK_MPTR`; see `plan_allows_challenge_phase_beyond_advice_phases`. + let mut pre_squeeze_advices = 0usize; + for (phase, section) in proof.advice_phases.iter().enumerate() { + pre_squeeze_advices += section.item_count; + if phase_challenge_counts.get(phase).copied().unwrap_or(0) > 0 { + break; + } + } let initial_run_bytes = word_absorb + g1_absorb + word_absorb + num_instances * word_absorb - + phase_1_advices * g1_absorb + + pre_squeeze_advices * g1_absorb + squeeze_cushion; - let eval_run_bytes = proof.quotient_limbs.byte_len + // Absorb bytes, so the quotient limbs are counted at the transcript's + // G1 absorb width -- NOT `quotient_limbs.byte_len`, which is a + // calldata length. The two happen to be equal today, but they have + // diverged before (a compressed 48/49-byte transcript encoding against + // padded calldata), and that divergence is what overruns the keccak + // buffer into `VK_MPTR`. + let eval_run_bytes = proof.quotient_limbs.item_count * g1_absorb + proof.evals.byte_len + proof.q_evals.byte_len + squeeze_cushion; @@ -464,7 +496,7 @@ mod tests { fn transcript_layout_matches_current_conservative_bound() { let protocol = protocol_shape(vec![64], vec![], 0, 0, 2); let proof = ProofCalldataLayout::from_protocol(&protocol, 0, 10, 3); - let transcript = TranscriptBufferLayout::from_proof_layout(&proof, 0); + let transcript = TranscriptBufferLayout::from_proof_layout(&proof, 0, &[1]); let first_phase_run = 32 + 128 + 32 + 64 * 128 + 32 * 32; assert!(transcript.words * WORD_BYTES >= first_phase_run); @@ -473,4 +505,37 @@ mod tests { 2 * G1_BYTES + (10 + 3) * WORD_BYTES + 32 * WORD_BYTES ); } + + #[test] + fn transcript_layout_covers_multi_phase_advice_before_first_squeeze() { + // Valid shape (see `plan_allows_challenge_phase_beyond_advice_phases`): + // phase 0 has advice but no challenge, so both phases' advices land in + // the same pre-first-squeeze run alongside a large public-input block. + let protocol = protocol_shape(vec![1, 9], vec![], 0, 0, 1); + let proof = ProofCalldataLayout::from_protocol(&protocol, 0, 12, 2); + let num_instances = 86; + let transcript = TranscriptBufferLayout::from_proof_layout(&proof, num_instances, &[0, 1]); + + let word = layout::transcript::WORD_ABSORB_BYTES; + let g1 = layout::transcript::G1_ABSORB_BYTES; + let cushion = layout::transcript::POST_SQUEEZE_CUSHION_WORDS * WORD_BYTES; + + // Bytes actually absorbed before the first squeeze: vk_digest, + // committed_pi, num_instances length word, instance words, then BOTH + // phases' advices (phase 0 owns no challenge). + let true_initial_run = word + g1 + word + num_instances * word + (1 + 9) * g1; + assert!( + transcript.words * WORD_BYTES >= true_initial_run, + "reserved {} bytes < true pre-squeeze run {}", + transcript.words * WORD_BYTES, + true_initial_run + ); + assert_eq!(transcript.initial_run_bytes, true_initial_run + cushion); + + // The old bound counted only phase 0's advice and therefore under-sized + // the buffer below the true run — confirm the fix was load-bearing. + let phase0_only_run = word + g1 + word + num_instances * word + 1 * g1; + assert!(phase0_only_run < true_initial_run); + assert!(phase0_only_run + cushion < true_initial_run); + } } diff --git a/proofs/solidity-verifier/src/lowering/artifacts.rs b/proofs/solidity-verifier/src/lowering/artifacts.rs index 4dbf611ed..fa61fabf8 100644 --- a/proofs/solidity-verifier/src/lowering/artifacts.rs +++ b/proofs/solidity-verifier/src/lowering/artifacts.rs @@ -157,6 +157,19 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { let num_fixed_bases = fixed_scalar_count .checked_sub(1 + num_perm_bases) .expect("accumulator fixed scalar count is smaller than -G + permutations"); + // Each generated base below is `fixed_comm_mptr + i * 0x80`, + // so more bases than the VK has fixed commitments would + // point past the region into the permutation commitments + // and then the rest of the VK payload. SolidityGenerator:: + // try_new rejects this shape with a typed error; fail + // closed here too, since this is where the out-of-region + // pointers would actually be emitted. + assert!( + num_fixed_bases <= plan.vk.fixed_comms.len(), + "accumulator fixed-base count {num_fixed_bases} exceeds the VK \ + fixed-commitment region ({} commitments)", + plan.vk.fixed_comms.len() + ); std::iter::once(("-G".to_string(), g1_base_mptr_byte, true)) .chain((0..num_fixed_bases).map(|i| { @@ -175,7 +188,10 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { })) .collect::>() }; - debug_assert_eq!( + // Holds by construction of `bases` above; kept as a cheap + // guard on the one-to-one correspondence with the scalars the + // generated verifier reads from calldata. + assert_eq!( bases.len(), fixed_scalar_count, "accumulator fixed-base scalar tail must match generated bases" diff --git a/proofs/solidity-verifier/src/lowering/calldata.rs b/proofs/solidity-verifier/src/lowering/calldata.rs index 0a3a462bc..3bafa14af 100644 --- a/proofs/solidity-verifier/src/lowering/calldata.rs +++ b/proofs/solidity-verifier/src/lowering/calldata.rs @@ -6,6 +6,7 @@ //! big-endian scalar words. This module performs that deterministic boundary //! conversion. +use ff::PrimeField; use group::GroupEncoding; use midnight_curves::{Fq, G1Affine}; @@ -84,11 +85,26 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { out.extend_from_slice(&y_be[16..48]); Ok(()) }; - let push_scalar_be = |cursor: &mut usize, out: &mut Vec| { - out.extend_from_slice(&scalar_le_to_be_word( - &compressed[*cursor..*cursor + layout::WORD_BYTES], - )); + let push_scalar_be = |cursor: &mut usize, out: &mut Vec| -> Result<(), RepackError> { + let le = &compressed[*cursor..*cursor + layout::WORD_BYTES]; + // The generated verifier reverts on any eval or q_eval word that + // is not a canonical Fr element, so reject it here rather than + // handing the caller calldata that is guaranteed to revert + // on-chain. Mirrors the G1 validation in `push_g1`. + let mut arr = [0u8; layout::WORD_BYTES]; + arr.copy_from_slice(le); + let repr = ::Repr::from(arr); + if Option::::from(Fq::from_repr(repr)).is_none() { + let mut be = arr; + be.reverse(); + return Err(RepackError::NonCanonicalScalar { + offset: *cursor, + bytes_hex: hex::encode(be), + }); + } + out.extend_from_slice(&scalar_le_to_be_word(le)); *cursor += layout::WORD_BYTES; + Ok(()) }; for &n in &plan.g1_groups { for _ in 0..n { @@ -98,13 +114,13 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { // evals (Fr 32-byte LE in native proof) -> BE calldata words // (incl. dummy slots). for _ in 0..plan.num_evals { - push_scalar_be(&mut cursor, &mut out); + push_scalar_be(&mut cursor, &mut out)?; } // f_com push_g1(&mut cursor, &mut out)?; // q_evals (Fr 32-byte LE in native proof) -> BE calldata words. for _ in 0..plan.num_point_sets { - push_scalar_be(&mut cursor, &mut out); + push_scalar_be(&mut cursor, &mut out)?; } // pi push_g1(&mut cursor, &mut out)?; diff --git a/proofs/solidity-verifier/src/lowering/encoding/mod.rs b/proofs/solidity-verifier/src/lowering/encoding/mod.rs index 5ddf9bcdf..a2ec9c7f4 100644 --- a/proofs/solidity-verifier/src/lowering/encoding/mod.rs +++ b/proofs/solidity-verifier/src/lowering/encoding/mod.rs @@ -13,6 +13,7 @@ use std::{ }; use ff::PrimeField; +use group::prime::PrimeCurveAffine; use itertools::{izip, Itertools}; use midnight_curves::{Coordinates, CurveAffine, Fq, G1Affine, G2Affine}; use midnight_proofs::plonk::{Any, Column, ConstraintSystem}; @@ -137,10 +138,22 @@ impl ConstraintSystemMeta { /// verifier *reads* from the proof transcript (vs. computes locally /// via Lagrange interpolation). For the poseidon example this is 0; /// for IVC-style fixtures with committed inputs it would be > 0. + /// + /// Panics if the constraint system is outside the supported verifier shape. + /// Use [`ConstraintSystemMeta::try_new`] on fallible paths. pub(crate) fn new(cs: &ConstraintSystem, nb_committed_instances: usize) -> Self { - let protocol = ProtocolPlan::from_constraint_system(cs, nb_committed_instances); + Self::try_new(cs, nb_committed_instances) + .unwrap_or_else(|err| panic!("invalid protocol plan: {err}")) + } - Self { + /// Fallible counterpart of [`ConstraintSystemMeta::new`]. + pub(crate) fn try_new( + cs: &ConstraintSystem, + nb_committed_instances: usize, + ) -> Result { + let protocol = ProtocolPlan::try_from_constraint_system(cs, nb_committed_instances)?; + + Ok(Self { protocol: protocol.clone(), num_fixeds: protocol.num_fixeds, permutation_columns: protocol.permutation_columns.clone(), @@ -163,7 +176,7 @@ impl ConstraintSystemMeta { advice_indices: protocol.advice_indices.clone(), challenge_indices: protocol.challenge_indices.clone(), rotation_last: protocol.rotation_last, - } + }) } /// Check legacy scalar fields against the typed protocol plan. @@ -249,7 +262,9 @@ pub(crate) struct Data { /// User challenge words. pub(crate) challenges: Vec, - /// Locally-computed non-committed instance evaluation. + /// Locally-computed non-committed instance evaluation at `Rotation::cur()`. + /// `SolidityGenerator::try_new` rejects rotated instance queries, so this + /// stays a single word instead of a `(column, rotation)` map. pub(crate) instance_eval: Word, /// Per-(committed-instance-column, rotation): the calldata word for /// that committed instance evaluation. Empty when @@ -273,8 +288,7 @@ pub(crate) struct Data { pub(crate) computed_quotient_eval: Word, /// Word offset (in the verifier's static memory map) of the start of - /// the per-category EIP-2537-padded commitment region. See the - /// `KNOWN BUG` block in `Data::new` for the layout convention. + /// the per-category EIP-2537-padded commitment region. pub(crate) comms_mptr_base: Ptr, /// Memory base of the decoded-evals buffer (Optimisation H3). The /// transcript-side `evaluations` loop spills the decoded scalar value to @@ -285,7 +299,9 @@ pub(crate) struct Data { /// fewer-point-sets path. Empty when the feature is disabled. The /// dummy buffer is laid out immediately after the main reversed- /// evals buffer; `dummy_eval_words[i]` points at - /// `REVERSED_EVALS_MPTR + (num_evals + i) * 0x20`. The transcript + /// `REVERSED_EVALS_MPTR + (num_main_evals + i) * 0x20`, where + /// `num_main_evals` is the eval count *before* dummies are appended + /// (not `meta.num_evals`, which already includes them). The transcript /// loop reads `num_dummy_evals` extra Fr scalars after the main /// eval block and spills them into this buffer the same way the /// main loop does. @@ -306,6 +322,27 @@ impl Data { // BLS12-381 G1 commitments occupy 4 words (EIP-2537 padded), so the // stride between consecutive points is 4 instead of the BN254-era 2. let fixed_comm_mptr = memory.vk_mptr + vk.constants.len(); + // The fixed-commitment region is consumed as meta.num_fixeds slots + // (EcPoint::range(fixed_comm_mptr).take(meta.num_fixeds) below), but the + // permutation base is advanced past vk.fixed_comms.len() of them. If the + // two counts ever diverge, the fixed and permutation commitment regions + // would silently overlap or leave a gap, so require them equal here. + assert_eq!( + vk.fixed_comms.len(), + meta.num_fixeds, + "VK fixed commitment count must match constraint-system fixed count" + ); + // Same argument for the permutation region: `permutation_comms` below + // zips `meta.permutation_columns` against `EcPoint::range(...)`, and + // `izip!` silently truncates to the shorter side. A VK carrying fewer + // commitments than the constraint system has permutation columns would + // therefore drop columns from the permutation argument rather than + // fail. + assert_eq!( + vk.permutation_comms.len(), + meta.permutation_columns.len(), + "VK permutation commitment count must match constraint-system permutation column count" + ); let permutation_comm_mptr = fixed_comm_mptr + G1_WORDS * vk.fixed_comms.len(); let challenge_mptr = memory.challenge_mptr; let theta_mptr = memory.theta_mptr; @@ -579,11 +616,17 @@ pub(crate) enum Location { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum Value { - /// Byte offset stored as signed so that the BLS code-gen can compute - /// `ptr - N` even when `N` exceeds the original offset (the result - /// only ever appears as `ptr_end` in `lt(ptr_end, ptr)` style loops - /// where any value strictly less than the smallest visited address is - /// acceptable). + /// Byte offset. Stored as signed for historical reasons; concrete + /// offsets must be non-negative and `Display` panics otherwise. + /// + /// A negative offset has no correct rendering here. It used to be + /// emitted as `sub(0, N)`, which in EVM unsigned arithmetic is + /// `2^256 - N` -- the *largest* representable word. The previous + /// comment claimed such a value was safe because it "only ever appears + /// as `ptr_end` in `lt(ptr_end, ptr)` style loops where any value + /// strictly less than the smallest visited address is acceptable", but + /// `lt` is unsigned, so it is larger than every address and such a loop + /// runs zero iterations. Integer(isize), /// A symbolic Yul identifier `name`, with an optional byte-offset that /// will be rendered as `add(name, 0xNN)` (or just `name` when zero). @@ -594,7 +637,19 @@ impl Value { /// Return the concrete offset, panicking for symbolic identifiers. pub(crate) fn as_usize(&self) -> usize { match self { - Value::Integer(int) => *int as usize, + Value::Integer(int) => { + // `Integer` is signed only so BLS pointer math can produce + // negative `ptr_end` sentinels for `lt(ptr_end, ptr)` loops. A + // concrete memory address is never negative; `*int as usize` on + // a negative value would wrap to a near-2^word offset and + // silently corrupt every derived mload/mstore, so fail closed. + assert!( + *int >= 0, + "Value::as_usize on negative offset {int}: signed offsets are \ + only valid as lt()-loop sentinels, not concrete addresses" + ); + *int as usize + } Value::Identifier(..) => unreachable!(), } } @@ -636,7 +691,9 @@ impl Display for Value { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Value::Integer(int) if *int >= 0 => write!(f, "{}", fmt_hex(*int)), - Value::Integer(int) => write!(f, "sub(0, {})", fmt_hex(-*int)), + Value::Integer(int) => { + panic!("negative pointer offset {int} has no correct Yul rendering") + } Value::Identifier(ident, 0) => write!(f, "{ident}"), Value::Identifier(ident, off) if *off > 0 => { write!(f, "add({ident}, {})", fmt_hex(*off)) @@ -648,29 +705,29 @@ impl Display for Value { } } -impl Add for Value { - /// Pointer-expression value after word-wise addition. +impl Sub for Value { + /// Pointer-expression value after word-wise subtraction. type Output = Value; - /// Advance by `rhs` EVM words. - fn add(self, rhs: usize) -> Self::Output { + /// Move backward by `rhs` EVM words. + fn sub(self, rhs: usize) -> Self::Output { match self { - Value::Integer(int) => Value::Integer(int + (rhs as isize) * WORD_BYTES as isize), + Value::Integer(int) => Value::Integer(int - (rhs as isize) * WORD_BYTES as isize), Value::Identifier(name, off) => { - Value::Identifier(name, off + (rhs as isize) * WORD_BYTES as isize) + Value::Identifier(name, off - (rhs as isize) * WORD_BYTES as isize) } } } } -impl Sub for Value { - /// Pointer-expression value after word-wise subtraction. +impl Add for Value { + /// Pointer-expression value after word-wise addition. type Output = Value; - /// Move backward by `rhs` EVM words. - fn sub(self, rhs: usize) -> Self::Output { + /// Advance by `rhs` EVM words. + fn add(self, rhs: usize) -> Self::Output { match self { - Value::Integer(int) => Value::Integer(int - (rhs as isize) * WORD_BYTES as isize), + Value::Integer(int) => Value::Integer(int + (rhs as isize) * WORD_BYTES as isize), Value::Identifier(name, off) => { - Value::Identifier(name, off - (rhs as isize) * WORD_BYTES as isize) + Value::Identifier(name, off + (rhs as isize) * WORD_BYTES as isize) } } } @@ -719,16 +776,6 @@ impl Display for Ptr { } } -impl Add for Ptr { - /// Pointer with the same location and advanced value. - type Output = Ptr; - /// Advance by `rhs` EVM words while preserving location. - fn add(mut self, rhs: usize) -> Self::Output { - self.value = self.value + rhs; - self - } -} - impl Sub for Ptr { /// Pointer with the same location and rewound value. type Output = Ptr; @@ -739,6 +786,16 @@ impl Sub for Ptr { } } +impl Add for Ptr { + /// Pointer with the same location and advanced value. + type Output = Ptr; + /// Advance by `rhs` EVM words while preserving location. + fn add(mut self, rhs: usize) -> Self::Output { + self.value = self.value + rhs; + self + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct Word(Ptr); @@ -795,7 +852,7 @@ impl EcPoint { /// Infinite iterator of consecutive padded G1 points. pub(crate) fn range(base: impl Into) -> impl Iterator { let base = base.into().base; - (0..).map(move |idx| EcPoint::new(base + 4 * idx)) + (0..).map(move |idx| EcPoint::new(base + G1_WORDS * idx)) } /// Return the pointer to the first word. @@ -832,8 +889,18 @@ fn fp48_be_to_hi_lo(be: &[u8]) -> (U256, U256) { /// big-endian and split (hi=top 16 bytes padded into u256, lo=bottom 32 /// bytes). pub(crate) fn g1_to_u256s(ec_point: impl Borrow) -> [U256; 4] { - let Some(coords) = Option::>::from(ec_point.borrow().coordinates()) - else { + let point = ec_point.borrow(); + let Some(coords) = Option::>::from(point.coordinates()) else { + // `coordinates()` returns None for the identity *and* for any off-curve + // point. The all-zero words below are the EIP-2537 encoding of the point + // at infinity, so silently returning them for an off-curve input would + // bake an identity commitment into the verifier. Only the identity may + // take this path; anything else is a malformed/corrupted point and must + // fail codegen rather than degrade a soundness-critical constant. + assert!( + bool::from(point.is_identity()), + "refusing to EIP-2537 encode an off-curve G1 point as the identity" + ); return [U256::ZERO; 4]; }; let mut x_be = [0u8; BLS_FP_BYTES]; @@ -854,8 +921,16 @@ pub(crate) fn g1_to_u256s(ec_point: impl Borrow) -> [U256; 4] { /// in big-endian per coord. The midnight-curves convention matches: /// each `Fp` coordinate read via `to_repr()` returns LE bytes. pub(crate) fn g2_to_u256s(ec_point: impl Borrow) -> [U256; 8] { - let Some(coords) = Option::>::from(ec_point.borrow().coordinates()) - else { + let point = ec_point.borrow(); + let Some(coords) = Option::>::from(point.coordinates()) else { + // See `g1_to_u256s`: `coordinates()` also returns None for off-curve G2 + // points, whose all-zero encoding would collapse to the identity (e.g. + // a corrupted `s_g2` degenerating the `e(w, -s*G2)` pairing term). Only + // the genuine identity may be encoded as zeros. + assert!( + bool::from(point.is_identity()), + "refusing to EIP-2537 encode an off-curve G2 point as the identity" + ); return [U256::ZERO; 8]; }; diff --git a/proofs/solidity-verifier/src/lowering/kzg/mod.rs b/proofs/solidity-verifier/src/lowering/kzg/mod.rs index 0fb662402..2a607b6d5 100644 --- a/proofs/solidity-verifier/src/lowering/kzg/mod.rs +++ b/proofs/solidity-verifier/src/lowering/kzg/mod.rs @@ -320,6 +320,23 @@ fn construct_intermediate_sets_impl(queries: &[Query]) -> IntermediateSets { // when their evals also agree; otherwise the proof claims the // same polynomial opens to two different values, which is a // protocol bug and must be rejected. + // + // TODO(structural): this dedup and the whole intermediate-set + // grouping compare EcPoint/Word handles by *memory pointer* (derived + // PartialEq), not by runtime value, whereas the midnight-proofs + // prover groups queries by polynomial identity and the verifier by + // commitment value. They agree today only because (a) + // SolidityGenerator supports a single committed-instance column (see + // validate_instance_column_shape in builder/api.rs), and (b) every + // downstream consumer depends only on first-appearance order and + // per-commitment point sets. The assert below also compares eval + // Words by pointer: with >= 2 committed-instance columns sharing + // G1_IDENTITY_MPTR at the same rotation it would panic at codegen + // (their eval Words are distinct memory handles) instead of + // collapsing them, and compute_dummy_queries would silently `skip` + // the same pair. Grouping by runtime value would lift both + // restrictions but is a structural change; the invariant is + // documented here rather than fixed. if let Some(existing_pos) = slot.1.iter().position(|pi| *pi == point_idx) { assert_eq!( slot.2[existing_pos], query.eval, @@ -744,9 +761,17 @@ pub(crate) fn memory_requirements( ) -> PcsMemoryRequirements { let sets = intermediate_sets(meta, data); let n_sets = sets.point_sets.len(); - if n_sets == 0 { - return PcsMemoryRequirements::default(); - } + // Fail closed: zero point sets means the plan carries no PCS queries, which + // would size (and, in `computations`, emit) a verifier with no final + // pairing check. Since zero-initialized PAIRING_{LHS,RHS}_MPTR encode the + // point at infinity, such a verifier accepts any transcript-parseable proof. + // `ProtocolPlan::validate` guarantees at least the Linearization query, so + // this is unreachable; assert it rather than silently return a default. + assert!( + n_sets != 0, + "KZG intermediate-set construction produced zero point sets; refusing to \ + size a verifier with no PCS/pairing check (would be accept-all)" + ); let by_set = commitments_by_set(&sets, n_sets); let commitments_per_set = by_set.iter().map(Vec::len); @@ -819,9 +844,17 @@ pub(crate) fn computations( const TRUNC_MASK_128: &str = "0xffffffffffffffffffffffffffffffff"; let sets = intermediate_sets(meta, data); let n_sets = sets.point_sets.len(); - if n_sets == 0 { - return Vec::new(); - } + // Fail closed: an empty point-set list would emit a verifier with no Block 6, + // so PAIRING_{LHS,RHS}_MPTR stay zero-initialized. Zero memory is the EIP-2537 + // encoding of the BLS12-381 point at infinity, so the final pairing evaluates + // to 1 and the verifier accepts ANY proof with no cryptographic checking. + // `ProtocolPlan::validate` requires the query schedule to end with the + // Linearization query (n_sets >= 1), so reaching here is a generator bug. + assert!( + n_sets != 0, + "KZG intermediate-set construction produced zero point sets; refusing to \ + emit a verifier with no PCS/pairing check (would be accept-all)" + ); // The emitted blocks below adapt the Rust `multi_prepare` flow: // construct/sort point sets, fold q_eval vectors, interpolate at x3, @@ -871,6 +904,26 @@ pub(crate) fn computations( let max_rot = *distinct_rotations.iter().max().unwrap_or(&0); let min_rot = *distinct_rotations.iter().min().unwrap_or(&0); + // Fail closed on pathological rotation magnitudes. This block unrolls one + // `mulmod` per unit step across the entire rotation span (forward to + // max_rot, backward to min_rot), so the emitted line count scales with + // |max_rot| + |min_rot|, NOT with the (separately capped) number of + // distinct rotations. A circuit using a very large rotation would emit + // enough Yul to exceed the EIP-170 24KB runtime-code limit and produce an + // undeployable verifier with no diagnostic from our own validation. Bound + // the walk here; if this ever fires for a legitimate circuit, roll the + // walk into a Yul loop (as Block 2 does for x1 powers) rather than raising + // the cap. The cap is far above any realistic circuit's rotation range. + const MAX_ROTATION_WALK_STEPS: i64 = 4096; + let walk_steps = i64::from(max_rot).max(0) + (-i64::from(min_rot)).max(0); + assert!( + walk_steps <= MAX_ROTATION_WALK_STEPS, + "PCS rotation-point walk would unroll {walk_steps} mulmod steps \ + (max_rot={max_rot}, min_rot={min_rot}), exceeding the \ + {MAX_ROTATION_WALK_STEPS}-step cap; such a verifier would blow the \ + EIP-170 runtime code-size limit (roll the walk into a Yul loop instead)" + ); + let store_rot = |rot: i32| -> Option { distinct_rotations.iter().position(|r| *r == rot).map(|idx| { format!( @@ -1189,7 +1242,7 @@ pub(crate) fn computations( )); lines.push(format!( "trace_point({}, {trace_scratch:#x})", - 40000 + set_idx + trace::PCS_Q_COM_BASE + set_idx as u64 )); continue; } @@ -1272,11 +1325,11 @@ pub(crate) fn computations( )); lines.push(format!( "if iszero(q_com_trace_ok_{set_idx}) {{ mstore(TRACE_U256_MPTR, {}) revert(TRACE_U256_MPTR, {WORD_BYTES:#x}) }}", - 40000 + set_idx + trace::PCS_Q_COM_BASE + set_idx as u64 )); lines.push(format!( "trace_point({}, {trace_scratch:#x})", - 40000 + set_idx + trace::PCS_Q_COM_BASE + set_idx as u64 )); } blocks.push(lines); @@ -1399,11 +1452,27 @@ pub(crate) fn computations( // Soundness: requires every input to be non-zero. dx_j is // non-zero by Fiat-Shamir (x3 is uniform random; the // probability that x3 = p_j for a structured rotation point - // is ~2^-256). lbasis_j is non-zero because the points in a - // set are distinct by construction (`construct_intermediate_sets` - // de-duplicates rotations within each set). Defensive note: - // a malicious prover cannot influence either, so we don't - // need an explicit zero check. + // is ~2^-256). lbasis_j = prod_{k != j} (p_j - p_k) is non-zero + // as long as the rotation points p = x*omega^rot are pairwise + // distinct within the set. `construct_intermediate_sets` + // de-duplicates rotation *values* (i32) per set, and distinct + // values map to distinct points only because the domain order + // n = 2^k exceeds the rotation span for every supported circuit + // (rotations are bounded by the gate/lookup structure, k is + // large). A malicious prover cannot influence either value, so + // no explicit codegen zero check is added here. + // + // Note the failure mode is fail-closed, not silent: if a + // degenerate tiny-domain circuit ever aliased two rotations + // (rot_i == rot_j mod n), the corresponding p_j - p_k would be + // zero, so some lbasis_j and hence the batched product bp_{n-1} + // would be zero, and `scalar_inv` reverts on a zero input + // (AssemblyHelpers.yul). Such a verifier rejects all proofs + // rather than computing a wrong f_eval. + // + // TODO: to surface that misconfiguration at codegen time instead + // of at proof time, thread the domain order n into this emitter + // and assert every point set's rotation span is < n. // // The reference computes lagrange interpolation directly via // full polynomial construction; here we collapse the @@ -1708,7 +1777,12 @@ pub(crate) fn computations( lines.push("// Scale z*pi - vG before the final pairing check".to_string()); lines.push("// pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi)".to_string()); - // PAIRING_LHS = pi (paired against G2_BASE). + // PAIRING_LHS = pi. FinalPairing.yul calls + // ec_pairing(success, PAIRING_RHS_MPTR, PAIRING_LHS_MPTR) with the + // slots swapped, and ec_pairing pairs its first argument against + // G2_BASE and its second against NEG_S_G2_BASE, so pi (PAIRING_LHS) + // is paired against NEG_S_G2_BASE (the [s]_2 side), matching the KZG + // identity e(final_com - v*G + x3*pi, [1]_2) = e(pi, [s]_2). lines.push(format!("mcopy(PAIRING_LHS_MPTR, PI_MPTR, {G1_BYTES:#x})")); // tmp = (-v) * G => load G into planned scratch, scale by (r - v). diff --git a/proofs/solidity-verifier/src/lowering/layout/memory.rs b/proofs/solidity-verifier/src/lowering/layout/memory.rs index 55bc5acb2..5be8d69a1 100644 --- a/proofs/solidity-verifier/src/lowering/layout/memory.rs +++ b/proofs/solidity-verifier/src/lowering/layout/memory.rs @@ -11,7 +11,7 @@ //! historical addresses in the generated verifier, gives each range a name and //! lifetime, and rejects accidental overlap when two live ranges can coexist. //! Intentional scratch reuse is modeled by assigning the same byte range to -//! disjoint `MemoryPhase`s. +//! disjoint `MemoryPhase`s or non-overlapping phase spans. //! //! This is not a packing allocator yet. The first version is deliberately //! conservative: it names the old layout, validates it, and centralizes all @@ -21,13 +21,13 @@ use std::collections::BTreeMap; pub(crate) use crate::lowering::layout::{ - ACC_MSM_MIN_SCRATCH_BYTES, G1ADD_INPUT_BYTES, G1_BYTES, G1_MSM_PAIR_BYTES, G1_WORDS, - LOW_MEMORY_SCRATCH_START, MODEXP_FRAME_BYTES, MODEXP_SCRATCH_BYTES, - PAIRING_STATIC_WORKING_WORDS, PAIRING_TWO_PAIR_BYTES, PCS_PAIRING_SCRATCH_START, - PCS_STATIC_WORKING_WORDS, QUOTIENT_RETURN_BUFFER_START, SOLIDITY_FREE_MEMORY_POINTER_SLOT, - SOLIDITY_RESERVED_MEMORY_BYTES, SOLIDITY_SCRATCH_SPACE_BYTES, SOLIDITY_ZERO_SLOT, - TRANSCRIPT_BUFFER_START, VERIFIER_RETURN_BUFFER_START, VK_CONSTRUCTOR_PAYLOAD_START, - WORD_BYTES, + accumulator::PAIRING_BATCH_HASH_BYTES, ACC_MSM_MIN_SCRATCH_BYTES, G1ADD_INPUT_BYTES, G1_BYTES, + G1_MSM_PAIR_BYTES, G1_WORDS, LOW_MEMORY_SCRATCH_START, MODEXP_FRAME_BYTES, + MODEXP_SCRATCH_BYTES, PAIRING_STATIC_WORKING_WORDS, PAIRING_TWO_PAIR_BYTES, + PCS_PAIRING_SCRATCH_START, PCS_STATIC_WORKING_WORDS, QUOTIENT_RETURN_BUFFER_START, + SOLIDITY_FREE_MEMORY_POINTER_SLOT, SOLIDITY_RESERVED_MEMORY_BYTES, + SOLIDITY_SCRATCH_SPACE_BYTES, SOLIDITY_ZERO_SLOT, TRANSCRIPT_BUFFER_START, + VERIFIER_RETURN_BUFFER_START, VK_CONSTRUCTOR_PAYLOAD_START, WORD_BYTES, }; use crate::lowering::{ encoding::{ConstraintSystemMeta, Ptr}, @@ -36,11 +36,11 @@ use crate::lowering::{ }; /// Accumulator pairing-batch hash frame. /// -/// The template starts this frame at `0x100`, writes a one-word domain tag, -/// then four G1 points: KZG rhs/lhs and accumulator rhs/lhs. The last copy ends -/// at `0x320`, so the registered range is `[0x100, 0x320)`. -const ACCUMULATOR_PAIRING_BATCH_BYTES: usize = - PAIRING_TWO_PAIR_BYTES - G1ADD_INPUT_BYTES + WORD_BYTES; +/// The template starts this frame at `PAIRING_BATCH_PTR` (`0x1000`), writes a +/// one-word domain tag, then four G1 points: KZG rhs/lhs and accumulator +/// rhs/lhs. The last copy ends `0x220` bytes later, so the registered range is +/// `[0x1000, 0x1220)`. +const ACCUMULATOR_PAIRING_BATCH_BYTES: usize = PAIRING_BATCH_HASH_BYTES; // Fixed word offsets from `THETA_MPTR`. // @@ -118,32 +118,47 @@ impl ThetaWindowLayout { } } +/// Verifier execution phases, in the order the generated code runs them. +/// +/// The derived `Ord` is load-bearing: `MemoryLifetime::intersects` compares +/// phases with `<=` to decide whether a `PhaseSpan` covers a `Phase`, so a +/// variant declared out of runtime order makes the arena's overlap validation +/// answer the wrong question. Keep this list in sync with the include order in +/// `templates/contracts/Halo2Verifier.sol` and the call sites it renders. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub(crate) enum MemoryPhase { /// Generated verifying-key constructor return payload. VkConstructorPayload, /// Constructor-only precompile smoke tests. ConstructorSmoke, + /// Public-accumulator MSM input buffer. + /// + /// `validate_public_accumulator` runs from VkLoading.yul, immediately + /// after the VK payload is loaded and *before* the transcript starts. + AccumulatorMsm, /// Streaming Fiat-Shamir buffer before generated VK memory is live. Transcript, - /// Single scalar inversion scratch used by the modexp wrapper. - ScalarInv, /// Batch inversion for Lagrange denominator terms. LagrangeBatchInvert, /// Compact quotient VM temps and stack. QuotientVm, - /// Historical fixed PCS windows rooted at `ROT_POINTS_MPTR`. - PcsFixed, + /// Trace-only linearization-commitment MSM, run after the quotient VM and + /// before the PCS blocks reuse the same scratch band. + LinearizationTrace, /// Source-address table used by the rolled q_eval fold. PcsQEvalSourceTable, /// Optional trace-only q_com MSM materialization. PcsQComTrace, + /// Single scalar inversion scratch used by the modexp wrapper. + /// + /// `scalar_inv` is called from the PCS f_eval interpolation, i.e. after + /// the q_eval source-table fold and before the fused final MSM -- not + /// during transcript absorption. + ScalarInv, /// Fused final PCS MSM input buffer. PcsFinalMsm, /// Low-memory PCS pairing input helpers. PcsPairing, - /// Public-accumulator MSM input buffer. - AccumulatorMsm, /// Public-accumulator pairing-batch hash and two G1 add/MSM frames. AccumulatorPairingBatch, /// Final two-pair KZG pairing frame. @@ -162,6 +177,11 @@ pub(crate) enum MemoryLifetime { /// Region is live only during the named phase. Regions in different phases /// may reuse the same byte range. Phase(MemoryPhase), + /// Region is written in one phase and read through a later phase. + PhaseSpan { + start: MemoryPhase, + end: MemoryPhase, + }, } impl MemoryLifetime { @@ -170,6 +190,20 @@ impl MemoryLifetime { match (self, other) { (Self::Permanent, _) | (_, Self::Permanent) => true, (Self::Phase(lhs), Self::Phase(rhs)) => lhs == rhs, + (Self::Phase(phase), Self::PhaseSpan { start, end }) + | (Self::PhaseSpan { start, end }, Self::Phase(phase)) => { + start <= phase && phase <= end + } + ( + Self::PhaseSpan { + start: lhs_start, + end: lhs_end, + }, + Self::PhaseSpan { + start: rhs_start, + end: rhs_end, + }, + ) => lhs_start <= rhs_end && rhs_start <= lhs_end, } } } @@ -481,11 +515,14 @@ pub(crate) struct VerifierMemoryLayout { pub(crate) trashcan_comms_mptr_base: Ptr, pub(crate) quotient_limb_comms_mptr_base: Ptr, /// First byte after all decompressed proof commitments. Selector - /// accumulators are live here during final linearization/final MSM. + /// accumulators are written by quotient evaluation and read by PCS MSMs. pub(crate) selector_acc_mptr: usize, /// Reuses selector-accumulator bytes during the earlier Lagrange batch /// inversion phase. pub(crate) batch_invert_scratch_mptr: usize, + /// Number of Fr words the generated Lagrange block writes in place at + /// `X_N_MPTR` as the batch-inversion input run. + pub(crate) batch_invert_input_words: usize, /// First quotient VM temporary. Also the canonical PCS scratch base once /// selector accumulators are accounted for. pub(crate) quotient_tmp_mptr: usize, @@ -604,6 +641,7 @@ impl VerifierMemoryLayout { .max() .expect("constructor G1MSM smoke bounds are non-empty"); let batch_invert_len = batch_invert_scratch_bytes(meta, config.num_instances); + let batch_invert_input_words = batch_invert_input_words(meta, config.num_instances); let quotient_return_len = (2 + meta.num_simple_selectors) * WORD_BYTES; let mut arena = MemoryArena::default(); @@ -695,29 +733,41 @@ impl VerifierMemoryLayout { + meta.num_lookups + meta.num_trashcans; let committed_g1s = non_quotient_g1s + meta.num_quotients; + // The rot_points / x1_powers / q_com / q_eval_set windows are NOT + // transient scratch even though they sit in the theta scratch band. + // They are written during PCS preparation and then read across several + // *later* phases: `x1_powers` feeds both the rolled q_eval fold + // (`PcsQEvalSourceTable`) and the fused final MSM (`PcsFinalMsm`), + // while `rot_points`/`q_eval_set` feed the f_eval interpolation. Because + // `MemoryLifetime::intersects` treats two different `Phase`s as never + // co-live, tagging these as a dedicated phase would make + // `validate()` blind to any overlap between them and the PCS scratch + // that consumes them. Nothing ever reuses these byte ranges, so they + // are `Permanent`: the planner must guarantee they never overlap any + // other live region. let rot_points_mptr = Ptr::memory(arena.alloc_fixed( "rot_points", at_theta(theta_windows.rot_points_word), config.pcs.rot_points_words * WORD_BYTES, - MemoryLifetime::Phase(MemoryPhase::PcsFixed), + MemoryLifetime::Permanent, )); let x1_powers_mptr = Ptr::memory(arena.alloc_fixed( "x1_powers", at_theta(theta_windows.x1_powers_word), config.pcs.x1_powers_words * WORD_BYTES, - MemoryLifetime::Phase(MemoryPhase::PcsFixed), + MemoryLifetime::Permanent, )); let q_com_mptr = Ptr::memory(arena.alloc_fixed( "q_com_fixed_window", at_theta(theta_windows.q_com_word), config.pcs.q_com_words * WORD_BYTES, - MemoryLifetime::Phase(MemoryPhase::PcsFixed), + MemoryLifetime::Permanent, )); let q_eval_set_mptr = Ptr::memory(arena.alloc_fixed( "q_eval_set", at_theta(theta_windows.q_eval_set_word), config.pcs.q_eval_set_words * WORD_BYTES, - MemoryLifetime::Phase(MemoryPhase::PcsFixed), + MemoryLifetime::Permanent, )); let q_eval_cptr_mptr = Ptr::memory(arena.alloc_fixed( "q_eval_cptr_slot", @@ -769,7 +819,10 @@ impl VerifierMemoryLayout { comms_mptr_base.value().as_usize(), commitments_len, selector_len, - MemoryLifetime::Phase(MemoryPhase::PcsFinalMsm), + MemoryLifetime::PhaseSpan { + start: MemoryPhase::QuotientVm, + end: MemoryPhase::PcsFinalMsm, + }, ); let batch_invert_scratch_mptr = { let mut scratch = arena.scratch_allocator(selector_acc_mptr); @@ -794,6 +847,29 @@ impl VerifierMemoryLayout { ); (quotient_tmp_mptr, quotient_stack_mptr) }; + // Trace renders expand the linearization terms into their own G1MSM + // frame at `SELECTOR_ACC_MPTR + selector_len`, i.e. starting exactly at + // `quotient_tmp_base`. Register it so the write band is visible to the + // arena and to the trace-log-word placement below; without this the + // only thing keeping it in bounds is the incidental fact that the PCS + // scratch allocated from the same base happens to be at least as long. + let linearization_trace_msm_mptr = { + let mut scratch = arena.scratch_allocator(quotient_tmp_base); + scratch.alloc_phase_scratch( + "linearization_trace_msm", + lin_trace_len, + MemoryPhase::LinearizationTrace, + ) + }; + // QuotientAndLinearization.yul derives the frame base as + // `add(SELECTOR_ACC_MPTR, selector_len)`. Pin the equality so the + // registered region cannot drift away from the address the template + // actually writes. + assert_eq!( + linearization_trace_msm_mptr, + selector_acc_mptr + selector_len, + "linearization trace MSM region must start where the template computes lin_scratch" + ); let pcs_scratch_mptr = quotient_tmp_mptr; let ( pcs_q_eval_source_table_mptr, @@ -844,12 +920,18 @@ impl VerifierMemoryLayout { vk_start + vk.len(), challenge_start + meta.challenge_indices.len() * WORD_BYTES, theta_start + theta_windows.rot_points_word * WORD_BYTES, + rot_points_mptr.value().as_usize() + config.pcs.rot_points_words * WORD_BYTES, + x1_powers_mptr.value().as_usize() + config.pcs.x1_powers_words * WORD_BYTES, + q_com_mptr.value().as_usize() + config.pcs.q_com_words * WORD_BYTES, + q_eval_set_mptr.value().as_usize() + config.pcs.q_eval_set_words * WORD_BYTES, + q_eval_cptr_mptr.value().as_usize() + WORD_BYTES, g1_identity_mptr.value().as_usize() + G1_BYTES, reversed_evals_mptr.value().as_usize() + meta.num_evals * WORD_BYTES, comms_mptr_base.value().as_usize() + commitments_len, selector_acc_mptr + selector_len, batch_invert_scratch_mptr + batch_invert_len, quotient_stack_mptr + quotient_stack_len.max(MODEXP_FRAME_BYTES), + linearization_trace_msm_mptr + lin_trace_len, pcs_q_eval_source_table_mptr + q_eval_source_len, pcs_q_com_trace_scratch_mptr + q_com_trace_len, pcs_final_msm_scratch_mptr + final_msm_len, @@ -925,6 +1007,7 @@ impl VerifierMemoryLayout { quotient_limb_comms_mptr_base, selector_acc_mptr, batch_invert_scratch_mptr, + batch_invert_input_words, quotient_tmp_mptr, quotient_stack_mptr, pcs_q_eval_source_table_mptr, @@ -985,6 +1068,24 @@ impl VerifierMemoryLayout { } } + // The verifier body runs inside `assembly ("memory-safe")`, so solc's + // via-IR stack-to-memory mover reserves spill slots upward from 0x80. + // A generated region below `LOW_MEMORY_SCRATCH_START` could share + // bytes with a live spill slot, and the lifetime model cannot see + // solc's opaque spill liveness -- so enforce disjointness by address. + // `compiled_memoryguard_does_not_overlap_generated_layout` checks the + // complementary bound, `reserved_end <= LOW_MEMORY_SCRATCH_START`, + // against real compiled verifier and quotient-evaluator bytecode. + for region in &self.map.regions { + if region.len != 0 && region.start < LOW_MEMORY_SCRATCH_START { + return Err(format!( + "memory region {} starts at {:#x}, below LOW_MEMORY_SCRATCH_START ({:#x}); \ + it can overlap solc's via-IR stack-to-memory spill window [0x80, reserved_end)", + region.name, region.start, LOW_MEMORY_SCRATCH_START + )); + } + } + let expected_scalar_inv = self.vk_mptr.value().as_usize().saturating_sub(MODEXP_SCRATCH_BYTES); if self.scalar_inv_scratch_mptr != expected_scalar_inv { @@ -1020,6 +1121,37 @@ impl VerifierMemoryLayout { )); } + // The Lagrange block writes its batch-inversion input run in place at + // `X_N_MPTR` (theta word 26). The run intentionally spills past the + // theta band into the PCS fixed windows, which are written only after + // the Lagrange phase, so it cannot be registered as a region without + // fake overlaps; it is capacity-checked here instead. The first bytes + // that are already live at Lagrange time are the q_eval calldata + // cursor (written by the proof parser), the G1 identity slot (must + // stay virgin zero memory), and the decoded proof evaluations. + // + // INVARIANT: this cap is sound only while rot_points, x1_powers and + // q_eval_set are first touched *after* the Lagrange phase. The arena's + // overlap loop cannot enforce that, because the run is not a + // registered region -- registering it would report overlaps that are + // correct by phase ordering. The ordering is pinned instead by + // `rot_points_window_is_written_after_the_lagrange_denominator_run`, + // which asserts it against rendered verifier source. + let lagrange_run_end = ThetaSlot::XN.word() + self.batch_invert_input_words; + let lagrange_run_cap = windows + .q_eval_cptr_word + .min(windows.g1_identity_word) + .min(windows.reversed_evals_word); + if lagrange_run_end > lagrange_run_cap { + return Err(format!( + "Lagrange batch-inversion input run overflows live verifier memory: \ + [X_N_MPTR, X_N_MPTR + {} word(s)) ends at theta word {lagrange_run_end}, \ + but Q_EVAL_CPTR_MPTR/G1_IDENTITY_MPTR/decoded evaluations are live from \ + theta word {lagrange_run_cap}; reduce the public instance count", + self.batch_invert_input_words + )); + } + self.map.validate()?; Ok(()) @@ -1037,25 +1169,31 @@ pub(crate) fn commitment_g1_count(meta: &ConstraintSystemMeta) -> usize { + meta.num_quotients } +/// Number of Fr words the generated Lagrange block writes in place starting at +/// `X_N_MPTR` as the batch-inversion input run. +/// +/// The input range covers: +/// - num_instances public Lagrange denominators, or one fallback word when +/// there are no public instances; +/// - `abs(rotation_last)` negative-row denominators; +/// - x_n - 1. +pub(crate) fn batch_invert_input_words(meta: &ConstraintSystemMeta, num_instances: usize) -> usize { + if num_instances == 0 { + meta.rotation_last.unsigned_abs() as usize + 2 + } else { + num_instances + meta.rotation_last.unsigned_abs() as usize + 1 + } +} + /// Scratch size required by the batched scalar-inversion helper. fn batch_invert_scratch_bytes(meta: &ConstraintSystemMeta, num_instances: usize) -> usize { // The template calls: // batch_invert(X_N_MPTR, mptr_end + WORD_BYTES, scratch, r) // - // The input range covers: - // - num_instances public Lagrange denominators, or one fallback word when - // there are no public instances; - // - `abs(rotation_last)` negative-row denominators; - // - x_n - 1. - // // For N inputs, the batched inversion stores N-2 prefix products and then // overlays one modexp frame at the current prefix pointer. Singletons use // only the frame. - let input_words = if num_instances == 0 { - meta.rotation_last.unsigned_abs() as usize + 2 - } else { - num_instances + meta.rotation_last.unsigned_abs() as usize + 1 - }; + let input_words = batch_invert_input_words(meta, num_instances); MODEXP_FRAME_BYTES + input_words.saturating_sub(2) * WORD_BYTES } @@ -1101,6 +1239,25 @@ mod tests { map.validate().expect("disjoint scratch lifetimes"); } + #[test] + fn phase_spans_cover_each_phase_in_their_range() { + let lifetime = MemoryLifetime::PhaseSpan { + start: MemoryPhase::QuotientVm, + end: MemoryPhase::PcsFinalMsm, + }; + + for phase in [ + MemoryPhase::QuotientVm, + MemoryPhase::PcsQEvalSourceTable, + MemoryPhase::PcsQComTrace, + MemoryPhase::PcsFinalMsm, + ] { + assert!(lifetime.intersects(&MemoryLifetime::Phase(phase))); + } + assert!(!lifetime.intersects(&MemoryLifetime::Phase(MemoryPhase::LagrangeBatchInvert))); + assert!(!lifetime.intersects(&MemoryLifetime::Phase(MemoryPhase::PcsPairing))); + } + #[test] fn unaligned_regions_fail() { let mut map = MemoryMap::default(); @@ -1236,7 +1393,7 @@ mod tests { let layout = VerifierMemoryLayout::new( &meta, &vk, - Ptr::memory(0x1000), + Ptr::memory(0x2000), VerifierMemoryLayoutConfig::default(), ); let theta = layout.theta_mptr.value().as_usize(); @@ -1291,7 +1448,7 @@ mod tests { let layout = VerifierMemoryLayout::new( &meta, &vk, - Ptr::memory(0x1000), + Ptr::memory(0x2000), VerifierMemoryLayoutConfig::default(), ); @@ -1364,13 +1521,36 @@ mod tests { meta.num_simple_selectors * G1_MSM_PAIR_BYTES ); + // These two regions carry different phases, and `MemoryLifetime:: + // intersects` treats distinct phases as never co-live -- so the arena's + // own overlap validation is structurally blind to an overlap here and + // this assertion is the only thing that catches one. + let batch = layout + .map + .region("accumulator_pairing_batch") + .expect("accumulator pairing batch registered"); + let final_pairing = layout + .map + .region("final_pairing_scratch") + .expect("final pairing scratch registered"); + assert!( + batch.start + batch.len <= final_pairing.start, + "accumulator pairing batch [{:#x}, {:#x}) must not overlap final pairing scratch \ + [{:#x}, {:#x}): the last word of the hashed ACC_LHS copy would share bytes with \ + ec_pairing's input frame", + batch.start, + batch.start + batch.len, + final_pairing.start, + final_pairing.start + final_pairing.len, + ); + let scalar_inv = layout .map .region("scalar_inv_scratch") .expect("scalar inversion scratch registered"); assert_eq!( layout.scalar_inv_scratch_mptr, - 0x1000 - MODEXP_SCRATCH_BYTES + 0x2000 - MODEXP_SCRATCH_BYTES ); assert_eq!(scalar_inv.start, layout.scalar_inv_scratch_mptr); assert_eq!(scalar_inv.len, MODEXP_FRAME_BYTES); @@ -1384,17 +1564,17 @@ mod tests { let windows = ThetaWindowLayout::compatibility(); let mut config = VerifierMemoryLayoutConfig::default(); config.pcs.rot_points_words = windows.rot_points_cap_words + 1; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); assert!(layout.validate().unwrap_err().contains("ROT_POINTS_MPTR")); let mut config = VerifierMemoryLayoutConfig::default(); config.pcs.x1_powers_words = windows.x1_powers_cap_words + 1; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); assert!(layout.validate().unwrap_err().contains("X1_POWERS_MPTR")); let mut config = VerifierMemoryLayoutConfig::default(); config.pcs.q_eval_set_words = windows.q_eval_set_cap_words + 1; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); assert!(layout.validate().unwrap_err().contains("Q_EVAL_SET_MPTR")); } @@ -1406,13 +1586,42 @@ mod tests { acc_msm_terms: 4, ..VerifierMemoryLayoutConfig::default() }; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); let region = layout.map.region("accumulator_msm").expect("accumulator MSM region registered"); assert_eq!(region.len, 4 * G1_MSM_PAIR_BYTES); } + #[test] + fn selector_accumulators_are_live_from_quotient_to_final_msm() { + let meta = ConstraintSystemMeta { + num_simple_selectors: 1, + ..ConstraintSystemMeta::default() + }; + let vk = synthetic_vk(); + let layout = VerifierMemoryLayout::new( + &meta, + &vk, + Ptr::memory(0x2000), + VerifierMemoryLayoutConfig::default(), + ); + let selector = layout + .map + .region("selector_accumulators") + .expect("selector accumulators registered"); + + assert_eq!( + selector.lifetime, + MemoryLifetime::PhaseSpan { + start: MemoryPhase::QuotientVm, + end: MemoryPhase::PcsFinalMsm, + } + ); + assert_eq!(layout.batch_invert_scratch_mptr, layout.selector_acc_mptr); + layout.validate().expect("earlier batch inversion may reuse selector bytes"); + } + #[test] fn trace_log_word_is_registered_after_scratch_regions() { let meta = ConstraintSystemMeta { @@ -1431,7 +1640,7 @@ mod tests { }, ..VerifierMemoryLayoutConfig::default() }; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); let region = layout.map.region("trace_u256_log_word").expect("trace_u256 region registered"); @@ -1443,6 +1652,52 @@ mod tests { layout.validate().expect("trace log word must not overlap"); } + #[test] + fn trace_log_word_accounts_for_theta_window_region_ends() { + let meta = ConstraintSystemMeta::default(); + let vk = synthetic_vk(); + let window_words = 4096; + let config = VerifierMemoryLayoutConfig { + pcs: PcsMemoryRequirements { + rot_points_words: window_words, + x1_powers_words: window_words, + q_com_words: window_words, + q_eval_set_words: window_words, + ..PcsMemoryRequirements::default() + }, + ..VerifierMemoryLayoutConfig::default() + }; + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); + + for (name, end) in [ + ( + "rot_points", + layout.rot_points_mptr.value().as_usize() + window_words * WORD_BYTES, + ), + ( + "x1_powers", + layout.x1_powers_mptr.value().as_usize() + window_words * WORD_BYTES, + ), + ( + "q_com_fixed_window", + layout.q_com_mptr.value().as_usize() + window_words * WORD_BYTES, + ), + ( + "q_eval_set", + layout.q_eval_set_mptr.value().as_usize() + window_words * WORD_BYTES, + ), + ( + "q_eval_cptr_slot", + layout.q_eval_cptr_mptr.value().as_usize() + WORD_BYTES, + ), + ] { + assert!( + layout.trace_u256_mptr >= end, + "trace log word should be after {name}" + ); + } + } + #[test] fn batch_invert_scratch_region_tracks_instance_shape() { let meta = ConstraintSystemMeta { @@ -1454,7 +1709,7 @@ mod tests { num_instances: 5, ..VerifierMemoryLayoutConfig::default() }; - let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x1000), config); + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); let region = layout .map .region("batch_invert_scratch") @@ -1465,4 +1720,45 @@ mod tests { MODEXP_FRAME_BYTES + (5 + 3 + 1 - 2) * WORD_BYTES ); } + + #[test] + fn lagrange_batch_invert_input_run_is_bounded_by_live_memory() { + let meta = ConstraintSystemMeta { + rotation_last: -3, + ..ConstraintSystemMeta::default() + }; + let vk = synthetic_vk(); + let windows = ThetaWindowLayout::compatibility(); + let cap_words = windows + .q_eval_cptr_word + .min(windows.g1_identity_word) + .min(windows.reversed_evals_word) + - ThetaSlot::XN.word(); + // `SolidityGenerator::try_new` rejects oversized instance counts up + // front against the constant form of this cap. The two must agree, or + // the early bound and the layout check would disagree about what fits. + assert_eq!(cap_words, theta_window::LAGRANGE_RUN_CAP_WORDS); + + // Largest instance count whose denominator run plus `x_n - 1` still + // fits below the q_eval calldata cursor: num + |rotation_last| + 1. + let max_instances = cap_words - 3 - 1; + let config = VerifierMemoryLayoutConfig { + num_instances: max_instances, + ..VerifierMemoryLayoutConfig::default() + }; + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); + assert_eq!(layout.batch_invert_input_words, cap_words); + layout.validate().expect("run ending below Q_EVAL_CPTR_MPTR is valid"); + + let config = VerifierMemoryLayoutConfig { + num_instances: max_instances + 1, + ..VerifierMemoryLayoutConfig::default() + }; + let layout = VerifierMemoryLayout::new(&meta, &vk, Ptr::memory(0x2000), config); + let err = layout.validate().unwrap_err(); + assert!( + err.contains("Lagrange batch-inversion input run"), + "unexpected validation error: {err}" + ); + } } diff --git a/proofs/solidity-verifier/src/lowering/layout/mod.rs b/proofs/solidity-verifier/src/lowering/layout/mod.rs index 1c885ea67..6425310a4 100644 --- a/proofs/solidity-verifier/src/lowering/layout/mod.rs +++ b/proofs/solidity-verifier/src/lowering/layout/mod.rs @@ -26,7 +26,22 @@ pub(crate) const SOLIDITY_ALLOCATABLE_MEMORY_START: usize = 0x80; /// The full reserved prefix: scratch, free-memory pointer, and zero slot. pub(crate) const SOLIDITY_RESERVED_MEMORY_BYTES: usize = SOLIDITY_ALLOCATABLE_MEMORY_START; /// Generated verifier transcript and low-memory precompile scratch base. -pub(crate) const LOW_MEMORY_SCRATCH_START: usize = SOLIDITY_ALLOCATABLE_MEMORY_START; +/// +/// Deliberately *above* [`SOLIDITY_ALLOCATABLE_MEMORY_START`]. The verifier +/// body is wrapped in `assembly ("memory-safe")`, which is what lets solc's +/// via-IR stack-to-memory mover run at all -- without it the block does not +/// compile (stack too deep). That mover reserves spill slots from `0x80` +/// upward and records the top in the runtime's `mstore(0x40, ...)` prologue. +/// Observed reservations run from `0x80` (none) to `0x8e0`, varying with the +/// circuit, the solc release, and the optimizer schedule. +/// +/// Basing the generated layout at `0x80` therefore put solc's spill slots and +/// the verifier's own transcript buffer in the same bytes, kept apart only by +/// live ranges that nothing enforced. Starting above the largest observed +/// reservation makes them disjoint by construction; +/// `compiled_memoryguard_does_not_overlap_generated_layout` fails the build if +/// a future circuit or compiler pushes the reservation past this base. +pub(crate) const LOW_MEMORY_SCRATCH_START: usize = 0x1000; /// Start of the Keccak transcript buffer used by the assembly helpers. pub(crate) const TRANSCRIPT_BUFFER_START: usize = LOW_MEMORY_SCRATCH_START; /// Shared low-memory scratch for PCS pairing serialization. @@ -36,7 +51,12 @@ pub(crate) const VERIFIER_RETURN_BUFFER_START: usize = LOW_MEMORY_SCRATCH_START; /// Return buffer used by split quotient evaluator calls. pub(crate) const QUOTIENT_RETURN_BUFFER_START: usize = LOW_MEMORY_SCRATCH_START; /// Constructor-time memory base for the separate VK runtime payload. -pub(crate) const VK_CONSTRUCTOR_PAYLOAD_START: usize = LOW_MEMORY_SCRATCH_START; +/// +/// Deliberately *not* derived from [`LOW_MEMORY_SCRATCH_START`]. This buffer +/// belongs to `Halo2VerifyingKey`, a separate contract whose assembly is not +/// annotated `memory-safe`, so solc reserves no via-IR spill window there and +/// the payload can sit at Solidity's normal allocatable start. +pub(crate) const VK_CONSTRUCTOR_PAYLOAD_START: usize = SOLIDITY_ALLOCATABLE_MEMORY_START; /// Number of EVM words in one Fr scalar. pub(crate) const FR_WORDS: usize = 1; /// EIP-2537 padded G1 encoding: x_hi, x_lo, y_hi, y_lo. @@ -89,7 +109,16 @@ pub(crate) const PCS_STATIC_WORKING_WORDS: usize = 32; /// Static two-pair KZG pairing scratch plus one return word. pub(crate) const PAIRING_STATIC_WORKING_WORDS: usize = PAIRING_TWO_PAIR_BYTES / WORD_BYTES + 1; /// Low-memory frame used by the final two-pair KZG pairing helper. -pub(crate) const FINAL_PAIRING_SCRATCH_START: usize = PAIRING_TWO_PAIR_BYTES; +/// +/// Placed past the end of the accumulator pairing-batch hash frame, which +/// occupies `[PAIRING_BATCH_PTR, PAIRING_BATCH_PTR + PAIRING_BATCH_HASH_BYTES)` +/// = `[0x1000, 0x1220)`. Starting lower would put the last word of the hashed +/// ACC_LHS copy inside this scratch. +/// The two regions carry different `MemoryPhase`s, and `MemoryLifetime:: +/// intersects` treats distinct phases as never co-live, so the planner cannot +/// catch that overlap -- it has to be avoided by construction here. +pub(crate) const FINAL_PAIRING_SCRATCH_START: usize = + accumulator::PAIRING_BATCH_PTR + accumulator::PAIRING_BATCH_HASH_BYTES; pub(crate) mod precompile { //! EVM precompile addresses used by the generated verifier. These values @@ -141,8 +170,29 @@ pub(crate) mod accumulator { pub(crate) const CARRIED_SCALARS: usize = 2; /// Low-memory hash frame for batching the accumulator pairing with KZG: /// domain tag word, KZG rhs/lhs G1s, then accumulator rhs/lhs G1s. - pub(crate) const PAIRING_BATCH_PTR: usize = 0x100; - /// ASCII `"pairing-batch-acc-kzg"` right-padded to one EVM word. + /// + /// Rooted at [`super::LOW_MEMORY_SCRATCH_START`] like every other + /// low-memory scratch base: the frame historically sat at `0x100`, inside + /// the `[0x80, reserved_end)` window solc's via-IR stack-to-memory mover + /// reserves for spill slots, so a live spill could silently corrupt the + /// alpha Fiat-Shamir preimage or the pairing inputs built here. + pub(crate) const PAIRING_BATCH_PTR: usize = super::LOW_MEMORY_SCRATCH_START; + /// Domain-separation word for the accumulator pairing batch. + /// + /// This is a 29-byte numeric literal: ASCII `"pairing-batch-acc-kzg"` + /// (21 bytes) followed by 8 zero bytes. FinalPairing.yul stores it with + /// `mstore`, which left-pads numeric literals to a full word, so the word + /// actually hashed into alpha is + /// + /// ```text + /// 00 00 00 || "pairing-batch-acc-kzg" || 00 * 8 + /// ``` + /// + /// i.e. NOT right-padded ASCII, as this comment previously claimed. The + /// value is still a fixed unique constant, so domain separation is + /// unaffected -- but any reimplementation or differential fixture that + /// derives alpha from the right-padded form will disagree with the + /// deployed verifier on accept/reject. pub(crate) const PAIRING_BATCH_DOMAIN_TAG_HEX: &str = "0x70616972696e672d62617463682d6163632d6b7a670000000000000000"; /// KZG pairing RHS point offset inside the batch hash frame. @@ -578,6 +628,25 @@ pub(crate) mod theta_window { /// Word offset where reversed proof evaluations begin. pub(crate) const REVERSED_EVALS_WORD: usize = G1_IDENTITY_WORD + G1_WORDS + G1_IDENTITY_PADDING_WORDS; + + /// Largest Lagrange batch-inversion input run that fits below live memory. + /// + /// The run starts in place at `X_N_MPTR` and deliberately spills past the + /// theta band into the rot_points / x1_powers / q_eval_set windows, which + /// are written only after the Lagrange phase. The first words that are + /// already live when Lagrange runs are the q_eval calldata cursor, the G1 + /// identity slot (which must stay virgin zero memory), and the decoded + /// proof evaluations, so the run must end at or before the lowest of those. + pub(crate) const LAGRANGE_RUN_CAP_WORDS: usize = { + let mut cap = Q_EVAL_CPTR_WORD; + if G1_IDENTITY_WORD < cap { + cap = G1_IDENTITY_WORD; + } + if REVERSED_EVALS_WORD < cap { + cap = REVERSED_EVALS_WORD; + } + cap - ThetaSlot::XN.word() + }; } pub(crate) mod trace { @@ -591,6 +660,7 @@ pub(crate) mod trace { pub(crate) const PROOF_COMMIT_BASE: usize = 10_000; // Proof G1 reads. pub(crate) const PROOF_EVAL_BASE: usize = 20_000; // Proof scalar eval reads. pub(crate) const QUOTIENT_IDENTITY_BASE: u64 = 30_000; // Quotient identities. + pub(crate) const PCS_Q_COM_BASE: u64 = 40_000; // PCS q_com points. pub(crate) const PCS_SERIALIZED_POINT_SET_BASE: u64 = 41_000; // PCS point sets. pub(crate) const SELECTOR_FOLD_BASE: usize = 60_000; // Selector accumulators. } @@ -618,7 +688,13 @@ mod tests { assert_eq!(super::SOLIDITY_FREE_MEMORY_POINTER_SLOT, 0x40); assert_eq!(super::SOLIDITY_ZERO_SLOT, 0x60); assert_eq!(super::SOLIDITY_RESERVED_MEMORY_BYTES, 0x80); - assert_eq!(super::TRANSCRIPT_BUFFER_START, 0x80); + assert_eq!( + super::TRANSCRIPT_BUFFER_START, + super::LOW_MEMORY_SCRATCH_START + ); + // Above Solidity's allocatable start on purpose: solc reserves + // via-IR spill slots upward from 0x80 in this contract. + assert!(super::LOW_MEMORY_SCRATCH_START > super::SOLIDITY_ALLOCATABLE_MEMORY_START); assert_eq!(super::VK_CONSTRUCTOR_PAYLOAD_START, 0x80); } @@ -680,6 +756,7 @@ mod tests { assert_eq!(trace::PROOF_COMMIT_BASE, 10_000); assert_eq!(trace::PROOF_EVAL_BASE, 20_000); assert_eq!(trace::QUOTIENT_IDENTITY_BASE, 30_000); + assert_eq!(trace::PCS_Q_COM_BASE, 40_000); assert_eq!(trace::PCS_SERIALIZED_POINT_SET_BASE, 41_000); assert_eq!(trace::SELECTOR_FOLD_BASE, 60_000); } @@ -703,8 +780,16 @@ mod tests { assert_eq!(accumulator::LIMB_BITS, 56); assert_eq!(accumulator::LIMBS, 7); assert_eq!(accumulator::LIMBS_PER_WORD, 4); - assert_eq!(accumulator::PAIRING_BATCH_PTR, 0x100); + // Above solc's via-IR spill window like every other low-memory base. + assert_eq!( + accumulator::PAIRING_BATCH_PTR, + super::LOW_MEMORY_SCRATCH_START + ); assert_eq!(accumulator::PAIRING_BATCH_HASH_BYTES, 0x220); + assert_eq!( + super::FINAL_PAIRING_SCRATCH_START, + super::LOW_MEMORY_SCRATCH_START + 0x220 + ); assert_eq!(quotient_limb::LIMBS, 7); assert_eq!(quotient_limb::PAIRWISE_TERMS, 49); assert_eq!(quotient_limb::PAIRWISE_COEFFS, 13); diff --git a/proofs/solidity-verifier/src/lowering/layout/vk_payload.rs b/proofs/solidity-verifier/src/lowering/layout/vk_payload.rs index c7c21f047..a416d3576 100644 --- a/proofs/solidity-verifier/src/lowering/layout/vk_payload.rs +++ b/proofs/solidity-verifier/src/lowering/layout/vk_payload.rs @@ -111,7 +111,9 @@ impl VkPayloadLayout { word_offset: self.cursor_words, word_len, }; - self.cursor_words += word_len; + self.cursor_words = self.cursor_words.checked_add(word_len).ok_or_else(|| { + format!("VK payload word cursor overflow reserving {kind:?} ({word_len} words)") + })?; self.sections.push(section); Ok(section) } @@ -122,7 +124,10 @@ impl VkPayloadLayout { kind: PayloadSectionKind, commitments: usize, ) -> Result { - self.reserve(kind, commitments * G1_WORDS) + let word_len = commitments.checked_mul(G1_WORDS).ok_or_else(|| { + format!("VK payload G1 word overflow for {kind:?}: {commitments} commitments") + })?; + self.reserve(kind, word_len) } /// Return the section for `kind`, if it has been reserved. diff --git a/proofs/solidity-verifier/src/lowering/plan.rs b/proofs/solidity-verifier/src/lowering/plan.rs index e1682f287..6e001d3e6 100644 --- a/proofs/solidity-verifier/src/lowering/plan.rs +++ b/proofs/solidity-verifier/src/lowering/plan.rs @@ -12,7 +12,9 @@ use crate::lowering::{ kzg, layout, layout::memory::{PcsMemoryRequirements, VerifierMemoryLayout, VerifierMemoryLayoutConfig}, quotient::{QuotientComputationBlocks, QuotientHelperFlags, QuotientStateSlots}, - quotient_numerator::vm::{QuotientProgramBuild, QuotientProgramPlan, RepackedProofLayoutPlan}, + quotient_numerator::vm::{ + certify, QuotientProgramBuild, QuotientProgramPlan, RepackedProofLayoutPlan, + }, render::{Halo2VerifyingKey, QuotientExternal, QuotientProgram}, VerifierBuildInputs, }; @@ -163,6 +165,23 @@ impl LoweringPlan { }; plan.validate_generator_invariants() .unwrap_or_else(|err| panic!("generator invariant violation: {err}")); + + // Certify the limb superinstructions against a generic-opcode build of + // the same identity stream. This needs `inputs`, so it runs here rather + // than inside `validate_generator_invariants`. + let baseline_build = inputs.build_quotient_program_items_with_limb_ops( + &plan.quotient.plan.items, + &plan.quotient.plan.selector_fold, + false, + ); + certify::certify_quotient_builds_agree( + &plan.quotient.plan, + &plan.quotient.build, + &baseline_build, + &plan.vk, + ) + .unwrap_or_else(|err| panic!("quotient dual-build certification failed: {err}")); + plan } @@ -279,6 +298,35 @@ impl LoweringPlan { self.vk.quotient_program_words )); } + // The VK payload embeds the const table and packed bytecode compiled + // inside `generate_vk`, but the interpreter is rendered from this + // independently recompiled `self.quotient.build`. Length checks alone + // let a nondeterministic/order-dependent compile ship a pinned VK whose + // bytecode disagrees with the rendered VM. Compare the embedded words + // against the plan build word-for-word so any divergence fails codegen. + if let Some(const_offset) = self.vk.quotient_const_offset_words { + let build_consts = &self.quotient.build.consts; + let embedded = &self.vk.constants[const_offset..const_offset + build_consts.len()]; + if embedded.iter().map(|(_, value)| value).ne(build_consts.iter()) { + return Err( + "quotient const table embedded in the VK payload does not match the \ + plan-rebuilt const table" + .to_string(), + ); + } + } + if let Some(program_offset) = self.vk.quotient_program_offset_words { + let build_words = + layout::vk_payload::PackedProgramCodec::encode_words(&self.quotient.build.bytes); + let embedded = &self.vk.constants[program_offset..program_offset + build_words.len()]; + if embedded.iter().map(|(_, value)| value).ne(build_words.iter()) { + return Err( + "quotient program bytecode embedded in the VK payload does not match the \ + plan-rebuilt bytecode" + .to_string(), + ); + } + } if self.quotient.program.stack_mptr != self.quotient.stack_mptr { return Err(format!( "quotient stack pointer drifted: model={:#x} planned={:#x}", @@ -291,6 +339,10 @@ impl LoweringPlan { self.quotient.program.eval_numer_mptr, self.quotient.state_slots.eval_numer_mptr )); } + // Prove the emitted bytecode still evaluates the identities it was + // lowered from, before it can be pinned into a verifying key. + certify::certify_quotient_program(&self.quotient.plan, &self.quotient.build, &self.vk) + .map_err(|err| format!("quotient program certification failed: {err}"))?; Ok(()) } diff --git a/proofs/solidity-verifier/src/lowering/protocol/mod.rs b/proofs/solidity-verifier/src/lowering/protocol/mod.rs index a035abaf5..41f004e59 100644 --- a/proofs/solidity-verifier/src/lowering/protocol/mod.rs +++ b/proofs/solidity-verifier/src/lowering/protocol/mod.rs @@ -324,10 +324,28 @@ impl ProtocolPlan { /// passed to `partially_evaluate_identities` and KZG `multi_prepare`. /// The plan preserves that order so the Solidity transcript and proof /// cursors stay byte-compatible with the Rust verifier. + /// + /// Panics if the resulting plan fails [`ProtocolPlan::validate`]. Callers on + /// a fallible path — notably [`SolidityGenerator::try_new`], which promises + /// a typed error for unsupported constraint systems — must use + /// [`ProtocolPlan::try_from_constraint_system`] instead. pub(crate) fn from_constraint_system( cs: &ConstraintSystem, nb_committed_instances: usize, ) -> Self { + Self::try_from_constraint_system(cs, nb_committed_instances) + .unwrap_or_else(|err| panic!("invalid protocol plan: {err}")) + } + + /// Fallible counterpart of [`ProtocolPlan::from_constraint_system`]. + /// + /// Returns the validation failure rather than panicking, so constraint + /// systems outside the supported verifier shape can be surfaced as a typed + /// error at the public API boundary. + pub(crate) fn try_from_constraint_system( + cs: &ConstraintSystem, + nb_committed_instances: usize, + ) -> Result { let cs_degree = cs.degree(); let num_fixeds = cs.num_fixed_columns(); let permutation_columns = cs.permutation().get_columns(); @@ -610,8 +628,8 @@ impl ProtocolPlan { common_polys, quotient, }; - plan.validate().unwrap_or_else(|err| panic!("invalid protocol plan: {err}")); - plan + plan.validate()?; + Ok(plan) } /// Number of scalar evaluations in the proof's main eval block. diff --git a/proofs/solidity-verifier/src/lowering/quotient.rs b/proofs/solidity-verifier/src/lowering/quotient.rs index d77a5e504..b9b0bf28e 100644 --- a/proofs/solidity-verifier/src/lowering/quotient.rs +++ b/proofs/solidity-verifier/src/lowering/quotient.rs @@ -253,7 +253,17 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { // `build.max_stack` only describes the interpreted operand stack. Some // native callbacks share `quotient_stack_mptr` as a scratch base, so // the registered memory region must cover both possible users. - build.max_stack.max(native_callback_scratch_words) + // + // Floor at one word: every inline/native direct_quotient_block writes + // one eval-scratch word at eval_scratch_slot == quotient_stack_mptr + // (see direct_quotient_block and compact_quotient_computation_blocks), + // so the region is always written even when the interpreted stack and + // native scratch are both empty (a degenerate-but-valid VK whose gates + // all fit the inline prefix with no permutation sets, lookups, or VM + // items). Accounting that word here keeps the in-bounds invariant with + // the code that emits the write, rather than relying on the unrelated + // MODEXP-frame clamp in layout/memory.rs. + build.max_stack.max(native_callback_scratch_words).max(1) } /// Number of persistent VM temp words needed for state plus selector @@ -594,12 +604,27 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { } /// Estimate the generated native callback block for one identity. + /// + /// This is a size/gas PROXY consumed only by the native-vs-VM gate + /// selection heuristic (native_gate_candidates); it is never emitted. The + /// real native blocks are produced by compact_quotient_computation_blocks + /// with `selector_fold.gap_for(identity)`, but that fold plan does not exist + /// yet at selection time (it is derived from the selection outcome), so we + /// use a fixed proxy gap: Some(1) for selector targets, None otherwise. + /// + /// Any divergence between this proxy and the eventual `gap_for` only + /// perturbs which gates get promoted to native callbacks; it can never + /// change the correctness of the generated verifier, because actual emission + /// always uses the real gap. Do not "fix" this to call `gap_for` here: the + /// plan is intentionally unavailable at this point. fn native_identity_estimate_block(identity: &QuotientIdentity) -> Vec { let state_slots = QuotientStateSlots { eval_numer_mptr: 0x2000, trace_id_mptr: 0x2020, selector_power_mptr: 0x2040, }; + // Proxy gap only; see the doc comment above. This deliberately differs + // from selector_fold.gap_for(identity) used at emission time. let selector_gap = matches!(identity.target, QuotientTarget::Selector(_)).then_some(1); Self::direct_quotient_block( &identity.lines, @@ -1262,15 +1287,26 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { yul_const_value(value, const_vars).as_deref() == Some(expected_coeff) } - /// Record `let name := const` bindings for later limb-chain matching. + /// Track constant variable bindings for later limb-chain matching. + /// + /// Records `name := const` (whether or not introduced with `let`), and, + /// crucially, forgets any variable that is reassigned to a non-constant + /// value. Ignoring non-`let` reassignments would leave a stale literal in + /// `const_vars`, so a later `mulmod(name, limb, r)` could be mis-recognized + /// as a fused limb7 coefficient and bake the wrong constant into the + /// generated quotient identity. fn record_yul_const_assignment(line: &str, const_vars: &mut HashMap) { - let Some((dst, rhs)) = yul_let_assignment(line) else { - return; - }; - let Some(value) = yul_const_value(&rhs, const_vars) else { + let Some(assignment) = yul_assignment(line) else { return; }; - const_vars.insert(dst, value); + match yul_const_value(&assignment.expr, const_vars) { + Some(value) => { + const_vars.insert(assignment.dst, value); + } + None => { + const_vars.remove(&assignment.dst); + } + } } /// Trace, advance, and accumulate one main quotient identity value. @@ -1589,6 +1625,18 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { Self::push_structured_main_fold(&mut block, "q_lookup_eval", state_slots, trace); block.push("}".to_string()); + // Fail closed on a chunk/helper-eval count mismatch: zip would + // otherwise silently drop the excess chunks, removing helper + // constraints from the y-batched numerator. Guarded indirectly today + // by the protocol lookup count check, but assert it directly at the + // zip site. + assert_eq!( + chunked.input_expression_chunks().len(), + h_evals.len(), + "lookup {lookup_idx}: input chunk count {} != helper eval count {}", + chunked.input_expression_chunks().len(), + h_evals.len(), + ); for (input_chunk, h_eval) in chunked.input_expression_chunks().iter().zip(h_evals.iter()) { @@ -1596,7 +1644,15 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { block.push("{".to_string()); if k == 0 { - block.push("let q_lookup_eval := 0".to_string()); + // Unreachable today (chunks are never empty), but emit the + // reference-faithful value (h_eval) rather than 0: the + // native verifier (plonk/logup.rs) computes + // helper_eval * (empty product = 1) - (empty sum = 0) + // = helper_eval for an empty chunk, enforcing h == 0. + // Emitting 0 leaves h unconstrained while the accumulator + // still folds this h_eval into sum_h. Mirrors the fix in + // quotient_numerator/yul_emit.rs. + block.push(format!("let q_lookup_eval := {}", h_eval)); Self::push_structured_main_fold( &mut block, "q_lookup_eval", @@ -1903,7 +1959,26 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { items: &[QuotientProgramItem], selector_fold: &SelectorFoldPlan, ) -> QuotientProgramBuild { - let mut builder = QuotientProgramBuilder::default(); + self.build_quotient_program_items_with_limb_ops( + items, + selector_fold, + crate::lowering::config::DEFAULT_QUOTIENT_LIMB_VM_OPS, + ) + } + + /// Lower the same item stream under an explicit limb-opcode policy. + /// + /// Rendering always uses the crate default. The `false` build exists so the + /// generator can cross-check the limb superinstructions against a program + /// that uses only generic Fr opcodes; see + /// `quotient_numerator::vm::certify::certify_quotient_builds_agree`. + pub(super) fn build_quotient_program_items_with_limb_ops( + &self, + items: &[QuotientProgramItem], + selector_fold: &SelectorFoldPlan, + limb_vm_ops: bool, + ) -> QuotientProgramBuild { + let mut builder = QuotientProgramBuilder::with_limb_vm_ops(limb_vm_ops); // Lower the logical plan into bytecode in one pass. Repeated // subexpressions are emitted directly; native callbacks remain opaque // markers because their arithmetic is emitted as separate Yul kernels diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs new file mode 100644 index 000000000..a8348ec77 --- /dev/null +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/certify.rs @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: CC0-1.0 +//! Render-time self-certification of the emitted quotient VM program. +//! +//! The quotient lowering runs a peephole optimizer: shape recognizers rewrite +//! seven-limb foreign-field expressions into superinstructions, and a +//! run-compaction pass rewrites adjacent affine terms into counted opcodes. Both +//! are pure encoding choices that must preserve the evaluated polynomial +//! exactly. This module proves that they did, for the specific program this +//! render is about to emit, by executing the finalized bytecode with the +//! independent interpreter in [`super::reference`] and comparing each identity +//! against direct evaluation of the [`QuotientExpr`] tree it was lowered from. +//! +//! This is a generator-time gate, not a test: every artifact the generator +//! produces is certified before it can be pinned into a verifying key, so a +//! recognizer bug on a previously unseen gate shape fails the render instead of +//! shipping a wrong verifier. +//! +//! See [`super::reference`] for what this does and does not cover. + +use ff::Field; +use midnight_curves::Fq; +use sha3::{Digest, Keccak256}; + +use super::{ + quotient_op_len, + reference::{eval_quotient_expr, eval_quotient_identity, QuotientRefMemory}, + QuotientExpr, QuotientMem, QuotientProgramBuild, QuotientProgramItem, QuotientProgramPlan, + QuotientTarget, Q_OP_FOLD_MAIN, Q_OP_FOLD_SELECTOR, Q_OP_NATIVE_IDENTITY, Q_OP_NATIVE_LOOKUP, + Q_OP_NATIVE_PERMUTATION, +}; +use crate::lowering::render::Halo2VerifyingKey; + +/// Seed for the certification assignment. +/// +/// The seed is deterministic so failing renders reproduce exactly, but it is +/// derived from the finalized artifact rather than fixed globally. This keeps +/// the sampled assignment independent of any circuit author's pre-render view +/// of the quotient program while preserving reproducible diagnostics. +const QUOTIENT_CERTIFY_SEED_DOMAIN: &[u8] = b"midfall/quotient-vm/certify-seed/v3"; + +/// Derive the random-assignment seed from every compared artifact. +/// +/// Binding the oracle expressions closes the case where a miscompile drops a +/// tunable constant before it reaches the emitted constant table. Binding both +/// builds makes the dual-build challenge depend on the optimized and baseline +/// representations. The VK payload is included after generator invariants have +/// checked that its quotient sections match the finalized build. +pub(crate) fn derive_certify_seed( + builds: &[&QuotientProgramBuild], + exprs: &[&QuotientExpr], + vk_payload: &[u8], +) -> [u8; 32] { + let mut hasher = Keccak256::new(); + hasher.update(QUOTIENT_CERTIFY_SEED_DOMAIN); + + hasher.update((builds.len() as u64).to_be_bytes()); + for build in builds { + hasher.update((build.bytes.len() as u64).to_be_bytes()); + hasher.update(&build.bytes); + + hasher.update((build.consts.len() as u64).to_be_bytes()); + for value in &build.consts { + hasher.update(value.to_be_bytes::<32>()); + } + } + + hasher.update((exprs.len() as u64).to_be_bytes()); + for expr in exprs { + hash_quotient_expr(&mut hasher, expr); + } + + hasher.update((vk_payload.len() as u64).to_be_bytes()); + hasher.update(vk_payload); + + hasher.finalize().into() +} + +/// Hash one expression with explicit node and memory-address tags. +fn hash_quotient_expr(hasher: &mut Keccak256, expr: &QuotientExpr) { + match expr { + QuotientExpr::Const(value) => { + hasher.update([0]); + hasher.update(value.to_be_bytes::<32>()); + } + QuotientExpr::Mem(QuotientMem::Literal(ptr)) => { + hasher.update([1]); + hasher.update(ptr.to_be_bytes()); + } + QuotientExpr::Mem(QuotientMem::Token(token)) => { + hasher.update([2, *token]); + } + QuotientExpr::Mem(QuotientMem::TokenOffset(token, offset)) => { + hasher.update([3, *token]); + hasher.update(offset.to_be_bytes()); + } + QuotientExpr::Add(lhs, rhs) => { + hasher.update([4]); + hash_quotient_expr(hasher, lhs); + hash_quotient_expr(hasher, rhs); + } + QuotientExpr::Mul(lhs, rhs) => { + hasher.update([5]); + hash_quotient_expr(hasher, lhs); + hash_quotient_expr(hasher, rhs); + } + QuotientExpr::Neg(inner) => { + hasher.update([6]); + hash_quotient_expr(hasher, inner); + } + } +} + +/// Expressions evaluated by the compact quotient program. +fn interpreted_exprs(plan: &QuotientProgramPlan) -> Vec<&QuotientExpr> { + plan.items + .iter() + .filter_map(|item| match item { + QuotientProgramItem::Identity(identity) => Some(&identity.expr), + _ => None, + }) + .collect() +} + +/// Certify that the emitted bytecode evaluates the planned identities. +/// +/// Runs after [`super::validate_quotient_program`], which has already proven the +/// stream decodes and is stack-safe; this pass assumes well-formedness and +/// checks *meaning*. +pub(crate) fn certify_quotient_program( + plan: &QuotientProgramPlan, + build: &QuotientProgramBuild, + vk: &Halo2VerifyingKey, +) -> Result<(), String> { + let exprs = interpreted_exprs(plan); + let vk_payload = vk.bytes(); + let seed = derive_certify_seed(&[build], &exprs, &vk_payload); + let mut mem = QuotientRefMemory::new(seed); + let bytes = &build.bytes; + let mut cursor = 0usize; + + for (item_idx, item) in plan.items.iter().enumerate() { + match item { + QuotientProgramItem::Identity(identity) => { + let (expr_end, fold_op) = identity_segment(bytes, cursor) + .map_err(|err| format!("quotient item {item_idx}: {err}"))?; + + let actual = + eval_quotient_identity(&bytes[cursor..expr_end], &build.consts, &mut mem) + .map_err(|err| { + format!( + "quotient identity {} ({:?}): {err}", + identity.meta.global_index, identity.meta.source + ) + })?; + let expected = eval_quotient_expr(&identity.expr, &mut mem); + + if actual != expected { + return Err(format!( + "quotient VM miscompiled identity {} ({:?}): bytecode evaluates to {:?} \ + but its expression evaluates to {:?}. This is a codegen bug in the \ + quotient lowering (shape recognizer, operand packing, or run \ + compaction), not a proof or verifying-key problem.", + identity.meta.global_index, identity.meta.source, actual, expected + )); + } + + check_fold(bytes, expr_end, fold_op, identity, plan).map_err(|err| { + format!( + "quotient identity {} ({:?}): {err}", + identity.meta.global_index, identity.meta.source + ) + })?; + + cursor = expr_end + quotient_op_len(bytes, expr_end); + } + QuotientProgramItem::NativePermutation + | QuotientProgramItem::NativeLookup + | QuotientProgramItem::NativeIdentity(_) => { + // Native markers carry no arithmetic here: the Yul template + // substitutes generated straight-line kernels. Certify only + // that the marker sits at the planned stream position. + let expected_op = match item { + QuotientProgramItem::NativePermutation => Q_OP_NATIVE_PERMUTATION, + QuotientProgramItem::NativeLookup => Q_OP_NATIVE_LOOKUP, + _ => Q_OP_NATIVE_IDENTITY, + }; + let actual_op = *bytes.get(cursor).ok_or_else(|| { + format!("quotient item {item_idx}: program ended before native marker") + })?; + if actual_op != expected_op { + return Err(format!( + "quotient item {item_idx}: expected native marker {expected_op:#x} at byte \ + {cursor}, found {actual_op:#x}" + )); + } + if let QuotientProgramItem::NativeIdentity(native_idx) = item { + // The marker index is a big-endian u16, not a single byte. + let hi = *bytes.get(cursor + 1).ok_or_else(|| { + format!("quotient item {item_idx}: truncated native identity index") + })?; + let lo = *bytes.get(cursor + 2).ok_or_else(|| { + format!("quotient item {item_idx}: truncated native identity index") + })?; + let encoded = u16::from_be_bytes([hi, lo]) as usize; + if encoded != *native_idx { + return Err(format!( + "quotient item {item_idx}: native identity index {encoded} does not \ + match planned index {native_idx}" + )); + } + } + cursor += quotient_op_len(bytes, cursor); + } + } + } + + if cursor != bytes.len() { + return Err(format!( + "quotient program has {} trailing byte(s) after the planned item stream", + bytes.len() - cursor + )); + } + + Ok(()) +} + +/// Find the end of one identity expression and the fold opcode that closes it. +fn identity_segment(bytes: &[u8], start: usize) -> Result<(usize, u8), String> { + let mut idx = start; + while idx < bytes.len() { + let op = bytes[idx]; + match op { + Q_OP_FOLD_MAIN | Q_OP_FOLD_SELECTOR => return Ok((idx, op)), + Q_OP_NATIVE_PERMUTATION | Q_OP_NATIVE_LOOKUP | Q_OP_NATIVE_IDENTITY => { + return Err(format!( + "native marker {op:#x} at byte {idx} interrupts an identity expression" + )); + } + _ => idx += quotient_op_len(bytes, idx), + } + } + Err(format!( + "identity expression starting at byte {start} is never folded" + )) +} + +/// Check that the emitted fold matches the planned target and selector gap. +fn check_fold( + bytes: &[u8], + fold_idx: usize, + fold_op: u8, + identity: &super::QuotientIdentity, + plan: &QuotientProgramPlan, +) -> Result<(), String> { + match (fold_op, identity.target) { + (Q_OP_FOLD_MAIN, QuotientTarget::Main) => Ok(()), + (Q_OP_FOLD_SELECTOR, QuotientTarget::Selector(selector_idx)) => { + let encoded_idx = *bytes + .get(fold_idx + 1) + .ok_or_else(|| "truncated selector fold index".to_string())? + as usize; + if encoded_idx != selector_idx { + return Err(format!( + "selector fold targets bucket {encoded_idx} but the plan says {selector_idx}" + )); + } + let hi = *bytes + .get(fold_idx + 2) + .ok_or_else(|| "truncated selector fold gap".to_string())?; + let lo = *bytes + .get(fold_idx + 3) + .ok_or_else(|| "truncated selector fold gap".to_string())?; + let encoded_gap = u16::from_be_bytes([hi, lo]) as usize; + let planned_gap = plan + .selector_fold + .gap_for(identity) + .ok_or_else(|| "selector identity has no planned fold gap".to_string())?; + if encoded_gap != planned_gap { + return Err(format!( + "selector fold gap {encoded_gap} does not match planned gap {planned_gap}" + )); + } + Ok(()) + } + (fold_op, target) => Err(format!( + "fold opcode {fold_op:#x} does not match planned target {target:?}" + )), + } +} + +/// Certify that two builds of the same identity stream agree. +/// +/// The limb-aware superinstructions are the least principled part of the +/// lowering: they pattern-match algebraic shapes out of a commutative-ring +/// expression tree. Building the same stream with those recognizers disabled +/// yields a program using only `PUSH`/`ADD`/`MUL`/`NEG`, which is +/// straightforward to audit. Requiring the two to agree identity-by-identity +/// turns every recognizer from trusted code into a checked optimization. +pub(crate) fn certify_quotient_builds_agree( + plan: &QuotientProgramPlan, + optimized: &QuotientProgramBuild, + baseline: &QuotientProgramBuild, + vk: &Halo2VerifyingKey, +) -> Result<(), String> { + let exprs = interpreted_exprs(plan); + let vk_payload = vk.bytes(); + let seed = derive_certify_seed(&[optimized, baseline], &exprs, &vk_payload); + let mut mem = QuotientRefMemory::new(seed); + + let optimized_values = identity_values(plan, optimized, &mut mem)?; + let baseline_values = identity_values(plan, baseline, &mut mem)?; + + if optimized_values.len() != baseline_values.len() { + return Err(format!( + "quotient dual build disagrees on identity count: {} with limb opcodes, {} without", + optimized_values.len(), + baseline_values.len() + )); + } + + for (position, (lhs, rhs)) in optimized_values.iter().zip(baseline_values.iter()).enumerate() { + if lhs != rhs { + return Err(format!( + "quotient limb superinstructions changed the value of interpreted identity at \ + stream position {position}: {lhs:?} with limb opcodes, {rhs:?} without. One of \ + the shape recognizers is unsound for this gate shape." + )); + } + } + + Ok(()) +} + +/// Evaluate every interpreted identity in one build, in stream order. +fn identity_values( + plan: &QuotientProgramPlan, + build: &QuotientProgramBuild, + mem: &mut QuotientRefMemory, +) -> Result, String> { + let bytes = &build.bytes; + let mut values = Vec::new(); + let mut cursor = 0usize; + + for item in &plan.items { + match item { + QuotientProgramItem::Identity(_) => { + let (expr_end, _) = identity_segment(bytes, cursor)?; + values.push(eval_quotient_identity( + &bytes[cursor..expr_end], + &build.consts, + mem, + )?); + cursor = expr_end + quotient_op_len(bytes, expr_end); + } + _ => { + // Native markers evaluate no bytecode; both builds emit the + // same marker at the same stream position. + values.push(Fq::ZERO); + cursor += quotient_op_len(bytes, cursor); + } + } + } + + Ok(values) +} diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs index affd899a2..67a17b82f 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/mod.rs @@ -52,6 +52,9 @@ //! underflow, and identity-boundary stack leaks before the bytes can be pinned //! into a VK runtime. +pub(crate) mod certify; +pub(crate) mod reference; + use std::collections::{HashMap, HashSet}; use ff::{Field, PrimeField}; @@ -1096,7 +1099,6 @@ impl Default for QuotientProgramBuilder { impl QuotientProgramBuilder { /// Create a builder, optionally enabling limb-specialized opcode emission. - #[cfg(test)] pub(crate) fn with_limb_vm_ops(enabled: bool) -> Self { Self { limb_vm_ops: enabled, @@ -1210,6 +1212,8 @@ impl QuotientProgramBuilder { let bytes = compact_quotient_runs(&self.bytes); let validated_max_stack = validate_quotient_program(&bytes) .unwrap_or_else(|err| panic!("invalid finalized quotient VM program: {err}")); + validate_quotient_const_slots(&bytes, self.consts.len()) + .unwrap_or_else(|err| panic!("invalid finalized quotient VM program: {err}")); assert_eq!( validated_max_stack, self.max_stack, "quotient VM physical program stack depth diverged from builder accounting" @@ -1357,18 +1361,47 @@ impl QuotientProgramBuilder { if !self.limb_vm_ops { return false; } - let Some((shape, residue)) = quotient_limb_subshape(expr) else { + let Some((shape, residue, matched)) = quotient_limb_subshape(expr) else { return false; }; if !self.limb_shape_has_u8_const_slots(&shape) { return false; } + self.reserve_limb_shape_consts(&shape); self.emit_expr(&residue); - self.emit_limb_shape(shape); + // `limb_shape_has_u8_const_slots` was checked against the constant table + // before `emit_expr(&residue)`. Emitting the residue can insert new + // constants and push a shape coefficient past a one-byte constant slot, + // which would panic in `emit_limb_shape`'s `u8::try_from(...).expect(...)`. + // Re-check against the post-residue table: keep the fused limb opcode + // only while every coefficient still fits, otherwise emit the matched + // terms through the generic path (their sum equals the shape's value). + if self.limb_shape_has_u8_const_slots(&shape) { + self.emit_limb_shape(shape); + } else { + self.emit_affine_terms(&matched); + } self.op_binary(Q_OP_ADD); true } + /// Emit `Σ terms[i]` with generic stack ops, leaving one value on the stack. + /// + /// Each entry is one recognized affine/bilinear term (a scaled memory load + /// or product), so emitting it cannot re-enter the limb-decomposition + /// peephole, and its coefficients use `emit_const`'s u16-capable slots. This + /// is the panic-free fallback for a recognized limb shape whose coefficients + /// no longer fit one-byte constant slots after intervening emission. The + /// slice is always non-empty (a recognized subshape uses at least one term). + fn emit_affine_terms(&mut self, terms: &[QuotientExpr]) { + for (idx, term) in terms.iter().enumerate() { + self.emit_expr(term); + if idx > 0 { + self.op_binary(Q_OP_ADD); + } + } + } + /// Try to replace a full expression with one limb-specialized opcode. /// /// The const-slot preflight is part of the ABI justification: limb opcodes @@ -1428,6 +1461,22 @@ impl QuotientProgramBuilder { self.peek_u8_const_slots(&coeffs).is_some() } + /// Reserve coefficient slots before residue emission can grow the table. + fn reserve_limb_shape_consts(&mut self, shape: &QuotientLimbShape) { + match shape { + QuotientLimbShape::Lin7 { terms } | QuotientLimbShape::Bilin7Row { terms, .. } => { + for (coeff, _) in terms { + self.const_slot(*coeff); + } + } + QuotientLimbShape::Bilin7Pairwise { coeffs, .. } => { + for coeff in coeffs { + self.const_slot(*coeff); + } + } + } + } + /// Emit the byte-level representation of a pre-validated limb shape. fn emit_limb_shape(&mut self, shape: QuotientLimbShape) { match shape { @@ -1589,15 +1638,39 @@ impl QuotientProgramBuilder { if !collect_product_leaves(product, &mut leaves) { return false; } - let Some(product) = self.product_add_macro(&leaves) else { + let Some(fused) = self.product_add_macro(&leaves) else { return false; }; + self.reserve_product_add_consts(fused); self.emit_expr(base); - self.emit_product_add(product); + // `product_add_macro` checked the fused scalar against the constant + // table as it stood *before* `emit_expr(base)`. Emitting `base` can + // insert new constants and push that scalar past a one-byte constant + // slot, which would panic in `emit_product_add`'s + // `u8::try_from(...).expect(...)`. Re-check against the post-`base` + // table: keep the fused opcode only while the scalar still fits, + // otherwise add the product through the generic path (its lone scalar + // goes through `emit_const`, which falls back to a u16 slot). + if self.product_add_fits_u8_slot(&fused) { + self.emit_product_add(fused); + } else { + self.emit_expr(product); + self.op_binary(Q_OP_ADD); + } true } + /// Whether the fused product-add scalar (if any) still lands in a one-byte + /// constant slot given the current constant table. + fn product_add_fits_u8_slot(&self, product: &QuotientProductAdd) -> bool { + match *product { + QuotientProductAdd::MemMemConstU8 { scalar, .. } + | QuotientProductAdd::ConstU8Mem { scalar, .. } => self.const_fits_u8_slot(scalar), + QuotientProductAdd::MemMem { .. } => true, + } + } + /// Recognize product leaves that can be encoded as one fused add-mul op. /// /// The fused forms require literal `u16` memory pointers and, where a @@ -1641,6 +1714,17 @@ impl QuotientProgramBuilder { } } + /// Reserve coefficient slots before another expression can grow the table. + fn reserve_product_add_consts(&mut self, product: QuotientProductAdd) { + match product { + QuotientProductAdd::MemMemConstU8 { scalar, .. } + | QuotientProductAdd::ConstU8Mem { scalar, .. } => { + self.const_slot(scalar); + } + QuotientProductAdd::MemMem { .. } => {} + } + } + /// Emit one fused add-mul accumulator operation. fn emit_product_add(&mut self, product: QuotientProductAdd) { match product { @@ -1969,6 +2053,148 @@ pub(crate) fn validate_quotient_program(bytes: &[u8]) -> Result { Ok(max_stack) } +/// Bounds-check every constant-table slot referenced by a finalized program. +/// +/// `validate_quotient_program` proves structural and stack safety but never +/// checks that decoded const-table indices fall inside the emitted table. An +/// encoder/planner regression that emits an out-of-range slot (this class has +/// already produced one real bug) would otherwise make the deployed verifier +/// load an arbitrary trailing VK word as a gate coefficient, silently flipping +/// accept/reject. Run this after `validate_quotient_program`, whose byte-length +/// validation guarantees the layout walked here is already in bounds. +pub(crate) fn validate_quotient_const_slots(bytes: &[u8], const_len: usize) -> Result<(), String> { + let check = |slot: usize, idx: usize| -> Result<(), String> { + if slot >= const_len { + return Err(format!( + "quotient VM const slot {slot} at byte {idx} is outside the {const_len}-entry constant table" + )); + } + Ok(()) + }; + let limb_stride = 1 + QUOTIENT_VM_BYTE_U16_BYTES; + + for (idx, op, _len) in quotient_bytecode_ops(bytes) { + match op { + Q_OP_PUSH_CONST | Q_OP_ADD_CONST | Q_OP_MUL_CONST => { + check(read_u16(bytes, idx + 1) as usize, idx)?; + } + Q_OP_PUSH_CONST_U8 | Q_OP_ADD_CONST_U8 | Q_OP_MUL_CONST_U8 => { + check(bytes[idx + 1] as usize, idx)?; + } + Q_OP_ADD_MUL_CONST_U8_MEM_U16 => { + check(bytes[idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + } + Q_OP_ADD_MUL_MEM_MEM_CONST_U8 => { + check( + bytes[idx + 1 + 2 * QUOTIENT_VM_BYTE_U16_BYTES] as usize, + idx, + )?; + } + Q_OP_RUN_ADD_MUL_CONST_U8_MEM_U16 => { + let count = read_u16(bytes, idx + 1) as usize; + let base = idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES; + let stride = QUOTIENT_VM_BYTE_U16_BYTES + 1; + for k in 0..count { + check( + bytes[base + k * stride + QUOTIENT_VM_BYTE_U16_BYTES] as usize, + idx, + )?; + } + } + Q_OP_RUN_ADD_MUL_MEM_MEM_CONST_U8 => { + let count = read_u16(bytes, idx + 1) as usize; + let base = idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES; + let stride = 2 * QUOTIENT_VM_BYTE_U16_BYTES + 1; + for k in 0..count { + check( + bytes[base + k * stride + 2 * QUOTIENT_VM_BYTE_U16_BYTES] as usize, + idx, + )?; + } + } + Q_OP_LIN7 => { + for k in 0..QUOTIENT_VM_LIMBS { + check(bytes[idx + 1 + k * limb_stride] as usize, idx)?; + } + } + Q_OP_BILIN7_ROW => { + let base = idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES; + for k in 0..QUOTIENT_VM_LIMBS { + check(bytes[base + k * limb_stride] as usize, idx)?; + } + } + Q_OP_BILIN7_PAIRWISE => { + let base = idx + 1 + 2 * QUOTIENT_VM_BYTE_U16_BYTES; + for k in 0..QUOTIENT_VM_PAIRWISE_COEFFS { + check(bytes[base + k] as usize, idx)?; + } + } + Q_OP_AFFINE_SUM => { + let lin_count = read_u16(bytes, idx + 1) as usize; + let product_count = read_u16(bytes, idx + 1 + QUOTIENT_VM_BYTE_U16_BYTES) as usize; + let mut cursor = idx + 1 + 2 * QUOTIENT_VM_BYTE_U16_BYTES; + for _ in 0..lin_count { + check(bytes[cursor + QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + cursor += QUOTIENT_VM_BYTE_U16_BYTES + 1; + } + for _ in 0..product_count { + check(bytes[cursor + 2 * QUOTIENT_VM_BYTE_U16_BYTES] as usize, idx)?; + cursor += 2 * QUOTIENT_VM_BYTE_U16_BYTES + 1; + } + } + Q_OP_MODARITH7 => { + let mut cursor = idx + 1; + let flags = bytes[cursor]; + cursor += 1; + if flags & Q_MODARITH7_FLAG_COND != 0 { + cursor += QUOTIENT_VM_BYTE_U16_BYTES; + } + if flags & Q_MODARITH7_FLAG_CONST != 0 { + check(bytes[cursor] as usize, idx)?; + cursor += 1; + } + let lin_count = bytes[cursor] as usize; + let row_count = bytes[cursor + 1] as usize; + let pairwise_count = bytes[cursor + 2] as usize; + let mem_count = bytes[cursor + 3] as usize; + let product_count = bytes[cursor + 4] as usize; + cursor += 5; + for _ in 0..lin_count { + for _ in 0..QUOTIENT_VM_LIMBS { + check(bytes[cursor] as usize, idx)?; + cursor += limb_stride; + } + } + for _ in 0..row_count { + cursor += QUOTIENT_VM_BYTE_U16_BYTES; + for _ in 0..QUOTIENT_VM_LIMBS { + check(bytes[cursor] as usize, idx)?; + cursor += limb_stride; + } + } + for _ in 0..pairwise_count { + cursor += 2 * QUOTIENT_VM_BYTE_U16_BYTES; + for _ in 0..QUOTIENT_VM_PAIRWISE_COEFFS { + check(bytes[cursor] as usize, idx)?; + cursor += 1; + } + } + for _ in 0..mem_count { + check(bytes[cursor] as usize, idx)?; + cursor += limb_stride; + } + for _ in 0..product_count { + check(bytes[cursor] as usize, idx)?; + cursor += 1 + 2 * QUOTIENT_VM_BYTE_U16_BYTES; + } + } + _ => {} + } + } + + Ok(()) +} + /// Decode one instruction and validate token operands. fn decode_byte_quotient_instruction(bytes: &[u8], idx: usize) -> Result<(u8, usize), String> { require_quotient_bytes(bytes, idx, 1, "opcode")?; @@ -2406,6 +2632,21 @@ impl QuotientExpressionEnv for DataQuotientExpressionEnv<'_> { .expect("committed instance eval present"), ) } else { + // The builder rejects rotated instance queries before lowering, so + // the direct public-input column always uses the one local + // Lagrange evaluation computed for `Rotation::cur()`. + // + // Hard assert (not debug_assert): `debug_assert` compiles out in + // release, and this is the last line of defense far from the + // constructor guard (builder/api.rs). Substituting the Rotation::cur + // eval for a rotated query would silently emit a verifier that + // evaluates the gate with instance(x) instead of instance(x*w^k), + // enforcing a different quotient identity than the circuit. Fail + // closed, matching word_to_quotient_expr / ptr_to_quotient_mem. + assert_eq!( + rotation, 0, + "rotated public instance query reached lowering" + ); word_to_quotient_expr(self.data.instance_eval) } } @@ -2435,17 +2676,40 @@ pub(crate) fn ptr_to_quotient_mem(ptr: Ptr) -> QuotientMem { ); match ptr.value() { Value::Integer(offset) => { - assert!(offset >= 0, "negative quotient memory pointer"); - QuotientMem::Literal(offset as u32) + // Checked conversion: Value offsets are isize, so `offset as u32` + // would silently wrap for a negative or > u32::MAX offset and make + // the VM mload an unrelated address (a verifier computing the + // quotient numerator from the wrong memory word, with no build-time + // diagnostic). Fail loudly instead, like the other narrowing casts + // in this file (u16::try_from, u8::try_from). + let offset = u32::try_from(offset) + .expect("quotient memory pointer must be a non-negative offset that fits in u32"); + // Every address the quotient VM reads is a 32-byte word slot (the + // layout allocates in WORD_BYTES units and all eval/challenge/VK/ + // scratch handles are word multiples). A non-word-aligned literal + // pointer signals a truncated/mis-encoded address that would make + // the VM mload a straddling window; reject it at the single + // construction choke point rather than emit a corrupt verifier. + assert!( + offset as usize % WORD_BYTES == 0, + "quotient memory pointer {offset:#x} is not 32-byte word aligned" + ); + QuotientMem::Literal(offset) } Value::Identifier(name, offset) => { - assert!(offset >= 0, "negative quotient memory token offset"); + let offset = u32::try_from(offset).expect( + "quotient memory token offset must be a non-negative offset that fits in u32", + ); + assert!( + offset as usize % WORD_BYTES == 0, + "quotient memory token offset {offset:#x} is not 32-byte word aligned" + ); let token = quotient_mem_token_from_name(name) .unwrap_or_else(|| panic!("unsupported quotient memory token: {name}")); if offset == 0 { QuotientMem::Token(token) } else { - QuotientMem::TokenOffset(token, offset as u32) + QuotientMem::TokenOffset(token, offset) } } } @@ -2654,7 +2918,7 @@ pub(crate) fn quotient_pow5_base(expr: &QuotientExpr) -> Option<&QuotientExpr> { /// Extract one limb shape from a larger affine sum and return the residue. pub(crate) fn quotient_limb_subshape( expr: &QuotientExpr, -) -> Option<(QuotientLimbShape, QuotientExpr)> { +) -> Option<(QuotientLimbShape, QuotientExpr, Vec)> { let mut terms = Vec::new(); let mut constant = Fq::ZERO; if !collect_quotient_affine_terms(expr, Fq::ONE, &mut terms, &mut constant) { @@ -2672,15 +2936,23 @@ pub(crate) fn quotient_limb_subshape( return None; } + // Split the affine terms into the residue (unused terms plus the constant) + // and the matched terms that reconstruct `shape` as a plain sum. The matched + // terms are the panic-free fallback for `emit_limb_shape`: their sum equals + // the fused opcode's value, but each is a single scaled load/product that + // uses u16-capable constant loads. let used = used.into_iter().collect::>(); let mut residue = QuotientExpr::Const(quotient_fq_to_u256(constant)); + let mut matched = Vec::with_capacity(used.len()); for (idx, (coeff, term)) in terms.into_iter().enumerate() { + let scaled = quotient_scaled_term_expr(coeff, (*term).clone()); if used.contains(&idx) { - continue; + matched.push(scaled); + } else { + residue = quotient_sum_expr(residue, scaled); } - residue = quotient_sum_expr(residue, quotient_scaled_term_expr(coeff, (*term).clone())); } - Some((shape, residue)) + Some((shape, residue, matched)) } /// Recognize a whole affine foreign-field/ECC identity that can be evaluated diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs new file mode 100644 index 000000000..ceb954afb --- /dev/null +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/vm/reference.rs @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: CC0-1.0 +//! Independent reference interpreter for finalized quotient VM bytecode. +//! +//! This is the second implementation of the quotient VM ABI. It exists so the +//! generator can certify its own output: for every render, the emitted bytecode +//! is executed here and compared against direct evaluation of the +//! [`QuotientExpr`] trees the bytecode was lowered from. A disagreement means +//! the emitter, one of its shape recognizers, or the run-compaction pass +//! miscompiled an identity, and the render is rejected before any artifact is +//! produced. +//! +//! Why a random-assignment check is sufficient: the certifier samples a +//! deterministic challenge from the finalized quotient bytecode, constant +//! table, oracle expression trees, and VK payload, then evaluates the fixed +//! program at that assignment. Any miscompilation therefore yields a wrong +//! polynomial that was fixed before the challenge was known, and it disagrees +//! with the correct one with probability `1 - deg/|Fr|`. One challenge-derived +//! evaluation is enough to catch it with overwhelming probability. +//! +//! Scope. This certifies the **emitter to reference-interpreter** leg, which is +//! where the shape recognizers in the parent module live. The +//! **reference-interpreter to Yul** leg is covered separately by the opcode and +//! memory-token table conformance tests and by the per-identity Rust/Solidity +//! trace differential on fixture circuits. Identities executed as inline Yul, +//! native callbacks, or the structured tail are not lowered to bytecode at all, +//! so this module evaluates them directly from their expression trees and they +//! remain covered only by those other two mechanisms. + +use std::collections::HashMap; + +use ff::{Field, PrimeField}; +use midnight_curves::Fq; +use ruint::aliases::U256; +use sha3::{Digest, Keccak256}; + +use super::{ + QuotientExpr, QuotientMem, QUOTIENT_VM_LIMBS, QUOTIENT_VM_PAIRWISE_COEFFS, + Q_MODARITH7_FLAG_COND, Q_MODARITH7_FLAG_CONST, Q_OP_ADD, Q_OP_ADD_CONST, Q_OP_ADD_CONST_U8, + Q_OP_ADD_MEM_U16, Q_OP_ADD_MUL_CONST_U8_MEM_U16, Q_OP_ADD_MUL_MEM_MEM, + Q_OP_ADD_MUL_MEM_MEM_CONST_U8, Q_OP_AFFINE_SUM, Q_OP_BILIN7_PAIRWISE, Q_OP_BILIN7_ROW, + Q_OP_FOLD_MAIN, Q_OP_FOLD_SELECTOR, Q_OP_LIN7, Q_OP_MODARITH7, Q_OP_MUL, Q_OP_MUL_CONST, + Q_OP_MUL_CONST_U8, Q_OP_MUL_MEM_U16, Q_OP_NATIVE_IDENTITY, Q_OP_NATIVE_LOOKUP, + Q_OP_NATIVE_PERMUTATION, Q_OP_NEG, Q_OP_POW5, Q_OP_PUSH_CONST, Q_OP_PUSH_CONST_U8, + Q_OP_PUSH_MEM_LITERAL, Q_OP_PUSH_MEM_TOKEN, Q_OP_PUSH_MEM_TOKEN_OFFSET, Q_OP_PUSH_MEM_U16, + Q_OP_RUN_ADD_MUL_CONST_U8_MEM_U16, Q_OP_RUN_ADD_MUL_MEM_MEM_CONST_U8, +}; +use crate::lowering::layout::WORD_BYTES; + +/// Deterministic pseudorandom assignment for every verifier memory slot. +/// +/// The bytecode addresses memory by absolute pointer or by symbolic token, and +/// the expression trees address the exact same slots. Deriving each value from +/// its own address means both sides observe identical memory without the +/// certifier having to enumerate the live address set up front, which in turn +/// means a pointer-packing bug shows up as a value mismatch rather than as a +/// missing map key. +#[derive(Clone, Debug)] +pub(crate) struct QuotientRefMemory { + seed: [u8; 32], + cache: HashMap<(u8, u32), Fq>, +} + +impl QuotientRefMemory { + /// Build an assignment for one certification run. + pub(crate) fn new(seed: [u8; 32]) -> Self { + Self { + seed, + cache: HashMap::new(), + } + } + + /// Value at an absolute memory pointer. + pub(crate) fn literal(&mut self, ptr: u32) -> Fq { + self.derive(0, ptr) + } + + /// Value behind a symbolic memory token, optionally offset. + pub(crate) fn token(&mut self, token: u8, offset: u32) -> Fq { + // Tokens resolve to generated addresses disjoint from the literal + // pointer space, so they get their own domain tag. + self.derive(1 + token, offset) + } + + /// Derive one field element from a domain-separated address. + fn derive(&mut self, domain: u8, address: u32) -> Fq { + if let Some(value) = self.cache.get(&(domain, address)) { + return *value; + } + let mut hasher = Keccak256::new(); + hasher.update(b"midfall/quotient-vm/reference-memory/v1"); + hasher.update(self.seed); + hasher.update([domain]); + hasher.update(address.to_be_bytes()); + let lo = hasher.finalize(); + + let mut hasher = Keccak256::new(); + hasher.update(b"midfall/quotient-vm/reference-memory/v1/hi"); + hasher.update(lo); + let hi = hasher.finalize(); + + let mut wide = [0u8; 64]; + wide[..32].copy_from_slice(&lo); + wide[32..].copy_from_slice(&hi); + let value = >::from_uniform_bytes(&wide); + self.cache.insert((domain, address), value); + value + } +} + +/// Evaluate a typed quotient expression directly, without going through the VM. +/// +/// This is the oracle side of the certification: it follows the same shape as +/// `Expression::evaluate` in the native verifier and knows nothing about +/// opcodes, shape recognizers, or operand packing. +pub(crate) fn eval_quotient_expr(expr: &QuotientExpr, mem: &mut QuotientRefMemory) -> Fq { + match expr { + QuotientExpr::Const(value) => fq_from_u256(*value), + QuotientExpr::Mem(QuotientMem::Literal(ptr)) => mem.literal(*ptr), + QuotientExpr::Mem(QuotientMem::Token(token)) => mem.token(*token, 0), + QuotientExpr::Mem(QuotientMem::TokenOffset(token, offset)) => mem.token(*token, *offset), + QuotientExpr::Add(lhs, rhs) => eval_quotient_expr(lhs, mem) + eval_quotient_expr(rhs, mem), + QuotientExpr::Mul(lhs, rhs) => eval_quotient_expr(lhs, mem) * eval_quotient_expr(rhs, mem), + QuotientExpr::Neg(inner) => -eval_quotient_expr(inner, mem), + } +} + +/// Evaluate one identity expression subprogram, up to but excluding its fold. +/// +/// Returns an error rather than panicking: this runs inside the generator, and +/// a malformed stream must surface as a `GeneratorError`, not a process abort. +pub(crate) fn eval_quotient_identity( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, +) -> Result { + let mut stack: Vec = Vec::new(); + let mut idx = 0usize; + + // Pop helpers keep the error path uniform; the offline validator has + // already proven depth safety, so these only fire on validator drift. + macro_rules! pop { + () => { + stack + .pop() + .ok_or_else(|| format!("reference VM stack underflow at byte {idx}"))? + }; + } + + while idx < bytes.len() { + let op = bytes[idx]; + match op { + Q_OP_PUSH_CONST => { + let slot = read_u16(bytes, idx + 1)? as usize; + stack.push(const_at(consts, slot, idx)?); + idx += 3; + } + Q_OP_PUSH_CONST_U8 => { + let slot = byte_at(bytes, idx + 1)? as usize; + stack.push(const_at(consts, slot, idx)?); + idx += 2; + } + Q_OP_PUSH_MEM_LITERAL => { + let ptr = read_u32(bytes, idx + 1)?; + stack.push(mem.literal(ptr)); + idx += 5; + } + Q_OP_PUSH_MEM_U16 => { + let ptr = read_u16(bytes, idx + 1)? as u32; + stack.push(mem.literal(ptr)); + idx += 3; + } + Q_OP_PUSH_MEM_TOKEN => { + let token = byte_at(bytes, idx + 1)?; + stack.push(mem.token(token, 0)); + idx += 2; + } + Q_OP_PUSH_MEM_TOKEN_OFFSET => { + let token = byte_at(bytes, idx + 1)?; + let offset = read_u32(bytes, idx + 2)?; + stack.push(mem.token(token, offset)); + idx += 6; + } + Q_OP_ADD => { + let rhs = pop!(); + let lhs = pop!(); + stack.push(lhs + rhs); + idx += 1; + } + Q_OP_MUL => { + let rhs = pop!(); + let lhs = pop!(); + stack.push(lhs * rhs); + idx += 1; + } + Q_OP_NEG => { + let value = pop!(); + stack.push(-value); + idx += 1; + } + Q_OP_POW5 => { + let value = pop!(); + let squared = value * value; + stack.push(value * squared * squared); + idx += 1; + } + Q_OP_ADD_CONST_U8 | Q_OP_MUL_CONST_U8 => { + let slot = byte_at(bytes, idx + 1)? as usize; + let value = const_at(consts, slot, idx)?; + let acc = pop!(); + stack.push(if op == Q_OP_ADD_CONST_U8 { + acc + value + } else { + acc * value + }); + idx += 2; + } + Q_OP_ADD_CONST | Q_OP_MUL_CONST => { + let slot = read_u16(bytes, idx + 1)? as usize; + let value = const_at(consts, slot, idx)?; + let acc = pop!(); + stack.push(if op == Q_OP_ADD_CONST { + acc + value + } else { + acc * value + }); + idx += 3; + } + Q_OP_ADD_MEM_U16 | Q_OP_MUL_MEM_U16 => { + let ptr = read_u16(bytes, idx + 1)? as u32; + let value = mem.literal(ptr); + let acc = pop!(); + stack.push(if op == Q_OP_ADD_MEM_U16 { + acc + value + } else { + acc * value + }); + idx += 3; + } + Q_OP_ADD_MUL_MEM_MEM_CONST_U8 => { + let acc = pop!(); + let (term, len) = affine_product_term(bytes, consts, mem, idx + 1)?; + stack.push(acc + term); + idx += 1 + len; + } + Q_OP_ADD_MUL_CONST_U8_MEM_U16 => { + let acc = pop!(); + let (term, len) = affine_linear_term(bytes, consts, mem, idx + 1)?; + stack.push(acc + term); + idx += 1 + len; + } + Q_OP_ADD_MUL_MEM_MEM => { + let lhs = read_u16(bytes, idx + 1)? as u32; + let rhs = read_u16(bytes, idx + 3)? as u32; + let acc = pop!(); + stack.push(acc + mem.literal(lhs) * mem.literal(rhs)); + idx += 5; + } + Q_OP_RUN_ADD_MUL_MEM_MEM_CONST_U8 | Q_OP_RUN_ADD_MUL_CONST_U8_MEM_U16 => { + // Run compaction is a pure encoding change: the same terms in + // the same order, with one shared count instead of repeated + // opcode bytes. + let count = read_u16(bytes, idx + 1)? as usize; + if count == 0 { + return Err(format!("reference VM zero-length run at byte {idx}")); + } + let mut cursor = idx + 3; + let mut acc = pop!(); + for _ in 0..count { + let (term, len) = if op == Q_OP_RUN_ADD_MUL_MEM_MEM_CONST_U8 { + affine_product_term(bytes, consts, mem, cursor)? + } else { + affine_linear_term(bytes, consts, mem, cursor)? + }; + acc += term; + cursor += len; + } + stack.push(acc); + idx = cursor; + } + Q_OP_AFFINE_SUM => { + // Mixed run: all linear terms first, then all product terms, + // matching `compact_quotient_runs`. + let lin_count = read_u16(bytes, idx + 1)? as usize; + let product_count = read_u16(bytes, idx + 3)? as usize; + if lin_count == 0 || product_count == 0 { + return Err(format!( + "reference VM AFFINE_SUM at byte {idx} requires nonzero counts" + )); + } + let mut cursor = idx + 5; + let mut acc = pop!(); + for _ in 0..lin_count { + let (term, len) = affine_linear_term(bytes, consts, mem, cursor)?; + acc += term; + cursor += len; + } + for _ in 0..product_count { + let (term, len) = affine_product_term(bytes, consts, mem, cursor)?; + acc += term; + cursor += len; + } + stack.push(acc); + idx = cursor; + } + Q_OP_LIN7 => { + let (value, len) = limb_linear_form(bytes, consts, mem, idx + 1)?; + stack.push(value); + idx += 1 + len; + } + Q_OP_BILIN7_ROW => { + let (value, len) = limb_row_form(bytes, consts, mem, idx + 1)?; + stack.push(value); + idx += 1 + len; + } + Q_OP_BILIN7_PAIRWISE => { + let (value, len) = limb_pairwise_form(bytes, consts, mem, idx + 1)?; + stack.push(value); + idx += 1 + len; + } + Q_OP_MODARITH7 => { + let (value, len) = modarith7_form(bytes, consts, mem, idx + 1)?; + stack.push(value); + idx += 1 + len; + } + Q_OP_FOLD_MAIN + | Q_OP_FOLD_SELECTOR + | Q_OP_NATIVE_PERMUTATION + | Q_OP_NATIVE_LOOKUP + | Q_OP_NATIVE_IDENTITY => { + return Err(format!( + "reference VM found stream opcode {op:#x} inside an identity expression at byte {idx}" + )); + } + _ => { + return Err(format!("reference VM unknown opcode {op:#x} at byte {idx}")); + } + } + } + + if stack.len() != 1 { + return Err(format!( + "reference VM identity left {} value(s) on the stack, expected 1", + stack.len() + )); + } + Ok(stack.pop().expect("checked length")) +} + +/// Decode one `const * mload(ptr)` term and return its byte length. +fn affine_linear_term( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let ptr = read_u16(bytes, idx)? as u32; + let slot = byte_at(bytes, idx + 2)? as usize; + Ok((const_at(consts, slot, idx)? * mem.literal(ptr), 3)) +} + +/// Decode one `mload(lhs) * mload(rhs) * const` term and return its byte length. +fn affine_product_term( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let lhs = read_u16(bytes, idx)? as u32; + let rhs = read_u16(bytes, idx + 2)? as u32; + let slot = byte_at(bytes, idx + 4)? as usize; + Ok(( + mem.literal(lhs) * mem.literal(rhs) * const_at(consts, slot, idx)?, + 5, + )) +} + +/// Decode a seven-limb linear form `sum_i const_i * mload(ptr_i)`. +fn limb_linear_form( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let mut acc = Fq::ZERO; + let mut cursor = idx; + for _ in 0..QUOTIENT_VM_LIMBS { + let slot = byte_at(bytes, cursor)? as usize; + let ptr = read_u16(bytes, cursor + 1)? as u32; + acc += const_at(consts, slot, cursor)? * mem.literal(ptr); + cursor += 3; + } + Ok((acc, cursor - idx)) +} + +/// Decode `mload(lhs) * sum_i const_i * mload(rhs_i)`. +fn limb_row_form( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let lhs = read_u16(bytes, idx)? as u32; + let lhs_value = mem.literal(lhs); + let (inner, len) = limb_linear_form(bytes, consts, mem, idx + 2)?; + Ok((lhs_value * inner, 2 + len)) +} + +/// Decode a 7x7 pairwise product with `i + j` indexed coefficients. +fn limb_pairwise_form( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let lhs_base = read_u16(bytes, idx)? as u32; + let rhs_base = read_u16(bytes, idx + 2)? as u32; + let coeff_idx = idx + 4; + // Bounds-check the whole coefficient block once so the inner loop cannot + // read past the program. + byte_at(bytes, coeff_idx + QUOTIENT_VM_PAIRWISE_COEFFS - 1)?; + + let mut acc = Fq::ZERO; + for i in 0..QUOTIENT_VM_LIMBS { + let lhs = mem.literal(lhs_base + (i as u32) * WORD_BYTES as u32); + for j in 0..QUOTIENT_VM_LIMBS { + let rhs = mem.literal(rhs_base + (j as u32) * WORD_BYTES as u32); + let slot = bytes[coeff_idx + i + j] as usize; + acc += lhs * rhs * const_at(consts, slot, coeff_idx)?; + } + } + Ok((acc, 4 + QUOTIENT_VM_PAIRWISE_COEFFS)) +} + +/// Decode the dynamic mixed seven-limb affine form. +fn modarith7_form( + bytes: &[u8], + consts: &[U256], + mem: &mut QuotientRefMemory, + idx: usize, +) -> Result<(Fq, usize), String> { + let mut cursor = idx; + let flags = byte_at(bytes, cursor)?; + if flags & !(Q_MODARITH7_FLAG_COND | Q_MODARITH7_FLAG_CONST) != 0 { + return Err(format!( + "reference VM MODARITH7 unknown flag bits {flags:#x} at byte {idx}" + )); + } + cursor += 1; + + let cond = if flags & Q_MODARITH7_FLAG_COND != 0 { + let ptr = read_u16(bytes, cursor)? as u32; + cursor += 2; + Some(ptr) + } else { + None + }; + + let mut acc = Fq::ZERO; + if flags & Q_MODARITH7_FLAG_CONST != 0 { + let slot = byte_at(bytes, cursor)? as usize; + acc += const_at(consts, slot, cursor)?; + cursor += 1; + } + + let lin_count = byte_at(bytes, cursor)? as usize; + let row_count = byte_at(bytes, cursor + 1)? as usize; + let pairwise_count = byte_at(bytes, cursor + 2)? as usize; + let mem_count = byte_at(bytes, cursor + 3)? as usize; + let product_count = byte_at(bytes, cursor + 4)? as usize; + cursor += 5; + + for _ in 0..lin_count { + let (value, len) = limb_linear_form(bytes, consts, mem, cursor)?; + acc += value; + cursor += len; + } + for _ in 0..row_count { + let (value, len) = limb_row_form(bytes, consts, mem, cursor)?; + acc += value; + cursor += len; + } + for _ in 0..pairwise_count { + let (value, len) = limb_pairwise_form(bytes, consts, mem, cursor)?; + acc += value; + cursor += len; + } + for _ in 0..mem_count { + // Note the operand order here is const-slot first, unlike the standalone + // `ADD_MUL_CONST_U8_MEM_U16` term, so this cannot reuse the helper. + let slot = byte_at(bytes, cursor)? as usize; + let ptr = read_u16(bytes, cursor + 1)? as u32; + acc += const_at(consts, slot, cursor)? * mem.literal(ptr); + cursor += 3; + } + for _ in 0..product_count { + let slot = byte_at(bytes, cursor)? as usize; + let lhs = read_u16(bytes, cursor + 1)? as u32; + let rhs = read_u16(bytes, cursor + 3)? as u32; + acc += const_at(consts, slot, cursor)? * mem.literal(lhs) * mem.literal(rhs); + cursor += 5; + } + + if let Some(cond) = cond { + acc *= mem.literal(cond); + } + Ok((acc, cursor - idx)) +} + +/// Read one byte with an explicit bounds error. +fn byte_at(bytes: &[u8], idx: usize) -> Result { + bytes + .get(idx) + .copied() + .ok_or_else(|| format!("reference VM read past end of program at byte {idx}")) +} + +/// Read a big-endian `u16` operand. +fn read_u16(bytes: &[u8], idx: usize) -> Result { + Ok(u16::from_be_bytes([ + byte_at(bytes, idx)?, + byte_at(bytes, idx + 1)?, + ])) +} + +/// Read a big-endian `u32` operand. +fn read_u32(bytes: &[u8], idx: usize) -> Result { + Ok(u32::from_be_bytes([ + byte_at(bytes, idx)?, + byte_at(bytes, idx + 1)?, + byte_at(bytes, idx + 2)?, + byte_at(bytes, idx + 3)?, + ])) +} + +/// Look up a constant-table slot with an explicit bounds error. +fn const_at(consts: &[U256], slot: usize, idx: usize) -> Result { + consts + .get(slot) + .copied() + .map(fq_from_u256) + .ok_or_else(|| format!("reference VM constant slot {slot} out of range at byte {idx}")) +} + +/// Convert a canonical `U256` constant-table entry into Fr. +fn fq_from_u256(value: U256) -> Fq { + let bytes = value.to_le_bytes::<32>(); + let repr = ::Repr::from(bytes); + Option::::from(Fq::from_repr(repr)).expect("constant table holds canonical field elements") +} diff --git a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs index bc8242de0..1b68231be 100644 --- a/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs +++ b/proofs/solidity-verifier/src/lowering/quotient_numerator/yul_emit.rs @@ -456,6 +456,16 @@ impl<'a> Evaluator<'a> { // `(Vec, String)` entries. let selector_expr = chunked.selector_expression(); + // Fail closed on a chunk/helper-eval count mismatch: zip would + // otherwise silently drop the excess chunks, removing helper + // constraints from the numerator. + assert_eq!( + chunked.input_expression_chunks().len(), + h_evals.len(), + "lookup {lookup_idx}: input chunk count {} != helper eval count {}", + chunked.input_expression_chunks().len(), + h_evals.len(), + ); for (input_chunk, h_eval) in chunked.input_expression_chunks().iter().zip(h_evals.iter()) { @@ -479,10 +489,17 @@ impl<'a> Evaluator<'a> { let k = f_plus_beta_vars.len(); if k == 0 { - // Empty chunk shouldn't happen but emit a no-op. - let zero = self.fresh_var(); - lines.push(format!("let {zero} := 0")); - out.push((lines, zero)); + // Unreachable today (BatchedArgument::new requires >= 1 + // parallel lookup and slice::chunks never yields an empty + // chunk), but emit the reference-faithful value rather than + // 0 so a future chunking change cannot silently drop the + // constraint. For an empty chunk the native verifier + // (plonk/logup.rs) computes helper_eval * (empty product = 1) + // - (empty sum = 0) = helper_eval, enforcing h == 0. Emitting + // 0 would leave h unconstrained while the accumulator still + // folds this h_eval into sum_h, letting a prover forge lookup + // balance. + out.push((lines, h_eval.to_string())); continue; } @@ -571,11 +588,18 @@ impl<'a> Evaluator<'a> { let beta = self.fresh_var(); lines.push(format!("let {beta} := mload(BETA_MPTR)")); - // Σ_h h_eval[c] + // Σ_h h_eval[c]. Empty for a lookup with no input expressions + // (zero helper chunks); the native verifier's sum_helpers folds + // over an empty set to 0, so mirror that instead of indexing + // h_evals[0] out of bounds and panicking at codegen. let sum_h = self.fresh_var(); - lines.push(format!("let {sum_h} := {}", h_evals[0])); - for h in &h_evals[1..] { - lines.push(format!("{sum_h} := addmod({sum_h}, {h}, r)")); + if let Some((first, rest)) = h_evals.split_first() { + lines.push(format!("let {sum_h} := {first}")); + for h in rest { + lines.push(format!("{sum_h} := addmod({sum_h}, {h}, r)")); + } + } else { + lines.push(format!("let {sum_h} := 0")); } // selector eval (full Expression; not necessarily a @@ -788,6 +812,14 @@ impl<'a> Evaluator<'a> { .get(&(column_index, query.rotation().0)) .copied()? } else { + // The non-committed public-input column only has its local + // Rotation::cur() interpolation at INSTANCE_EVAL_MPTR. + // Decline to treat a rotated query as a direct memory + // pointer; the constructor rejects rotated instance queries + // and instance_eval_at hard-asserts rotation == 0. + if query.rotation().0 != 0 { + return None; + } self.data.instance_eval } } @@ -1103,8 +1135,18 @@ impl<'a> Evaluator<'a> { .to_string() } else { // The current public API supports one non-committed instance - // column, whose Lagrange-combined evaluation is computed by - // the template prologue and stored at INSTANCE_EVAL_MPTR. + // column, whose Lagrange-combined evaluation is computed by the + // template prologue and stored at INSTANCE_EVAL_MPTR for + // Rotation::cur() only. Hard-assert (not debug_assert) so a + // rotated query that ever bypasses the far-away constructor guard + // (builder/api.rs) fails closed in release builds too, instead of + // silently evaluating instance(x) in place of instance(x*omega^k) + // and generating a verifier that checks a different identity than + // the native Midfall verifier. + assert_eq!( + rotation, 0, + "rotated public instance query reached Yul quotient emission" + ); self.data.instance_eval.to_string() } } @@ -1141,7 +1183,7 @@ fn u256_string(value: U256) -> String { /// Stable variable name for a column evaluation and rotation. fn column_eval_var(prefix: &'static str, column_index: usize, rotation: i32) -> String { match rotation.cmp(&0) { - Ordering::Less => format!("{prefix}_{column_index}_prev_{}", rotation.abs()), + Ordering::Less => format!("{prefix}_{column_index}_prev_{}", rotation.unsigned_abs()), Ordering::Equal => format!("{prefix}_{column_index}"), Ordering::Greater => format!("{prefix}_{column_index}_next_{rotation}"), } diff --git a/proofs/solidity-verifier/src/lowering/render/models.rs b/proofs/solidity-verifier/src/lowering/render/models.rs index 0ff85f228..58974681b 100644 --- a/proofs/solidity-verifier/src/lowering/render/models.rs +++ b/proofs/solidity-verifier/src/lowering/render/models.rs @@ -10,6 +10,8 @@ use std::fmt; use askama::{Error, Template}; +use group::{prime::PrimeCurveAffine, Curve, Group}; +use midnight_curves::{G1Affine, G1Projective}; use ruint::aliases::U256; use crate::lowering::{ @@ -54,6 +56,14 @@ pub(crate) struct Eip2537TemplateConstants { pub(crate) g1msm_address: usize, pub(crate) pairing_address: usize, pub(crate) smoke_scratch_bytes: usize, + /// BLS12-381 G1 generator in EIP-2537 padded encoding. + /// + /// Used with [`Self::g1_double_generator`] as a known-answer vector for the + /// constructor smoke test: identity-only probes are satisfied by + /// implementations that never do any real curve arithmetic. + pub(crate) g1_generator: G1Words, + /// Twice the BLS12-381 G1 generator, in EIP-2537 padded encoding. + pub(crate) g1_double_generator: G1Words, } /// EIP-198 modexp frame constants rendered into templates. @@ -142,6 +152,12 @@ pub(crate) struct QuotientVmTemplateConstants { pub(crate) limb_pairwise_coeffs: usize, } +/// Convert a G1 point into the tuple form the templates render. +fn g1_words(point: G1Affine) -> G1Words { + let [x_hi, x_lo, y_hi, y_lo] = crate::lowering::encoding::g1_to_u256s(point); + (x_hi, x_lo, y_hi, y_lo) +} + impl Default for TemplateConstants { /// Build template constants from the Rust-side layout and VM specs. fn default() -> Self { @@ -158,6 +174,11 @@ impl Default for TemplateConstants { g1msm_address: layout::precompile::G1MSM_ADDRESS, pairing_address: layout::precompile::PAIRING_ADDRESS, smoke_scratch_bytes: layout::PAIRING_TWO_PAIR_BYTES, + g1_generator: g1_words(G1Affine::generator()), + g1_double_generator: { + let g = G1Projective::generator(); + g1_words((g + g).to_affine()) + }, }, modexp: ModexpTemplateConstants { address: layout::precompile::MODEXP_ADDRESS, @@ -257,6 +278,10 @@ pub(crate) struct Halo2VerifyingKey { pub(crate) const VK_RUNTIME_PREFIX: u8 = 0xfe; /// Number of bytes skipped before copying the separate VK payload. pub(crate) const VK_RUNTIME_PREFIX_LEN: usize = 1; +/// EIP-170 deployed-contract runtime code-size limit (24576 bytes). The VK is +/// shipped as its own data contract whose runtime is `[INVALID, ...payload]`, +/// so it must fit under this bound to be deployable on EIP-170 chains. +pub(crate) const EIP_170_MAX_RUNTIME_BYTES: usize = 0x6000; impl Halo2VerifyingKey { /// Reconstruct and validate the typed VK payload layout. @@ -306,7 +331,15 @@ impl Halo2VerifyingKey { self.len() )); } - let constructor_memory = VkConstructorMemoryLayout::new(self.runtime_len()); + let runtime_len = self.runtime_len(); + if runtime_len > EIP_170_MAX_RUNTIME_BYTES { + return Err(format!( + "VK runtime code size {runtime_len} bytes exceeds the EIP-170 limit of \ + {EIP_170_MAX_RUNTIME_BYTES} bytes; the VK data contract would revert at \ + deployment. Reduce the circuit's constant/commitment count." + )); + } + let constructor_memory = VkConstructorMemoryLayout::new(runtime_len); constructor_memory.validate()?; if self.constructor_payload_mptr != constructor_memory.payload_mptr { return Err(format!( @@ -1032,7 +1065,7 @@ mod tests { let memory = VerifierMemoryLayout::new( &ConstraintSystemMeta::default(), &synthetic_vk(0, 0), - Ptr::memory(0x1000), + Ptr::memory(0x2000), VerifierMemoryLayoutConfig::default(), ); let mut proof = ProofReadPlan::default(); @@ -1086,9 +1119,9 @@ mod tests { }, memory, vk_header: Default::default(), - vk_mptr: Ptr::memory(0x1000), - challenge_mptr: Ptr::memory(0x1200), - theta_mptr: Ptr::memory(0x1300), + vk_mptr: Ptr::memory(0x2000), + challenge_mptr: Ptr::memory(0x2200), + theta_mptr: Ptr::memory(0x2300), proof_cptr: Ptr::calldata(proof_cptr), abi_selector_bytes: crate::lowering::layout::abi::SELECTOR_BYTES, abi_proof_head_offset: crate::lowering::layout::abi::VERIFY_PROOF_PROOF_HEAD_OFFSET, diff --git a/proofs/solidity-verifier/src/lowering/tests.rs b/proofs/solidity-verifier/src/lowering/tests.rs index d3ea64048..d4c1c7f25 100644 --- a/proofs/solidity-verifier/src/lowering/tests.rs +++ b/proofs/solidity-verifier/src/lowering/tests.rs @@ -278,6 +278,231 @@ impl Circuit for LoweringPlanTestCircuit { } } +/// Number of advice columns backing one seven-limb foreign-field shape. +const QUOTIENT_VM_TEST_LIMBS: usize = 7; +/// Gate count chosen to exceed the inline prefix plus the native-gate budget, +/// so identities are left over for the compact VM. +const QUOTIENT_VM_TEST_GATES: usize = + DEFAULT_HYBRID_QUOTIENT_INLINE_IDENTITIES + DEFAULT_QUOTIENT_NATIVE_GATES + 8; + +#[derive(Clone, Debug)] +struct QuotientVmTestConfig { + limbs: [Column; QUOTIENT_VM_TEST_LIMBS], + selector: Selector, +} + +/// Circuit whose quotient identities are numerous enough to reach the VM. +/// +/// `LoweringPlanTestCircuit` has a single gate, so its whole identity stream +/// fits in the inline prefix and the compact VM never runs. This circuit exists +/// so the fast test suite exercises the bytecode path — and therefore the +/// generator's own program certification — on a real `LoweringPlan`. +#[derive(Clone, Debug, Default)] +struct QuotientVmTestCircuit; + +impl Circuit for QuotientVmTestCircuit { + type Config = QuotientVmTestConfig; + type FloorPlanner = SimpleFloorPlanner; + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let limbs: [Column; QUOTIENT_VM_TEST_LIMBS] = + core::array::from_fn(|_| meta.advice_column()); + let selector = meta.selector(); + // The generator only supports one identity-committed plus one + // non-committed instance column, so mirror that shape here. + let committed_instance = meta.instance_column(); + let public_instance = meta.instance_column(); + + meta.create_gate("quotient vm instance balance", |meta| { + let advice = meta.query_advice(limbs[0], Rotation::cur()); + let committed = meta.query_instance(committed_instance, Rotation::cur()); + let public = meta.query_instance(public_instance, Rotation::cur()); + Constraints::without_selector(vec![advice + committed + public]) + }); + + // Seven-limb linear forms: the shape the LIN7 recognizer is built for. + // Distinct per-gate coefficients keep the gates from deduplicating. + for gate in 0..QUOTIENT_VM_TEST_GATES { + meta.create_gate("quotient vm limb form", move |meta| { + let terms = limbs + .iter() + .enumerate() + .map(|(limb, column)| { + let coeff = Fq::from(((gate + 1) * 16 + limb + 1) as u64); + meta.query_advice(*column, Rotation::cur()) * Expression::Constant(coeff) + }) + .reduce(|acc, term| acc + term) + .expect("limb count is nonzero"); + Constraints::without_selector(vec![terms]) + }); + } + + // One simple-selector gate so the selector fold path is covered too. + meta.create_gate("quotient vm selector form", |meta| { + let lhs = meta.query_advice(limbs[0], Rotation::cur()); + let rhs = meta.query_advice(limbs[1], Rotation::cur()); + Constraints::with_selector(selector, vec![("quotient vm selector form", lhs - rhs)]) + }); + + QuotientVmTestConfig { limbs, selector } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), PlonkError> { + layouter.assign_region( + || "quotient vm row", + |mut region| { + config.selector.enable(&mut region, 0)?; + for column in config.limbs { + region.assign_advice(|| "limb", column, 0, || Value::known(Fq::ZERO))?; + } + Ok(()) + }, + ) + } +} + +/// Generate parameters and VK for the VM-exercising lowering-plan tests. +fn quotient_vm_test_vk() -> ( + ParamsKZG, + VerifyingKey>, +) { + let mut rng = ChaCha8Rng::seed_from_u64(11); + let params = ParamsKZG::::unsafe_setup(6, &mut rng); + let circuit = QuotientVmTestCircuit; + let vk = keygen_vk_with_k::, _>(¶ms, &circuit, 6) + .expect("quotient VM test circuit VK should build"); + (params, vk) +} + +/// The generator certifies its own quotient bytecode on a real plan. +/// +/// `LoweringPlan::new` runs `certify_quotient_program` and the dual-build +/// agreement check, both of which panic on mismatch, so simply building the +/// plan is the assertion. The explicit checks below guard against this test +/// silently going vacuous if the planner ever stops routing these identities +/// through the VM. +#[test] +fn lowering_plan_certifies_emitted_quotient_bytecode() { + let (params, vk) = quotient_vm_test_vk(); + let generator = SolidityGenerator::new(¶ms, &vk, GeneratorConfig::new(1, 1)); + let plan = generator.inputs().lowering_plan(); + + let interpreted = plan + .quotient + .plan + .items + .iter() + .filter(|item| matches!(item, QuotientProgramItem::Identity(_))) + .count(); + assert!( + interpreted > 0, + "test circuit no longer routes any identity through the compact VM, so the \ + certification path is untested" + ); + assert!( + !plan.quotient.build.bytes.is_empty(), + "interpreted identities should emit bytecode" + ); + assert!( + plan.quotient.build.used_ops.contains(&Q_OP_LIN7), + "test circuit should exercise the seven-limb linear recognizer; used ops: {:?}", + plan.quotient.build.used_ops + ); +} + +#[test] +fn quotient_certification_seed_binds_all_compared_artifacts() { + let build = QuotientProgramBuild { + bytes: vec![Q_OP_PUSH_CONST_U8, 0], + consts: vec![U256::from(11u64)], + max_stack: 1, + used_ops: vec![Q_OP_PUSH_CONST_U8], + used_mem_tokens: Vec::new(), + }; + let baseline = QuotientProgramBuild { + bytes: vec![Q_OP_PUSH_CONST, 0, 0], + consts: vec![U256::from(13u64)], + max_stack: 1, + used_ops: vec![Q_OP_PUSH_CONST], + used_mem_tokens: Vec::new(), + }; + let expr = QuotientExpr::Add( + Box::new(QuotientExpr::Mem(QuotientMem::Literal(0x120))), + Box::new(QuotientExpr::Const(U256::from(17u64))), + ); + let vk_payload = [0xabu8; 64]; + let seed = super::quotient_numerator::vm::certify::derive_certify_seed( + &[&build, &baseline], + &[&expr], + &vk_payload, + ); + + let mut changed_bytecode = build.clone(); + changed_bytecode.bytes[0] ^= 1; + assert_ne!( + seed, + super::quotient_numerator::vm::certify::derive_certify_seed( + &[&changed_bytecode, &baseline], + &[&expr], + &vk_payload, + ) + ); + + let mut changed_const = build.clone(); + changed_const.consts[0] = U256::from(19u64); + assert_ne!( + seed, + super::quotient_numerator::vm::certify::derive_certify_seed( + &[&changed_const, &baseline], + &[&expr], + &vk_payload, + ) + ); + + let mut changed_baseline = baseline.clone(); + changed_baseline.consts[0] = U256::from(23u64); + assert_ne!( + seed, + super::quotient_numerator::vm::certify::derive_certify_seed( + &[&build, &changed_baseline], + &[&expr], + &vk_payload, + ) + ); + + let changed_expr = QuotientExpr::Add( + Box::new(QuotientExpr::Mem(QuotientMem::Literal(0x120))), + Box::new(QuotientExpr::Const(U256::from(29u64))), + ); + assert_ne!( + seed, + super::quotient_numerator::vm::certify::derive_certify_seed( + &[&build, &baseline], + &[&changed_expr], + &vk_payload, + ) + ); + + let changed_vk_payload = [0xcdu8; 64]; + assert_ne!( + seed, + super::quotient_numerator::vm::certify::derive_certify_seed( + &[&build, &baseline], + &[&expr], + &changed_vk_payload, + ) + ); +} + /// Generate parameters and VK for lowering-plan integration tests. fn lowering_plan_test_vk() -> ( ParamsKZG, @@ -291,6 +516,121 @@ fn lowering_plan_test_vk() -> ( (params, vk) } +#[derive(Clone, Debug)] +struct RotatedPublicInstanceCircuit; + +impl Circuit for RotatedPublicInstanceCircuit { + type Config = (); + type FloorPlanner = SimpleFloorPlanner; + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advice = meta.advice_column(); + let committed_instance = meta.instance_column(); + let public_instance = meta.instance_column(); + + meta.create_gate("rotated public instance", |meta| { + let advice = meta.query_advice(advice, Rotation::cur()); + let committed = meta.query_instance(committed_instance, Rotation::cur()); + let public_next = meta.query_instance(public_instance, Rotation::next()); + Constraints::without_selector(vec![( + "rotated public instance", + advice + committed + public_next, + )]) + }); + } + + fn synthesize( + &self, + _config: Self::Config, + _layouter: impl Layouter, + ) -> Result<(), PlonkError> { + Ok(()) + } +} + +#[test] +fn generator_rejects_rotated_non_committed_instance_queries() { + let mut rng = ChaCha8Rng::seed_from_u64(8); + let params = ParamsKZG::::unsafe_setup(4, &mut rng); + let circuit = RotatedPublicInstanceCircuit; + let vk = keygen_vk_with_k::, _>(¶ms, &circuit, 4) + .expect("test circuit VK should build"); + + assert!(matches!( + SolidityGenerator::try_new(¶ms, &vk, GeneratorConfig::new(1, 1)), + Err(GeneratorError::RotatedInstanceQuery { + column: 1, + rotation: 1, + }) + )); +} + +/// Declares two advice columns but queries only one, so the second is absorbed +/// into the transcript without ever being opened by a PCS query. +struct UnopenedAdviceColumnCircuit; + +impl Circuit for UnopenedAdviceColumnCircuit { + type Config = (); + type FloorPlanner = SimpleFloorPlanner; + type Params = (); + + fn without_witnesses(&self) -> Self { + Self + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advice = meta.advice_column(); + // Declared and committed, but never queried: this is the shape + // `ProtocolPlan::validate` rejects. + let _unopened = meta.advice_column(); + let committed_instance = meta.instance_column(); + let public_instance = meta.instance_column(); + + meta.create_gate("unopened advice", |meta| { + let advice = meta.query_advice(advice, Rotation::cur()); + let committed = meta.query_instance(committed_instance, Rotation::cur()); + let public = meta.query_instance(public_instance, Rotation::cur()); + Constraints::without_selector(vec![("unopened advice", advice + committed + public)]) + }); + } + + fn synthesize( + &self, + _config: Self::Config, + _layouter: impl Layouter, + ) -> Result<(), PlonkError> { + Ok(()) + } +} + +/// `try_new` documents a typed error for unsupported constraint systems, so an +/// unopened advice column must not reach the `panic!` inside +/// `ProtocolPlan::from_constraint_system`. +#[test] +fn generator_reports_unopened_advice_column_as_typed_error() { + let mut rng = ChaCha8Rng::seed_from_u64(8); + let params = ParamsKZG::::unsafe_setup(4, &mut rng); + let circuit = UnopenedAdviceColumnCircuit; + let vk = keygen_vk_with_k::, _>(¶ms, &circuit, 4) + .expect("test circuit VK should build"); + + let err = SolidityGenerator::try_new(¶ms, &vk, GeneratorConfig::new(1, 1)) + .expect_err("unopened advice column is outside the supported verifier shape"); + let GeneratorError::Planning { stage, message } = err else { + panic!("expected a planning error, got {err:?}"); + }; + assert_eq!(stage, "constraint system"); + assert!( + message.contains("absorbed but never opened"), + "error should name the unopened advice column, got {message}" + ); +} + #[test] fn scalar_le_to_be_word_reverses_exactly_one_word() { let mut le = [0u8; 32]; @@ -333,6 +673,73 @@ fn external_quotient_output_uses_planned_return_buffer() { ); } +/// The Lagrange denominator run is deliberately allowed to spill out of the +/// theta band into the rot_points / x1_powers / q_eval_set windows, so +/// `VerifierMemoryLayout::validate` caps it against the *live* boundary +/// (`Q_EVAL_CPTR_MPTR`) rather than against `ROT_POINTS_MPTR`. That is only +/// sound while those three windows are written strictly after the Lagrange +/// phase and never read before being rewritten. +/// +/// Because the run is not registered as a `MemoryRegion` -- registering it +/// would report overlaps that are correct by phase ordering -- the arena's +/// overlap loop cannot enforce this. Pin the ordering here instead, so moving +/// a rot_points write earlier fails a test rather than silently corrupting the +/// denominators. +#[test] +fn rot_points_window_is_written_after_the_lagrange_denominator_run() { + let (params, vk) = lowering_plan_test_vk(); + let generator = SolidityGenerator::new(¶ms, &vk, GeneratorConfig::new(1, 1)); + let source = generator + .render(crate::RenderOptions::default()) + .expect("test verifier should render") + .verifier; + + let lagrange_end = source + .find("batch_invert(success, X_N_MPTR") + .expect("Lagrange block must call batch_invert over the denominator run"); + // Any access at all, read or write: the run leaves garbage in this window, + // so an early read is as wrong as an early write being clobbered. + let first_rot_points_access = source + .find("add(ROT_POINTS_MPTR") + .expect("verifier must access the rot_points window"); + + assert!( + first_rot_points_access > lagrange_end, + "ROT_POINTS_MPTR is accessed at byte {first_rot_points_access}, before the Lagrange \ + batch_invert at byte {lagrange_end}; the denominator run spills into that window, so \ + touching it earlier would either corrupt the denominators or read their leftovers" + ); +} + +/// The Lagrange denominator run grows with `num_instances`, so an oversized +/// instance count must be rejected at construction with a typed error rather +/// than surfacing later as a memory-layout failure. +#[test] +fn generator_rejects_instance_counts_that_overrun_the_lagrange_run() { + let (params, vk) = lowering_plan_test_vk(); + let meta = ConstraintSystemMeta::new(vk.cs(), 1); + let rotation_last_words = meta.rotation_last.unsigned_abs() as usize; + let max_num_instances = + crate::lowering::layout::theta_window::LAGRANGE_RUN_CAP_WORDS - rotation_last_words - 1; + + SolidityGenerator::try_new(¶ms, &vk, GeneratorConfig::new(max_num_instances, 1)) + .expect("the largest fitting instance count must still be accepted"); + + let err = + SolidityGenerator::try_new(¶ms, &vk, GeneratorConfig::new(max_num_instances + 1, 1)) + .expect_err("one instance past the cap must be rejected"); + assert!( + matches!( + err, + GeneratorError::TooManyInstances { + max_num_instances: reported, + .. + } if reported == max_num_instances + ), + "unexpected error: {err:?}" + ); +} + #[test] fn lowering_plan_reuses_stable_layout_facts() { let (params, vk) = lowering_plan_test_vk(); @@ -1015,6 +1422,39 @@ fn failed_success_paths_do_not_enter_ec_precompiles() { ); } +/// The constructor's known-answer probe is only as good as its constants: a +/// wrong 2G would brick every deployment, and a 2G that happened to equal the +/// probe's own input would silently restore the identity-only weakness. Pin +/// both against the curve library rather than against hardcoded literals. +#[test] +fn constructor_known_answer_vector_is_the_generator_and_its_double() { + use group::{prime::PrimeCurveAffine, Curve, Group}; + use midnight_curves::{G1Affine, G1Projective}; + + let constants = crate::lowering::render::TemplateConstants::default().eip2537; + let expected_g = crate::lowering::encoding::g1_to_u256s(G1Affine::generator()); + let g = G1Projective::generator(); + let expected_2g = crate::lowering::encoding::g1_to_u256s((g + g).to_affine()); + + assert_eq!( + constants.g1_generator, + (expected_g[0], expected_g[1], expected_g[2], expected_g[3]) + ); + assert_eq!( + constants.g1_double_generator, + ( + expected_2g[0], + expected_2g[1], + expected_2g[2], + expected_2g[3] + ) + ); + assert_ne!( + constants.g1_generator, constants.g1_double_generator, + "a known-answer probe whose expected output equals its input tests nothing" + ); +} + #[test] fn verifier_constructor_smoke_tests_runtime_prerequisites() { let verifier_template = verifier_template_corpus(); @@ -1029,6 +1469,9 @@ fn verifier_constructor_smoke_tests_runtime_prerequisites() { "eq(mload(add(scratch, {{ template_constants.word_bytes|hex() }})), 0x1234)", "non-Cancun fork fails during deployment", "G1ADD(identity, identity) -> identity", + "Known-answer probe: G1ADD(G, G) == 2G", + "template_constants.eip2537.g1_generator", + "template_constants.eip2537.g1_double_generator", "Worst-case generated G1MSM with all identity/zero terms", "constructor_g1msm_smoke_input_bytes", "PAIRING_CHECK([(identity_g1, identity_g2), (identity_g1, identity_g2)])", @@ -1219,6 +1662,26 @@ fn accumulator_points_are_prevalidated_before_transcript_work() { } } +#[test] +fn accumulator_scalars_are_range_checked_where_they_are_read() { + let verifier_template = verifier_template_corpus(); + + // `validate_public_accumulator` runs before the transcript instance loop + // that rejects non-canonical instance words, and EIP-2537 G1MSM reduces + // scalars mod r implicitly. Canonicality must therefore be enforced at the + // read sites in this helper rather than inherited from a later template. + for required in [ + "out := and(out, lt(lhs_scalar, r))", + "out := and(out, lt(rhs_scalar, r))", + "out := and(out, lt(fixed_scalar_{{ loop.index0 }}, r))", + ] { + assert!( + verifier_template.contains(required), + "accumulator scalars must be range-checked against r where they are read: {required}" + ); + } +} + #[test] fn accumulator_decoder_rejects_noncanonical_infinity() { let verifier_template = verifier_template_corpus(); @@ -1432,7 +1895,10 @@ fn differential_trace_hooks_cover_expected_categories() { "serialized PCS point sets", "trace::PCS_SERIALIZED_POINT_SET_BASE + set_idx as u64", ), - ("PCS q_com commitments", "40000 + set_idx"), + ( + "PCS q_com commitments", + "trace::PCS_Q_COM_BASE + set_idx as u64", + ), ] { assert!( pcs_source.contains(needle), @@ -1689,6 +2155,26 @@ fn batch_invert_handles_empty_and_singleton_ranges() { verifier_template.contains("if ret { mstore(mptr_start, mload(single_scratch)) }"), "singleton batch inversion must store the single inverse in place" ); + // The general path must reject non-canonical words (x >= r) like the + // singleton path, so accept/reject semantics do not depend on batch + // length: one guard on the first element, one inside the prefix-product + // loop, one on the final element. + assert_eq!( + verifier_template.matches("if iszero(lt(gp, r)) {").count(), + 1, + "general batch inversion path must range-check the first element" + ); + assert_eq!( + verifier_template.matches("if iszero(lt(x, r)) {").count(), + 2, + "batch inversion must range-check the singleton element and every \ + prefix-product loop element" + ); + assert_eq!( + verifier_template.matches("if iszero(lt(x_last, r)) {").count(), + 1, + "general batch inversion path must range-check the final element" + ); } #[test] @@ -1716,7 +2202,7 @@ fn production_verifier_documents_revert_or_true_policy() { && verifier_template .contains("ret := success\n if iszero(ret) { leave }") && verifier_template.contains( - "ret := and(ret, mload(scratch))\n if iszero(ret) { revert(0, 0) }\n ret := 1", + "ret := and(ret, eq(mload(scratch), 1))\n if iszero(ret) { revert(0, 0) }\n ret := 1", ), "final pairing helper must revert on pairing failure and normalize success to one" ); @@ -2388,6 +2874,49 @@ fn quotient_vm_bilin7_pairwise_matches_direct_expr_eval() { ); } +#[test] +fn quotient_vm_limb_decomposition_survives_const_table_overflow() { + // A large affine sum over consecutive limb pointers is emitted as a chain of + // LIN7 opcodes whose coefficients land in the one-byte constant table. With + // more than 256 distinct coefficients the table overflows a `u8` slot + // partway through emission. Before the fix, `emit_limb_shape`'s + // `u8::try_from(slot).expect(...)` panicked once the shape being emitted was + // preceded by enough residue constants; the decomposition path now re-checks + // the post-residue table and falls back to generic ops for the overflowing + // shape. This exercises that fallback and confirms it still evaluates the + // expression correctly. + let term_count = 300u32; + let mut values = HashMap::new(); + let mut expr = QuotientExpr::Const(U256::ZERO); + for k in 0..term_count { + let ptr = 0x1000 + k * 0x20; + values.insert(ptr, Fq::from(17 + k as u64)); + expr = quotient_add_expr( + expr, + // Coefficient `k + 2` keeps every term scaled (coeff 1 would drop the + // constant) and distinct, so the table grows one slot per term. + quotient_scale_expr( + Fq::from(k as u64 + 2), + QuotientExpr::Mem(QuotientMem::Literal(ptr)), + ), + ); + } + + let expected = eval_quotient_expr_for_test(&expr, &values); + let mut builder = QuotientProgramBuilder::with_limb_vm_ops(true); + // The pre-fix builder panics inside this call for this input. + builder.emit_expr(&expr); + + assert!( + builder.consts.len() > u8::MAX as usize, + "test must overflow the one-byte constant table to exercise the fallback" + ); + assert_eq!( + eval_quotient_vm_for_test(&builder.bytes, &builder.consts, &values), + expected + ); +} + #[test] fn quotient_vm_pow5_matches_direct_expr_eval() { let ptr = 0xa20; @@ -2439,6 +2968,41 @@ fn quotient_vm_pow5_rejects_near_miss_product_shapes() { ); } +#[test] +fn quotient_vm_add_product_reserves_scalar_before_base_constants() { + let lhs = 0xaa0; + let rhs = 0xac0; + let mut values = HashMap::new(); + values.insert(lhs, Fq::from(149u64)); + values.insert(rhs, Fq::from(157u64)); + + let mut base = QuotientExpr::Const(U256::ZERO); + for value in 1..=255u64 { + base = quotient_add_expr(base, QuotientExpr::Const(U256::from(value))); + } + let product = quotient_mul_expr( + quotient_mul_expr( + QuotientExpr::Mem(QuotientMem::Literal(lhs)), + QuotientExpr::Mem(QuotientMem::Literal(rhs)), + ), + QuotientExpr::Const(U256::from(300u64)), + ); + let expr = quotient_add_expr(base, product); + + let expected = eval_quotient_expr_for_test(&expr, &values); + let mut builder = QuotientProgramBuilder::default(); + builder.emit_expr(&expr); + + assert!( + builder.bytes.contains(&Q_OP_ADD_MUL_MEM_MEM_CONST_U8), + "product should stay on the fused add-mul path" + ); + assert_eq!( + eval_quotient_vm_for_test(&builder.bytes, &builder.consts, &values), + expected + ); +} + #[test] fn quotient_vm_limb_subshape_matches_direct_expr_eval() { let mut values = HashMap::new(); @@ -2486,6 +3050,53 @@ fn quotient_vm_limb_subshape_matches_direct_expr_eval() { ); } +#[test] +fn quotient_vm_limb_decomposition_reserves_shape_coeffs_before_residue() { + let mut values = HashMap::new(); + let mut expr = QuotientExpr::Const(U256::ZERO); + + for i in 0..7u32 { + let ptr = 0xb00 + i * WORD_BYTES as u32; + values.insert(ptr, Fq::from(151 + i as u64)); + expr = quotient_add_expr( + expr, + quotient_scale_expr( + Fq::from(19 + i as u64), + QuotientExpr::Mem(QuotientMem::Literal(ptr)), + ), + ); + } + + for i in 0..256u32 { + let ptr = 0x2000 + i * 0x40; + values.insert(ptr, Fq::from(401 + i as u64)); + expr = quotient_add_expr( + expr, + quotient_scale_expr( + Fq::from(1000 + i as u64), + QuotientExpr::Mem(QuotientMem::Literal(ptr)), + ), + ); + } + + let expected = eval_quotient_expr_for_test(&expr, &values); + let mut builder = QuotientProgramBuilder::with_limb_vm_ops(true); + builder.emit_expr(&expr); + + assert!( + builder.consts.len() > u8::MAX as usize, + "residue should grow the constant table past u8" + ); + assert!( + builder.bytes.contains(&Q_OP_LIN7), + "larger affine sums should still extract LIN7 subshapes" + ); + assert_eq!( + eval_quotient_vm_for_test(&builder.bytes, &builder.consts, &values), + expected + ); +} + #[test] fn quotient_vm_limb_subshape_inside_conditional_product_matches_direct_expr_eval() { let mut values = HashMap::new(); diff --git a/proofs/solidity-verifier/src/lowering/vk.rs b/proofs/solidity-verifier/src/lowering/vk.rs index 480b68c37..af26bc0f7 100644 --- a/proofs/solidity-verifier/src/lowering/vk.rs +++ b/proofs/solidity-verifier/src/lowering/vk.rs @@ -6,7 +6,7 @@ //! generated verifier contracts. use ff::Field; -use group::{prime::PrimeCurveAffine, Curve}; +use group::{prime::PrimeCurveAffine, Curve, Group}; use itertools::chain; use midnight_curves::{Fq, G1Affine, G1Projective, G2Affine}; use ruint::aliases::U256; @@ -69,6 +69,27 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { // (4 / 8 u256 words respectively). We cannot read `params.g[0]` // directly (the field is crate-private in midnight-proofs), so // we use the canonical BLS12-381 generator. + // + // The generated verifier subtracts `v * G1_BASE` in the final KZG + // linearization while every commitment in the proof/VK is over the + // SRS base `g[0]`. If a deployer-supplied `params` had `g[0] != G`, + // the contract would enforce a different pairing equation than the + // native verifier (bricking honest proofs, or accepting evaluation + // claims for a scaled statement). Validate consistency at build + // time: the commitment of the constant-1 polynomial equals `g[0]`, + // and in the Lagrange basis that commitment is `sum(g_lagrange)`. + let g_lagrange = self.params.g_lagrange(); + let srs_g1_base = g_lagrange + .iter() + .copied() + .fold(G1Projective::identity(), |acc, g| acc + g) + .to_affine(); + assert_eq!( + srs_g1_base, + G1Affine::generator(), + "SRS G1 base (sum of g_lagrange) is not the canonical BLS12-381 \ + generator; the emitted G1_BASE would diverge from the commitment base" + ); let g1_pt: G1Affine = G1Affine::generator(); let g2_pt: G2Affine = self.params.g2().to_affine(); let neg_s_g2_pt: G2Affine = (-self.params.s_g2()).to_affine(); @@ -366,6 +387,8 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { Self::transcript_buffer_layout_for_meta(meta, self.num_instances).words; let transcript_end = layout::TRANSCRIPT_BUFFER_START + transcript_words * WORD_BYTES; let pcs_end = layout::PCS_PAIRING_SCRATCH_START + pcs_computation * WORD_BYTES; + let pairing_batch_end = + layout::accumulator::PAIRING_BATCH_PTR + layout::accumulator::PAIRING_BATCH_HASH_BYTES; let final_pairing_end = layout::FINAL_PAIRING_SCRATCH_START + layout::PAIRING_STATIC_WORKING_WORDS * WORD_BYTES; let verifier_return_end = layout::VERIFIER_RETURN_BUFFER_START + WORD_BYTES; @@ -381,8 +404,12 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { transcript_end, // PCS computation scratch pcs_end, + // Accumulator/KZG pairing-batch hash frame. Currently ends where + // the final pairing frame starts, but list it explicitly so the + // bound survives if that derivation changes. + pairing_batch_end, // Pairing: two-pair input frame plus output word, rooted above - // Solidity's reserved memory prefix. + // solc's via-IR spill window. final_pairing_end, // Low-memory return frames. The quotient evaluator's output grows // with the number of simple selector buckets. @@ -414,8 +441,9 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { // midnight-proofs verifiers the dominating run is whichever of the // following is largest: // (a) initial absorbs (vk_digest + committed_pi + num_instances - // + all instance scalars + all phase-1 advices) before the - // first user-phase challenge squeeze (`theta`), or + // + all instance scalars + every advice commitment up to and + // including the first challenge-bearing phase) before the first + // user-phase challenge squeeze (`theta` at the latest), or // (b) the evaluation block (all `num_evals` scalars) absorbed // after the `y` squeeze and before the next squeeze. // @@ -438,6 +466,10 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> { meta.num_evals, meta.num_point_sets, ); - TranscriptBufferLayout::from_proof_layout(&proof_layout, num_instances) + TranscriptBufferLayout::from_proof_layout( + &proof_layout, + num_instances, + &meta.protocol.num_user_challenges, + ) } } diff --git a/proofs/solidity-verifier/src/test.rs b/proofs/solidity-verifier/src/test.rs index 0c585e179..e3e8747ab 100644 --- a/proofs/solidity-verifier/src/test.rs +++ b/proofs/solidity-verifier/src/test.rs @@ -45,7 +45,8 @@ use ruint::aliases::U256; use sha3::Digest; use crate::{ - compile_solidity, encode_calldata, pinned_solc_available, CallOutcome, Evm, GeneratorConfig, + compile_solidity, compile_solidity_runtime, encode_calldata, pinned_solc_available, + runtime_free_memory_pointer_init, AccumulatorEncoding, CallOutcome, Evm, GeneratorConfig, RenderDiagnostics, RenderOptions, RenderQuotient, RenderVk, SolidityGenerator, FN_SIG_VERIFY_PROOF, }; @@ -62,6 +63,9 @@ type PoseidonVerifierParams = const POSEIDON_K: u32 = 6; /// Environment flag that opts into expensive EVM/Solidity integration tests. const RUN_EVM_TESTS_ENV: &str = "HALO2_SOLIDITY_RUN_EVM_TESTS"; +/// Source for the test SRS, as documented by `zk_stdlib`'s own loader error. +const SRS_DOWNLOAD_URL: &str = + "https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19"; /// Minimal caller used to exercise the production verifier under STATICCALL. const STATICCALL_VERIFIER_HARNESS: &str = r#" // SPDX-License-Identifier: CC0-1.0 @@ -972,10 +976,9 @@ fn shape_fuzz_inputs_available_for_evm() -> bool { eprintln!("skipping supported-shape circuit fuzz: set {RUN_EVM_TESTS_ENV}=1 to run it"); return false; } - if !solc_available() { - eprintln!("skipping supported-shape circuit fuzz: solc not found"); - return false; - } + // Requested but unusable is a failure, not a skip. See + // `poseidon_inputs_available_for_evm`. + solc_available(); true } @@ -1355,6 +1358,267 @@ fn pinned_quotient_verifier_rejects_wrong_vk_and_quotient_contracts() { ); } +/// The verifier body is wrapped in `assembly ("memory-safe")` while writing +/// absolute addresses, which is a false promise: it never allocates through the +/// free-memory pointer. That annotation is nonetheless load-bearing -- without +/// it the block does not compile (stack too deep) -- and it is what lets solc's +/// via-IR stack-to-memory mover reserve spill slots upward from `0x80`. +/// +/// The generated layout is therefore based above that reservation rather than +/// at `0x80`, so solc's spill slots and the verifier's own memory are disjoint +/// by construction instead of by a liveness coincidence. The reservation size +/// is not fixed -- observed values range from `0x80` to `0x8e0` depending on +/// the circuit, the solc release, and the optimizer schedule -- so assert the +/// property against real compiled bytecode rather than assuming it holds. +/// +/// `VerifierMemoryLayout::validate()` enforces the complementary bound for the +/// generated layout: every generated region starts at or above +/// `LOW_MEMORY_SCRATCH_START`, so `reserved_end <= LOW_MEMORY_SCRATCH_START` +/// here proves full disjointness. Check both verifier and quotient evaluator +/// runtimes, because each via-IR compilation unit receives its own independent +/// spill reservation. The accumulator-bearing variants are checked too, +/// because their FinalPairing pairing-batch block adds frames and live values +/// the property fixture never renders. +#[test] +fn compiled_memoryguard_does_not_overlap_generated_layout() { + if !poseidon_inputs_available_for_evm() { + return; + } + + fn assert_memoryguard_clears_generated_layout(name: &str, source: &str) { + let runtime = compile_solidity_runtime(source); + let reserved_end = runtime_free_memory_pointer_init(&runtime).unwrap_or_else(|| { + panic!("{name}: could not read the free-memory-pointer prologue from runtime bytecode") + }); + assert!( + reserved_end <= crate::lowering::layout::LOW_MEMORY_SCRATCH_START, + "{name}: solc reserved [0x80, {reserved_end:#x}) for via-IR spill slots, which \ + overlaps the generated verifier layout based at {:#x}. A live spill slot can then \ + sit across a verifier write, silently corrupting a challenge or pairing input. \ + Raise LOW_MEMORY_SCRATCH_START above {reserved_end:#x}.", + crate::lowering::layout::LOW_MEMORY_SCRATCH_START + ); + } + + let fixture = create_property_poseidon_fixture(); + for (name, source) in [ + ("embedded", fixture.embedded_verifier_solidity.as_str()), + ("separate", fixture.separate_verifier_solidity.as_str()), + ("quotient", fixture.quotient_verifier_solidity.as_str()), + ( + "quotient evaluator", + fixture.quotient_evaluator_solidity.as_str(), + ), + ( + "trace quotient evaluator", + fixture.trace_quotient_evaluator_solidity.as_str(), + ), + ] { + assert_memoryguard_clears_generated_layout(name, source); + } + + for (name, _, artifacts, quotient_evaluator) in render_accumulator_verifier_variants() { + assert_memoryguard_clears_generated_layout(name, &artifacts.verifier); + assert_memoryguard_clears_generated_layout( + &format!("{name} quotient evaluator"), + "ient_evaluator, + ); + } +} + +/// `Halo2VerifyingKey` is size-checked at render time by +/// `validate_payload_layout`, because a data contract's runtime length is known +/// before compilation. The verifier's is not -- it only exists once solc has +/// run -- so nothing bounded it, and the revm harness deliberately sets +/// `limit_contract_code_size = usize::MAX` (see `evm.rs`), meaning an oversized +/// verifier would pass the whole suite and then fail to deploy on any EIP-170 +/// chain. Check the compiled artifact directly. +#[test] +fn compiled_verifier_runtime_fits_the_eip170_limit() { + if !poseidon_inputs_available_for_evm() { + return; + } + + let limit = crate::lowering::render::EIP_170_MAX_RUNTIME_BYTES; + let fixture = create_property_poseidon_fixture(); + for (name, source) in [ + ("embedded", fixture.embedded_verifier_solidity.as_str()), + ("separate", fixture.separate_verifier_solidity.as_str()), + ("quotient", fixture.quotient_verifier_solidity.as_str()), + ("vk", fixture.vk_solidity.as_str()), + ] { + let runtime_len = compile_solidity_runtime(source).len(); + assert!( + runtime_len <= limit, + "{name}: compiled runtime is {runtime_len} bytes, over the EIP-170 limit of \ + {limit}; this contract cannot be deployed on mainnet or any \ + EIP-170 chain. Note the revm harness lifts this cap, so no other test catches it." + ); + } +} + +/// Extract one rendered Yul function (signature through matching close brace) +/// from generated verifier source. The rendered helpers contain no string +/// literals, so plain brace counting is sufficient. +fn extract_yul_function<'a>(source: &'a str, signature_prefix: &str) -> &'a str { + let start = source + .find(signature_prefix) + .unwrap_or_else(|| panic!("rendered source should define {signature_prefix}")); + let tail = &source[start..]; + let open = tail.find('{').expect("function definition must open a brace"); + let mut depth = 0usize; + for (idx, byte) in tail.bytes().enumerate().skip(open) { + match byte { + b'{' => depth += 1, + b'}' => { + depth -= 1; + if depth == 0 { + return &tail[..=idx]; + } + } + _ => {} + } + } + panic!("unbalanced braces while extracting {signature_prefix}"); +} + +/// Test-only contract that runs the rendered `batch_invert` helper on +/// caller-chosen memory words. Raw calldata is `n || r || n words`; raw +/// returndata is `success flag || the n (possibly inverted) words`. +fn batch_invert_harness_source(verifier_solidity: &str) -> String { + let batch_invert = extract_yul_function( + verifier_solidity, + "function batch_invert(success, mptr_start, mptr_end, scratch_mptr, r) -> ret", + ); + format!( + r#"// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.24; + +contract BatchInvertHarness {{ + fallback() external {{ + assembly {{ + {batch_invert} + + let n := calldataload(0x00) + let r := calldataload(0x20) + let base := 0x1000 + calldatacopy(base, 0x40, mul(n, 0x20)) + let ok := batch_invert(1, base, add(base, mul(n, 0x20)), 0x8000, r) + mstore(0x80, ok) + mcopy(0xa0, base, mul(n, 0x20)) + return(0x80, add(0x20, mul(n, 0x20))) + }} + }} +}} +"# + ) +} + +/// Execute the rendered `batch_invert` helper against adversarial words. +/// +/// The template greps in `lowering/tests.rs` pin the guard text; this pins +/// the behavior: the singleton and general paths must both fail closed on +/// words outside the canonical range (`x >= r`, including invertible +/// residues, `x = r`, and literal zero) without touching the input run, +/// so accept/reject semantics never depend on batch length. Canonical +/// batches must produce exactly the native inverses. +#[test] +fn batch_invert_fails_closed_on_noncanonical_words_in_all_paths() { + if !poseidon_inputs_available_for_evm() { + return; + } + + let fixture = create_property_poseidon_fixture(); + let harness = batch_invert_harness_source(&fixture.embedded_verifier_solidity); + let mut evm = Evm::default(); + let address = evm.create(compile_solidity(&harness)); + + let r = fr_modulus_u256(); + let mut run = |elems: &[U256]| -> (bool, Vec) { + let mut calldata = Vec::with_capacity((2 + elems.len()) * 0x20); + calldata.extend_from_slice(&U256::from(elems.len()).to_be_bytes::<0x20>()); + calldata.extend_from_slice(&r.to_be_bytes::<0x20>()); + for elem in elems { + calldata.extend_from_slice(&elem.to_be_bytes::<0x20>()); + } + match evm.try_call(address, calldata) { + CallOutcome::Success { output, .. } => { + assert_eq!( + output.len(), + (1 + elems.len()) * 0x20, + "harness returndata shape" + ); + let flag = U256::try_from_be_slice(&output[..0x20]).unwrap(); + assert!(flag <= U256::from(1), "success flag must be boolean"); + let words = output[0x20..] + .chunks_exact(0x20) + .map(|word| U256::try_from_be_slice(word).unwrap()) + .collect(); + (flag == U256::from(1), words) + } + outcome => panic!("harness must not revert or halt: {outcome:?}"), + } + }; + + let word = |value: u64| U256::from(value); + let inv = + |value: u64| crate::lowering::encoding::fe_to_u256::(F::from(value).invert().unwrap()); + + // Canonical batches succeed and invert every element in place; the + // lengths cover the empty, singleton, two-element, and looped general + // paths. + let (ok, out) = run(&[]); + assert!(ok, "empty batch must be a no-op success"); + assert!(out.is_empty()); + for elems in [vec![7u64], vec![2, 3], vec![1, 2, 3, 5, 7]] { + let input: Vec = elems.iter().copied().map(word).collect(); + let (ok, out) = run(&input); + assert!(ok, "canonical batch of {} must succeed", elems.len()); + let expected: Vec = elems.iter().copied().map(inv).collect(); + assert_eq!( + out, + expected, + "batch of {} must produce native inverses", + elems.len() + ); + } + + // Every rejection leaves the input run untouched: zero and anything + // congruent to zero mod r has no inverse, and non-canonical words with + // invertible residues (x = r + 5, 2^256 - 1) must fail closed in both + // paths rather than being reduced by mulmod. The three r + 5 positions + // hit the general path's first-element, loop, and final-element guards. + let r_plus_5 = r + word(5); + for (name, elems) in [ + ("singleton literal zero", vec![U256::ZERO]), + ("singleton x = r", vec![r]), + ("singleton x = r + 5", vec![r_plus_5]), + ("singleton x = 2^256 - 1", vec![U256::MAX]), + ("general literal zero", vec![word(2), U256::ZERO, word(3)]), + ("general x = r", vec![word(2), r, word(3)]), + ( + "general first element x = r + 5", + vec![r_plus_5, word(2), word(3)], + ), + ( + "general loop element x = r + 5", + vec![word(2), r_plus_5, word(3)], + ), + ( + "general final element x = r + 5", + vec![word(2), word(3), r_plus_5], + ), + ( + "general two-element x = 2^256 - 1", + vec![word(2), U256::MAX], + ), + ] { + let (ok, out) = run(&elems); + assert!(!ok, "{name} must fail closed"); + assert_eq!(out, elems, "{name} must leave the input words untouched"); + } +} + #[test] fn verifier_constructor_rejects_missing_or_mismatched_eip2537_precompiles() { if !poseidon_inputs_available_for_evm() { @@ -1485,6 +1749,212 @@ fn compile_solidity_is_deterministic_for_same_source() { assert_eq!(bytecode_a, bytecode_b); } +/// The accumulator fixed-base scalar tail must match the verifying key. +/// +/// `fixed_scalar_count` is derived from `num_instances`, but the bases those +/// scalars multiply are generated as `fixed_comm_mptr + i * 0x80` from the VK. +/// A tail longer than the VK's fixed-commitment count used to render fine and +/// silently emit base pointers past that region, aliasing permutation +/// commitments -- and beyond them arbitrary VK payload words -- as accumulator +/// G1 bases. +#[test] +fn accumulator_fixed_base_tail_must_match_verifying_key() { + if !poseidon_inputs_available_for_evm() { + return; + } + + let srs_dir = srs_dir(); + env::set_var("SRS_DIR", &srs_dir); + let relation = PoseidonExample; + let srs = srs_for_test(&relation, Some(POSEIDON_K)); + let vk = setup_vk(&srs, &relation); + + let num_fixed_comms = vk.vk().fixed_commitments().len(); + let num_permutation_comms = vk.vk().permutation().commitments().len(); + let collapsed = AccumulatorEncoding::FULLY_COLLAPSED_PUBLIC_INPUT_WORDS; + // `-G` plus every permutation commitment, then up to one scalar per fixed + // commitment. + let min_tail = 1 + num_permutation_comms; + let max_tail = min_tail + num_fixed_comms; + + // A tail three scalars longer than the VK can supply bases for. Before the + // guard this rendered a verifier whose last three "fixed bases" were + // permutation commitments. + let err = SolidityGenerator::try_new( + &srs, + vk.vk(), + GeneratorConfig::new(collapsed + max_tail + 3, 1) + .with_accumulator(AccumulatorEncoding::new(0, 7, 56)), + ) + .expect_err("oversized accumulator fixed-base tail should be rejected"); + assert!( + matches!( + err, + crate::GeneratorError::AccumulatorFixedBaseTailMismatch { + fixed_scalar_count, + max_fixed_scalar_count, + .. + } if fixed_scalar_count == max_tail + 3 && max_fixed_scalar_count == max_tail + ), + "unexpected error for oversized tail: {err}" + ); + + // A tail too short to cover `-G` plus the permutation commitments would + // underflow the base count in the artifact emitter. + SolidityGenerator::try_new( + &srs, + vk.vk(), + GeneratorConfig::new(collapsed + min_tail - 1, 1) + .with_accumulator(AccumulatorEncoding::new(0, 7, 56)), + ) + .expect_err("undersized accumulator fixed-base tail should be rejected"); + + // Supported shapes still build: no tail at all, the full base set, and a + // prefix of the fixed commitments in between (every pointer stays inside + // the fixed-commitment region). + for num_instances in [ + collapsed, + collapsed + min_tail, + collapsed + max_tail, + collapsed + (min_tail + max_tail) / 2, + ] { + SolidityGenerator::try_new( + &srs, + vk.vk(), + GeneratorConfig::new(num_instances, 1) + .with_accumulator(AccumulatorEncoding::new(0, 7, 56)), + ) + .unwrap_or_else(|err| panic!("supported accumulator tail should build: {err}")); + } +} + +/// Render the three accumulator-bearing verifier variants over the Poseidon +/// fixture VK: fully collapsed, fixed-base scalar tail, and point-pair +/// encodings. Returns `(name, has_carried_scalars, artifacts, evaluator)` per +/// variant. +fn render_accumulator_verifier_variants( +) -> Vec<(&'static str, bool, crate::RenderedArtifacts, String)> { + let srs_dir = srs_dir(); + env::set_var("SRS_DIR", &srs_dir); + let relation = PoseidonExample; + let srs = srs_for_test(&relation, Some(POSEIDON_K)); + let vk = setup_vk(&srs, &relation); + + // A fully collapsed accumulator occupies FULLY_COLLAPSED_PUBLIC_INPUT_WORDS + // public inputs and has no fixed-base scalar tail. + let collapsed_words = AccumulatorEncoding::FULLY_COLLAPSED_PUBLIC_INPUT_WORDS; + // The partially collapsed form appends one scalar per generated base: + // `-G`, then every fixed commitment, then every permutation commitment. + let num_fixed_comms = vk.vk().fixed_commitments().len(); + let num_perm_comms = vk.vk().permutation().commitments().len(); + let tail_words = 1 + num_fixed_comms + num_perm_comms; + + let variants = [ + ( + "fully collapsed accumulator", + collapsed_words, + AccumulatorEncoding::new(0, 7, 56), + true, + ), + ( + "accumulator with fixed-base scalar tail", + collapsed_words + tail_words, + AccumulatorEncoding::new(0, 7, 56), + true, + ), + // Point-pair encodings carry no explicit scalars -- both are implicit + // one -- so no calldata scalar is read and none is range-checked. + ( + "point-pair accumulator", + AccumulatorEncoding::POINT_PAIR_PUBLIC_INPUT_WORDS, + AccumulatorEncoding::point_pair(0, 7, 56), + false, + ), + ]; + + variants + .into_iter() + .map(|(name, num_instances, acc, has_carried_scalars)| { + let generator = SolidityGenerator::new( + &srs, + vk.vk(), + GeneratorConfig::new(num_instances, 1).with_accumulator(acc), + ); + let artifacts = generator + .render(RenderOptions { + vk: RenderVk::Separate, + ..RenderOptions::default() + }) + .unwrap_or_else(|err| panic!("{name} should render: {err}")); + let quotient_evaluator = generator + .render_quotient_evaluator(RenderDiagnostics::default()) + .unwrap_or_else(|err| panic!("{name} quotient evaluator should render: {err}")); + (name, has_carried_scalars, artifacts, quotient_evaluator) + }) + .collect() +} + +/// Compile the accumulator render arm. +/// +/// No production fixture enables `with_accumulator`, so before this test the +/// whole `{%- if self.expected_has_accumulator %}` branch of +/// AccumulatorHelpers.yul -- the limb decoder, the pre-transcript +/// public-accumulator MSM, and the fixed-base scalar tail -- was never handed +/// to solc by the default gate. Only the opt-in `ivc_keccak_solidity` bench +/// (k = 20, release, external SRS assets) rendered it, so a Yul syntax error +/// or a solc stack-depth regression in that branch could reach a release +/// unnoticed. +/// +/// This does not execute the accumulator logic against a real recursive proof +/// -- that still needs a decider circuit carrying a genuine accumulator in its +/// public inputs. It does guarantee the branch compiles, and it pins the +/// canonicality checks on the scalars the helper feeds to G1MSM. +#[test] +fn accumulator_verifier_variants_compile_with_pinned_solc() { + if !poseidon_inputs_available_for_evm() { + return; + } + + for (name, has_carried_scalars, artifacts, _) in render_accumulator_verifier_variants() { + let verifier = artifacts.verifier; + assert!( + verifier.contains("function validate_public_accumulator"), + "{name} should render the accumulator helper" + ); + // The helper runs before the transcript loop that rejects + // non-canonical instance words, and EIP-2537 G1MSM reduces scalars mod + // r implicitly, so it must reject `s >= r` itself. + if has_carried_scalars { + for required in ["lt(lhs_scalar, r)", "lt(rhs_scalar, r)"] { + assert!( + verifier.contains(required), + "{name} should range-check accumulator scalars: {required}" + ); + } + } else { + assert!( + !verifier.contains("lt(lhs_scalar, r)"), + "{name} carries no explicit scalars, so none should be read or checked" + ); + } + + for (label, source) in [ + (name, verifier.as_str()), + ( + "accumulator VK", + artifacts.verifying_key.as_deref().expect("separate render includes VK"), + ), + ] { + let bytecode = std::panic::catch_unwind(AssertUnwindSafe(|| compile_solidity(source))) + .unwrap_or_else(|_| panic!("{label} should compile under the pinned solc")); + assert!( + !bytecode.is_empty(), + "{label} should compile to non-empty bytecode" + ); + } + } +} + #[test] fn poseidon_verifier_variants_compile_with_pinned_solc() { if !poseidon_inputs_available_for_evm() { @@ -3938,13 +4408,13 @@ fn poseidon_inputs_available_for_evm() -> bool { eprintln!("skipping Poseidon Solidity property test: set {RUN_EVM_TESTS_ENV}=1 to run it"); return false; } - if !poseidon_srs_available() { - return false; - } - if !solc_available() { - eprintln!("skipping Poseidon Solidity property test: solc not found"); - return false; - } + // Past this point the gate was explicitly requested, so a missing + // prerequisite is a failure rather than a skip. Returning `false` here + // would report a green run that compiled no Solidity and executed no + // proof -- the failure mode that let the rendered fixture artifacts drift + // out of date across several commits without any test noticing. + poseidon_srs_available(); + solc_available(); true } @@ -3960,25 +4430,36 @@ fn env_flag_enabled(name: &str) -> bool { .unwrap_or(false) } -/// Return whether the Poseidon test SRS can be found on disk. -fn poseidon_srs_available() -> bool { +/// Require the Poseidon test SRS on disk. +/// +/// Only called once the EVM gate has been explicitly requested, so a missing +/// asset panics with fetch instructions instead of silently skipping. +fn poseidon_srs_available() { let srs_dir = PathBuf::from(srs_dir()); let exact_srs_path = srs_dir.join(format!("bls_filecoin_2p{POSEIDON_K}")); let fallback_srs_path = srs_dir.join("bls_filecoin_2p19"); - if !exact_srs_path.exists() && !fallback_srs_path.exists() { - eprintln!( - "skipping Poseidon Solidity property test: SRS not found at {} or {}", - exact_srs_path.display(), - fallback_srs_path.display() - ); - return false; - } - true + assert!( + exact_srs_path.exists() || fallback_srs_path.exists(), + "{RUN_EVM_TESTS_ENV}=1 requires the test SRS, but it was not found at {} or {}.\n\ + Fetch it with:\n curl -L -o {} {SRS_DOWNLOAD_URL}\n\ + or point SRS_DIR at an existing copy.", + exact_srs_path.display(), + fallback_srs_path.display(), + fallback_srs_path.display(), + ); } -/// Return whether the configured pinned solc is available. -fn solc_available() -> bool { - pinned_solc_available() +/// Require the pinned solc. +/// +/// Same contract as [`poseidon_srs_available`]: loud once the gate is on. +fn solc_available() { + assert!( + pinned_solc_available(), + "{RUN_EVM_TESTS_ENV}=1 requires solc {}, which was not found or did not match.\n\ + Install it, point SOLC at the binary, or set {}=1 to accept another version.", + crate::PINNED_SOLC_VERSION, + crate::ALLOW_UNPINNED_SOLC_ENV, + ); } /// Resolve the SRS directory used by fixture setup. diff --git a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol index c0bd92500..40e7e6568 100644 --- a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2Verifier.sol @@ -43,7 +43,7 @@ contract Halo2Verifier { // EXPECTED_VK_PAYLOAD_LENGTH. uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 8640; uint256 internal constant EXPECTED_VK_LENGTH = 8641; - uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0xaa09cafce41491f57d16b422d0224fa9090c5313065c1edccaf639ecd603e0fa; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x055be1a34918fad6a0d2545bab0bac0a28bbc5c616f3a9d284639391b7b99b3e; bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); // Solidity ABI calldata cursors. The generated verifier accepts exactly @@ -55,8 +55,8 @@ contract Halo2Verifier { uint256 internal constant INSTANCE_CPTR = 0x15c4; // First general-purpose memory words reserved by the generated verifier. // RETURN_MPTR is a single word set to 1 on success. - uint256 internal constant TRANSCRIPT_MPTR = 0x80; - uint256 internal constant RETURN_MPTR = 0x80; + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; // ---------------------------------------------------------------------- // Verifying-key memory map. The VK header lives at VK_MPTR, followed @@ -64,84 +64,84 @@ contract Halo2Verifier { // runtime comes the challenge slots (challenge_mptr..) and the // per-stage scratch (theta_mptr..). // ---------------------------------------------------------------------- - uint256 internal constant VK_MPTR = 0x1de0; - uint256 internal constant VK_DIGEST_MPTR = 0x1de0; - uint256 internal constant NUM_INSTANCES_MPTR = 0x1e00; - uint256 internal constant K_MPTR = 0x1e20; - uint256 internal constant N_INV_MPTR = 0x1e40; - uint256 internal constant OMEGA_MPTR = 0x1e60; - uint256 internal constant OMEGA_INV_MPTR = 0x1e80; - uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x1ea0; - uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x1ec0; - uint256 internal constant ACC_OFFSET_MPTR = 0x1ee0; - uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x1f00; - uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x1f20; - uint256 internal constant G1_BASE_MPTR = 0x1f40; - uint256 internal constant G2_BASE_MPTR = 0x1fc0; - uint256 internal constant NEG_S_G2_BASE_MPTR = 0x20c0; - - uint256 internal constant CHALLENGE_MPTR = 0x3fa0; + uint256 internal constant VK_MPTR = 0x2d60; + uint256 internal constant VK_DIGEST_MPTR = 0x2d60; + uint256 internal constant NUM_INSTANCES_MPTR = 0x2d80; + uint256 internal constant K_MPTR = 0x2da0; + uint256 internal constant N_INV_MPTR = 0x2dc0; + uint256 internal constant OMEGA_MPTR = 0x2de0; + uint256 internal constant OMEGA_INV_MPTR = 0x2e00; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x2e20; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x2e40; + uint256 internal constant ACC_OFFSET_MPTR = 0x2e60; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x2e80; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x2ea0; + uint256 internal constant G1_BASE_MPTR = 0x2ec0; + uint256 internal constant G2_BASE_MPTR = 0x2f40; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x3040; + + uint256 internal constant CHALLENGE_MPTR = 0x4f20; // Challenge layout. Squeeze order in midnight-proofs: // user_phase challenges (variable count) // theta -> beta, gamma -> trash_challenge -> y -> x -> // x1, x2 -> x3 -> x4 - uint256 internal constant THETA_MPTR = 0x3fa0; - uint256 internal constant BETA_MPTR = 0x3fc0; - uint256 internal constant GAMMA_MPTR = 0x3fe0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x4000; - uint256 internal constant Y_MPTR = 0x4020; - uint256 internal constant X_MPTR = 0x4040; - uint256 internal constant X1_MPTR = 0x4060; - uint256 internal constant X2_MPTR = 0x4080; - uint256 internal constant X3_MPTR = 0x40a0; - uint256 internal constant X4_MPTR = 0x40c0; + uint256 internal constant THETA_MPTR = 0x4f20; + uint256 internal constant BETA_MPTR = 0x4f40; + uint256 internal constant GAMMA_MPTR = 0x4f60; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x4f80; + uint256 internal constant Y_MPTR = 0x4fa0; + uint256 internal constant X_MPTR = 0x4fc0; + uint256 internal constant X1_MPTR = 0x4fe0; + uint256 internal constant X2_MPTR = 0x5000; + uint256 internal constant X3_MPTR = 0x5020; + uint256 internal constant X4_MPTR = 0x5040; // Batch-open commitments live in 4-word EIP-2537 padded slots. - uint256 internal constant F_COM_MPTR = 0x40e0; - uint256 internal constant PI_MPTR = 0x4160; + uint256 internal constant F_COM_MPTR = 0x5060; + uint256 internal constant PI_MPTR = 0x50e0; // Accumulator (KZG IVC). - uint256 internal constant ACC_LHS_MPTR = 0x41e0; - uint256 internal constant ACC_RHS_MPTR = 0x4260; + uint256 internal constant ACC_LHS_MPTR = 0x5160; + uint256 internal constant ACC_RHS_MPTR = 0x51e0; // Lagrange / linearization scratch. - uint256 internal constant X_N_MPTR = 0x42e0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x4300; - uint256 internal constant L_LAST_MPTR = 0x4320; - uint256 internal constant L_BLIND_MPTR = 0x4340; - uint256 internal constant L_0_MPTR = 0x4360; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x4380; + uint256 internal constant X_N_MPTR = 0x5260; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x5280; + uint256 internal constant L_LAST_MPTR = 0x52a0; + uint256 internal constant L_BLIND_MPTR = 0x52c0; + uint256 internal constant L_0_MPTR = 0x52e0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x5300; // Legacy name: this is not h(x). It stores the expected opening // scalar for the linearized commitment, i.e. the negated y-batched // identity numerator reconstructed from the alleged evals at x. - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x43a0; - uint256 internal constant QUOTIENT_MPTR = 0x43c0; // 4 words - uint256 internal constant F_EVAL_MPTR = 0x4460; - uint256 internal constant V_MPTR = 0x4480; - uint256 internal constant FINAL_COM_MPTR = 0x44a0; // 4 words - uint256 internal constant PAIRING_LHS_MPTR = 0x4520; // 4 words - uint256 internal constant PAIRING_RHS_MPTR = 0x45a0; // 4 words + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x5320; + uint256 internal constant QUOTIENT_MPTR = 0x5340; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x53e0; + uint256 internal constant V_MPTR = 0x5400; + uint256 internal constant FINAL_COM_MPTR = 0x5420; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x54a0; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x5520; // 4 words // Multi-prepare scratch (sized at codegen time). - uint256 internal constant ROT_POINTS_MPTR = 0x4620; - uint256 internal constant X1_POWERS_MPTR = 0x49a0; + uint256 internal constant ROT_POINTS_MPTR = 0x55a0; + uint256 internal constant X1_POWERS_MPTR = 0x5920; // Q_COM materialization is currently fused into the final MSM scratch, // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero // reserved capacity until a future emitter starts writing Q_COM_MPTR. - uint256 internal constant Q_COM_MPTR = 0x51c0; - uint256 internal constant Q_EVAL_SET_MPTR = 0x51c0; + uint256 internal constant Q_COM_MPTR = 0x6140; + uint256 internal constant Q_EVAL_SET_MPTR = 0x6140; // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals // block of the proof; we keep it as a memory slot for symmetry. - uint256 internal constant Q_EVAL_CPTR_MPTR = 0x58c0; + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x6840; // Reserved 4-word slot for the G1 identity (point at infinity) in // EIP-2537 padded form. EVM memory is zero-initialised, and we // never write to this region, so the four `mload`s below produce // 0,0,0,0 which is exactly the identity encoding the EIP-2537 // ec_add / ec_mul precompiles accept. - uint256 internal constant G1_IDENTITY_MPTR = 0x59c0; + uint256 internal constant G1_IDENTITY_MPTR = 0x6940; // Decoded polynomial-eval buffer (Optimisation H3). The off-chain // Solidity proof shim rewrites proof scalars into canonical BE words, @@ -149,11 +149,11 @@ contract Halo2Verifier { // side `evaluations` loop range-checks and spills that value here so // downstream eval references (gate evaluator + PCS q_eval Horner) // become 3-gas `mload(...)` instead of calldata reads. - uint256 internal constant REVERSED_EVALS_MPTR = 0x5b20; - uint256 internal constant SELECTOR_ACC_MPTR = 0x6ee0; - uint256 internal constant QUOTIENT_RETURN_MPTR = 0x80; - uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x6ee0; - uint256 internal constant TRACE_U256_MPTR = 0x9a00; + uint256 internal constant REVERSED_EVALS_MPTR = 0x6aa0; + uint256 internal constant SELECTOR_ACC_MPTR = 0x7e60; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x7e60; + uint256 internal constant TRACE_U256_MPTR = 0xa980; // ---------------------------------------------------------------------- // Per-category bases for EIP-2537 padded G1 commitments. The proof @@ -170,13 +170,13 @@ contract Halo2Verifier { // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans // ---------------------------------------------------------------------- - uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x63e0; - uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x67e0; - uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x68e0; - uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x6a60; - uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x6b60; - uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x6c60; - uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x6ce0; + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x7360; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x7760; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x7860; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x79e0; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x7ae0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x7be0; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x7c60; // BLS12-381 scalar-field modulus, used for transcript challenges and all // Halo2 verifier arithmetic. @@ -198,7 +198,7 @@ contract Halo2Verifier { function require_eip2537_precompiles() private view { assembly ("memory-safe") { // Scratch is reused for every runtime-prerequisite probe. - let scratch := 0x80 + let scratch := 0x1000 // MCOPY must be available because the verifier uses it for // proof-time point/scratch staging. Execute the opcode here so a @@ -222,11 +222,44 @@ contract Halo2Verifier { revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair // smoke call. - let msm_scratch := 0x6ee0 + let msm_scratch := 0x7e60 for { let off := 0 } lt(off, 0x2940) { off := add(off, 0x20) } { mstore(add(msm_scratch, off), 0) } @@ -321,9 +354,15 @@ contract Halo2Verifier { // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } - let p := 0x1ce0 + let p := 0x2c60 // EIP-198 modexp frame: // [base_len, exp_len, mod_len, base, exponent, modulus] mstore(add(p, 0x00), 0x20) // base len @@ -458,6 +497,13 @@ contract Halo2Verifier { // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -478,16 +524,34 @@ contract Halo2Verifier { // Forward pass: scratch stores prefix products up to, but not // including, the final element. `gp` becomes the total product. + // + // Match the single-element path: reject non-canonical words + // (x >= r) instead of letting mulmod reduce them silently, so + // accept/reject semantics do not depend on batch length. let gp_mptr := scratch_mptr let gp := mload(mptr_start) + if iszero(lt(gp, r)) { + ret := 0 + leave + } let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) {} { - gp := mulmod(gp, mload(mptr), r) + let x := mload(mptr) + if iszero(lt(x, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x, r) mstore(gp_mptr, gp) mptr := add(mptr, 0x20) gp_mptr := add(gp_mptr, 0x20) } - gp := mulmod(gp, mload(mptr), r) + let x_last := mload(mptr) + if iszero(lt(x_last, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x_last, r) // A zero total product means at least one denominator was // zero, so no batch inverse exists. if iszero(gp) { @@ -504,6 +568,12 @@ contract Halo2Verifier { mstore(add(gp_mptr, 0xa0), r) ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -528,21 +598,30 @@ contract Halo2Verifier { // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0300 + let scratch := 0x1220 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } @@ -574,7 +653,13 @@ contract Halo2Verifier { // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -1378,15 +1463,15 @@ contract Halo2Verifier { // q_const_mptr points to Fr constants used by the VM. // q_program_mptr points to the bytecode stream. // Constants are stored as consecutive 32-byte Fr words. - let q_const_mptr := 0x21c0 + let q_const_mptr := 0x3140 // Program bytes are also stored in the VK payload, packed into // 32-byte words by PackedProgramCodec. - let q_program_mptr := 0x27c0 + let q_program_mptr := 0x3740 // Running Horner accumulator for fully evaluated identities. // After all identities, this is nu_y(x) for the `None` // identity group. // Initialize A = 0 before scanning the identity stream. - mstore(0x70c0, 0) + mstore(0x8040, 0) // Simple selectors are grouped into separate linearization // buckets. They start at zero for every proof. // q_sel_zero_off walks selector bucket byte offsets. @@ -1401,12 +1486,19 @@ contract Halo2Verifier { { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0x8080, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 42) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) // Store y^i at selector_power_mptr + 32*i. - mstore(add(0x7100, shl(5, q_y_power_i)), q_y_power) + mstore(add(0x8080, shl(5, q_y_power_i)), q_y_power) } } @@ -1415,102 +1507,102 @@ contract Halo2Verifier { // VM/native identities, so they occupy the same y-batch order. { let var0 := 0x1 - let f_3 := mload(0x5f60) - let f_4 := mload(0x5e60) - let a_0 := mload(0x5b40) + let f_3 := mload(0x6ee0) + let f_4 := mload(0x6de0) + let a_0 := mload(0x6ac0) let var1 := mulmod(f_4, a_0, r) let var2 := addmod(f_3, var1, r) - let f_5 := mload(0x5e80) - let a_1 := mload(0x5b60) + let f_5 := mload(0x6e00) + let a_1 := mload(0x6ae0) let var3 := mulmod(f_5, a_1, r) let var4 := addmod(var2, var3, r) - let f_6 := mload(0x5ea0) - let a_2 := mload(0x5b80) + let f_6 := mload(0x6e20) + let a_2 := mload(0x6b00) let var5 := mulmod(f_6, a_2, r) let var6 := addmod(var4, var5, r) - let f_7 := mload(0x5ec0) - let a_3 := mload(0x5ba0) + let f_7 := mload(0x6e40) + let a_3 := mload(0x6b20) let var7 := mulmod(f_7, a_3, r) let var8 := addmod(var6, var7, r) - let f_8 := mload(0x5ee0) - let a_4 := mload(0x5bc0) + let f_8 := mload(0x6e60) + let a_4 := mload(0x6b40) let var9 := mulmod(f_8, a_4, r) let var10 := addmod(var8, var9, r) - let f_0 := mload(0x5f00) - let a_0_next_1 := mload(0x5be0) + let f_0 := mload(0x6e80) + let a_0_next_1 := mload(0x6b60) let var11 := mulmod(f_0, a_0_next_1, r) let var12 := addmod(var10, var11, r) - let f_1 := mload(0x5f20) + let f_1 := mload(0x6ea0) let var13 := mulmod(f_1, a_0, r) let var14 := mulmod(var13, a_1, r) let var15 := addmod(var12, var14, r) - let f_2 := mload(0x5f40) + let f_2 := mload(0x6ec0) let var16 := mulmod(f_2, a_0, r) let var17 := mulmod(var16, a_2, r) let var18 := addmod(var15, var17, r) let var19 := mulmod(var0, var18, r) - mstore(0x7640, var19) + mstore(0x85c0, var19) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } { let var0 := 0x1 - let a_1 := mload(0x5b60) - let a_2 := mload(0x5b80) + let a_1 := mload(0x6ae0) + let a_2 := mload(0x6b00) let var1 := addmod(a_1, a_2, r) - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var2 := addmod(0, sub(r, a_3), r) let var3 := addmod(var1, var2, r) - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var4 := addmod(0, sub(r, a_4), r) let var5 := addmod(var3, var4, r) let var6 := mulmod(var0, var5, r) - mstore(0x7640, var6) + mstore(0x85c0, var6) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } { let var0 := 0x1 - let a_0 := mload(0x5b40) - let f_4 := mload(0x5e60) + let a_0 := mload(0x6ac0) + let f_4 := mload(0x6de0) let var1 := addmod(a_0, f_4, r) - let a_0_next_1 := mload(0x5be0) + let a_0_next_1 := mload(0x6b60) let var2 := addmod(0, sub(r, a_0_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x7640, var4) + mstore(0x85c0, var4) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } { let var0 := 0x1 - let a_1 := mload(0x5b60) - let f_5 := mload(0x5e80) + let a_1 := mload(0x6ae0) + let f_5 := mload(0x6e00) let var1 := addmod(a_1, f_5, r) - let a_1_next_1 := mload(0x5c00) + let a_1_next_1 := mload(0x6b80) let var2 := addmod(0, sub(r, a_1_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x7640, var4) + mstore(0x85c0, var4) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x7100, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x8080, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } // VM registers: @@ -1530,7 +1622,7 @@ contract Halo2Verifier { // q_end is an exclusive byte pointer for the VM loop. let q_end := add(q_program_mptr, 0x025d) // q_sp starts at the first free stack word. - let q_sp := 0x7640 + let q_sp := 0x85c0 // q_top is meaningless until q_has_top is set. let q_top := 0 // q_has_top = 0 means the VM stack is empty. @@ -1861,70 +1953,70 @@ contract Halo2Verifier { // stack. The Rust memory planner must reserve enough // words for structured_permutation_scratch_words(meta) // whenever this opcode can appear. - q_sp := 0x7640 + q_sp := 0x85c0 // The generated lines below call the same fold snippets // used by interpreted expressions, so trace IDs and // y-batch positions remain contiguous. { let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 - let q_perm_vals := 0x7640 - let q_perm_sigmas := 0x7760 - let q_perm_z_cur := 0x7880 - let q_perm_z_next := 0x78e0 - let q_perm_z_last := 0x7940 - let q_perm_delta_base_ptr := 0x7980 + let q_perm_vals := 0x85c0 + let q_perm_sigmas := 0x86e0 + let q_perm_z_cur := 0x8800 + let q_perm_z_next := 0x8860 + let q_perm_z_last := 0x88c0 + let q_perm_delta_base_ptr := 0x8900 let q_perm_num_cols := 9 let q_perm_num_sets := 3 let q_perm_chunk_len := 3 let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 - mstore(add(q_perm_vals, 0x0), mload(0x5e40)) + mstore(add(q_perm_vals, 0x0), mload(0x6dc0)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x5b40, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x6ac0, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0xc0), mload(0x5b20)) + mstore(add(q_perm_vals, 0xc0), mload(0x6aa0)) mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) - mstore(add(q_perm_vals, 0x100), mload(0x5c40)) + mstore(add(q_perm_vals, 0x100), mload(0x6bc0)) { for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 9) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off - mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x60a0, q_perm_sigma_load_src_off))) + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x7020, q_perm_sigma_load_src_off))) } } { for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 3) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) - mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x61c0, q_perm_z_cur_load_src_off))) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x7140, q_perm_z_cur_load_src_off))) } } { for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 3) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) - mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x61e0, q_perm_z_next_load_src_off))) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x7160, q_perm_z_next_load_src_off))) } } - mstore(add(q_perm_z_last, 0x0), mload(0x6200)) - mstore(add(q_perm_z_last, 0x20), mload(0x6260)) + mstore(add(q_perm_z_last, 0x0), mload(0x7180)) + mstore(add(q_perm_z_last, 0x20), mload(0x71e0)) let q_perm_eval := 0 q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_perm_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_perm_eval, r)) let q_perm_zn := mload(add(q_perm_z_cur, 0x40)) q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_perm_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_perm_eval, r)) for { let q_perm_i := 1 } lt(q_perm_i, 3) { q_perm_i := add(q_perm_i, 1) } { let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_perm_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_perm_eval, r)) } mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) for { let q_perm_set := 0 } lt(q_perm_set, 3) { q_perm_set := add(q_perm_set, 1) } { @@ -1943,8 +2035,8 @@ contract Halo2Verifier { q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) } q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_perm_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_perm_eval, r)) mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) } } @@ -1965,13 +2057,13 @@ contract Halo2Verifier { // f+beta/prefix/suffix scratch rather than as a // conventional VM stack. The Rust memory planner must // reserve structured_lookup_scratch_words(meta). - q_sp := 0x7640 + q_sp := 0x85c0 // Generated LogUp code follows the same y-batch order // as the Rust identity stream. { - let q_lookup_f := 0x7640 - let q_lookup_prefix := 0x7680 - let q_lookup_suffix := 0x76c0 + let q_lookup_f := 0x85c0 + let q_lookup_prefix := 0x8600 + let q_lookup_suffix := 0x8640 let q_lookup_l0 := mload(L_0_MPTR) let q_lookup_llast := mload(L_LAST_MPTR) let q_lookup_lblind := mload(L_BLIND_MPTR) @@ -1981,54 +2073,54 @@ contract Halo2Verifier { let q_lookup_theta := mload(THETA_MPTR) { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x6300), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x7280), r) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } { - let f_10 := mload(0x5f80) + let f_10 := mload(0x6f00) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_1, r) - let q_lookup_eval := addmod(mulmod(mload(0x62e0), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x7260), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x62e0) - let f_19 := mload(0x6040) - let f_11 := mload(0x5fa0) + let q_lookup_sum_h := mload(0x7260) + let f_19 := mload(0x6fc0) + let f_11 := mload(0x6f20) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) - let f_12 := mload(0x5fc0) + let f_12 := mload(0x6f40) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) let q_lookup_s_sum_h := mulmod(f_19, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x6320), sub(r, addmod(mload(0x6300), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x72a0), sub(r, addmod(mload(0x7280), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x62c0), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x7240), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } } { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x6380), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x7300), r) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } { - let f_0 := mload(0x5f00) + let f_0 := mload(0x6e80) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_0, r) - let a_6 := mload(0x5c60) + let a_6 := mload(0x6be0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_6, r) - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var2 := addmod(mulmod(var1, q_lookup_theta, r), a_0, r) mstore(add(q_lookup_f, 0x0), addmod(var2, q_lookup_beta, r)) - let f_1 := mload(0x5f20) + let f_1 := mload(0x6ea0) let var3 := addmod(mulmod(0, q_lookup_theta, r), f_1, r) - let a_7 := mload(0x5c80) + let a_7 := mload(0x6c00) let var4 := addmod(mulmod(var3, q_lookup_theta, r), a_7, r) - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var5 := addmod(mulmod(var4, q_lookup_theta, r), a_1, r) mstore(add(q_lookup_f, 0x20), addmod(var5, q_lookup_beta, r)) let q_lookup_product := 1 @@ -2049,26 +2141,26 @@ contract Halo2Verifier { for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 2) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) } - let q_lookup_eval := addmod(mulmod(mload(0x6360), q_lookup_product, r), sub(r, q_lookup_sum), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x72e0), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x6360) - let f_20 := mload(0x6060) - let f_13 := mload(0x5fe0) + let q_lookup_sum_h := mload(0x72e0) + let f_20 := mload(0x6fe0) + let f_13 := mload(0x6f60) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_13, r) - let f_14 := mload(0x6000) + let f_14 := mload(0x6f80) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_14, r) - let f_15 := mload(0x6020) + let f_15 := mload(0x6fa0) let var2 := addmod(mulmod(var1, q_lookup_theta, r), f_15, r) let q_lookup_s_sum_h := mulmod(f_20, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x63a0), sub(r, addmod(mload(0x6380), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x7320), sub(r, addmod(mload(0x7300), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var2, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x6340), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x72c0), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_lookup_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_lookup_eval, r)) } } } @@ -2089,24 +2181,24 @@ contract Halo2Verifier { // interpreter stack before dispatching. q_top := 0 q_has_top := 0 - q_sp := 0x7640 + q_sp := 0x85c0 // Native identity sub-cases are generated from selected heavy gate identities. switch q_native_idx case 0 { { let var0 := 0x1 let var1 := 0x1000000000000000 - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var2 := mulmod(var1, a_4, r) let var3 := 0x10000000000 - let a_3_prev_1 := mload(0x5ca0) + let a_3_prev_1 := mload(0x6c20) let var4 := mulmod(var3, a_3_prev_1, r) let var5 := addmod(var2, var4, r) let var6 := 0x400000 - let a_4_prev_1 := mload(0x5cc0) + let a_4_prev_1 := mload(0x6c40) let var7 := mulmod(var6, a_4_prev_1, r) let var8 := addmod(var5, var7, r) - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var9 := addmod(var8, a_3, r) let var10 := 0x40000000000 let var11 := mulmod(var10, a_3, r) @@ -2128,54 +2220,54 @@ contract Halo2Verifier { let var27 := addmod(var24, var26, r) let var28 := addmod(var27, a_3_prev_1, r) let var29 := addmod(var19, var28, r) - let a_0_prev_1 := mload(0x5ce0) + let a_0_prev_1 := mload(0x6c60) let var30 := mulmod(var10, a_0_prev_1, r) - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var31 := mulmod(var25, a_0, r) let var32 := addmod(var30, var31, r) - let a_0_next_1 := mload(0x5be0) + let a_0_next_1 := mload(0x6b60) let var33 := addmod(var32, a_0_next_1, r) let var34 := 0x2 - let a_1_prev_1 := mload(0x5d00) + let a_1_prev_1 := mload(0x6c80) let var35 := mulmod(var10, a_1_prev_1, r) - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var36 := mulmod(var25, a_1, r) let var37 := addmod(var35, var36, r) - let a_1_next_1 := mload(0x5c00) + let a_1_next_1 := mload(0x6b80) let var38 := addmod(var37, a_1_next_1, r) let var39 := mulmod(var34, var38, r) let var40 := addmod(var33, var39, r) let var41 := addmod(0, sub(r, var40), r) let var42 := addmod(var29, var41, r) let var43 := mulmod(var0, var42, r) - mstore(0x7640, var43) + mstore(0x85c0, var43) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xa0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } } case 1 { { let var0 := 0x1 let var1 := 0x10000000000000 - let a_3_next_1 := mload(0x5d20) + let a_3_next_1 := mload(0x6ca0) let var2 := mulmod(var1, a_3_next_1, r) let var3 := 0x4000000000 - let a_3_prev_1 := mload(0x5ca0) + let a_3_prev_1 := mload(0x6c20) let var4 := mulmod(var3, a_3_prev_1, r) let var5 := addmod(var2, var4, r) let var6 := 0x4000 - let a_4_prev_1 := mload(0x5cc0) + let a_4_prev_1 := mload(0x6c40) let var7 := mulmod(var6, a_4_prev_1, r) let var8 := addmod(var5, var7, r) let var9 := 0x400 - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var10 := mulmod(var9, a_3, r) let var11 := addmod(var8, var10, r) - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var12 := addmod(var11, a_4, r) let var13 := 0x40000000000000 let var14 := mulmod(var13, a_4, r) @@ -2202,112 +2294,112 @@ contract Halo2Verifier { let var35 := addmod(var33, var34, r) let var36 := addmod(var35, a_3_prev_1, r) let var37 := addmod(var25, var36, r) - let a_0_prev_1 := mload(0x5ce0) + let a_0_prev_1 := mload(0x6c60) let var38 := mulmod(var15, a_0_prev_1, r) let var39 := 0x100000 - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var40 := mulmod(var39, a_0, r) let var41 := addmod(var38, var40, r) - let a_0_next_1 := mload(0x5be0) + let a_0_next_1 := mload(0x6b60) let var42 := addmod(var41, a_0_next_1, r) let var43 := 0x2 - let a_1_prev_1 := mload(0x5d00) + let a_1_prev_1 := mload(0x6c80) let var44 := mulmod(var15, a_1_prev_1, r) - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var45 := mulmod(var39, a_1, r) let var46 := addmod(var44, var45, r) - let a_1_next_1 := mload(0x5c00) + let a_1_next_1 := mload(0x6b80) let var47 := addmod(var46, a_1_next_1, r) let var48 := mulmod(var43, var47, r) let var49 := addmod(var42, var48, r) let var50 := addmod(0, sub(r, var49), r) let var51 := addmod(var37, var50, r) let var52 := mulmod(var0, var51, r) - mstore(0x7640, var52) + mstore(0x85c0, var52) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xc0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } } case 2 { { let var0 := 0x1 - let f_0 := mload(0x5f00) - let a_0_next_1 := mload(0x5be0) + let f_0 := mload(0x6e80) + let a_0_next_1 := mload(0x6b60) let var1 := addmod(0, sub(r, a_0_next_1), r) let var2 := addmod(f_0, var1, r) let var3 := 0x1b8114c381b922fd5d6d241210e2d8a68ad5744053ba9e776118de4107b51ace - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0x3df32e4cc4cb2ed20e5d21899cf5331775990ccaec4c09b4e3717213fcc0d763 - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_2 := mload(0x5b80) + let a_2 := mload(0x6b00) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x5c40) + let a_5 := mload(0x6bc0) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x7640, var18) + mstore(0x85c0, var18) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x1c0) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x7100, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x8080, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } } case 3 { { let var0 := 0x1 - let f_1 := mload(0x5f20) - let a_1_next_1 := mload(0x5c00) + let f_1 := mload(0x6ea0) + let a_1_next_1 := mload(0x6b80) let var1 := addmod(0, sub(r, a_1_next_1), r) let var2 := addmod(f_1, var1, r) let var3 := 0x404d21073985d14e432a4ad76d3fae06ca74314b950fe7b1d7f501cd31a8b374 - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0xb2cc8704264c6bd81bc620e9e524d4b73e9b2317679422ff7fa1603955649f1 - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f - let a_2 := mload(0x5b80) + let a_2 := mload(0x6b00) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x5c40) + let a_5 := mload(0x6bc0) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x7640, var18) + mstore(0x85c0, var18) } - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x1c0) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x7100, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7640), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x8080, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x85c0), r)) } } default { revert(0, 0) } @@ -2331,14 +2423,14 @@ contract Halo2Verifier { // The global fully-evaluated accumulator is still // multiplied by y so later main identities land at the // same y powers as Rust's reverse fold. - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) let q_sel_acc := mload(q_target_ptr) if q_sel_gap { // Selector buckets are sparse in the global // identity stream. Precomputed y^gap advances only // this selector's local accumulator. - q_sel_acc := mulmod(q_sel_acc, mload(add(0x7100, shl(5, q_sel_gap))), r) + q_sel_acc := mulmod(q_sel_acc, mload(add(0x8080, shl(5, q_sel_gap))), r) } mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) } @@ -2353,6 +2445,12 @@ contract Halo2Verifier { // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0x85c0)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -2364,52 +2462,52 @@ contract Halo2Verifier { { let q_trash_tau := mload(TRASH_CHALLENGE_MPTR) { - let f_0 := mload(0x5f00) - let a_0_next_1 := mload(0x5be0) + let f_0 := mload(0x6e80) + let a_0_next_1 := mload(0x6b60) let var0 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 let var1 := mulmod(a_0_next_1, var0, r) let var2 := addmod(f_0, var1, r) let var3 := 0x590ba402032e82eb1f660ef09796c5686345a5054ed96dae8e2d233633788771 - let a_0 := mload(0x5b40) + let a_0 := mload(0x6ac0) let var4 := mulmod(var3, a_0, r) let var5 := addmod(var2, var4, r) let var6 := 0x52f789e4afc3801f7411102ee2f47cc5954a744e71cac98e75ea962a55a0a76f - let a_1 := mload(0x5b60) + let a_1 := mload(0x6ae0) let var7 := mulmod(var6, a_1, r) let var8 := addmod(var5, var7, r) let var9 := 0x3509dd2fe3aac0080783557fec090fb1cb4b2b0901253c55282024331d1fe1a8 - let a_2 := mload(0x5b80) + let a_2 := mload(0x6b00) let var10 := q_pow5(a_2) let var11 := mulmod(var9, var10, r) let var12 := addmod(var8, var11, r) let var13 := 0x333f8046ece5579cbd6872449c57f2703dfc8864cfadc06d587ff104a0d0c1f2 - let a_3 := mload(0x5ba0) + let a_3 := mload(0x6b20) let var14 := q_pow5(a_3) let var15 := mulmod(var13, var14, r) let var16 := addmod(var12, var15, r) let var17 := 0x412c98232b6ab8a47aa76ee814ef7ec6261987c9802f2cfc490e007951a60ca5 - let a_4 := mload(0x5bc0) + let a_4 := mload(0x6b40) let var18 := q_pow5(a_4) let var19 := mulmod(var17, var18, r) let var20 := addmod(var16, var19, r) let var21 := 0x53fded36d490ba6b05a5d10fd99ffe5456baec6a6a8753199d5ebdc33c99790e - let a_5 := mload(0x5c40) + let a_5 := mload(0x6bc0) let var22 := q_pow5(a_5) let var23 := mulmod(var21, var22, r) let var24 := addmod(var20, var23, r) let var25 := 0x6ccb1c7d87f3c12a2bde4e68ac7f1e8b03481ba15d7f88f9a7f9b8310dd6d34 - let a_6 := mload(0x5c60) + let a_6 := mload(0x6be0) let var26 := q_pow5(a_6) let var27 := mulmod(var25, var26, r) let var28 := addmod(var24, var27, r) let var29 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_7 := mload(0x5c80) + let a_7 := mload(0x6c00) let var30 := q_pow5(a_7) let var31 := mulmod(var29, var30, r) let var32 := addmod(var28, var31, r) let var33 := addmod(mulmod(0, q_trash_tau, r), var32, r) - let f_1 := mload(0x5f20) - let a_1_next_1 := mload(0x5c00) + let f_1 := mload(0x6ea0) + let a_1_next_1 := mload(0x6b80) let var34 := mulmod(a_1_next_1, var0, r) let var35 := addmod(f_1, var34, r) let var36 := 0x5b1fc262a28cbb8bf75d9b1a6edaa74591ec24cd9a209512213cec3a3c0f1a5d @@ -2437,7 +2535,7 @@ contract Halo2Verifier { let var58 := mulmod(var57, var30, r) let var59 := addmod(var56, var58, r) let var60 := addmod(mulmod(var33, q_trash_tau, r), var59, r) - let f_2 := mload(0x5f40) + let f_2 := mload(0x6ec0) let var61 := mulmod(a_3, var0, r) let var62 := addmod(f_2, var61, r) let var63 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 @@ -2450,7 +2548,7 @@ contract Halo2Verifier { let var70 := mulmod(var69, var10, r) let var71 := addmod(var68, var70, r) let var72 := addmod(mulmod(var60, q_trash_tau, r), var71, r) - let f_3 := mload(0x5f60) + let f_3 := mload(0x6ee0) let var73 := mulmod(a_4, var0, r) let var74 := addmod(f_3, var73, r) let var75 := 0x222e83e70453dfee19b402e9fa8dfe2c4987b034d0be3ceb478b3022e97934c1 @@ -2465,7 +2563,7 @@ contract Halo2Verifier { let var84 := mulmod(var69, var14, r) let var85 := addmod(var83, var84, r) let var86 := addmod(mulmod(var72, q_trash_tau, r), var85, r) - let f_4 := mload(0x5e60) + let f_4 := mload(0x6de0) let var87 := mulmod(a_5, var0, r) let var88 := addmod(f_4, var87, r) let var89 := 0x726df1506749848155630b86ae25a82b281ecd050fe3a52d85a181fa87202e4b @@ -2482,7 +2580,7 @@ contract Halo2Verifier { let var100 := mulmod(var69, var18, r) let var101 := addmod(var99, var100, r) let var102 := addmod(mulmod(var86, q_trash_tau, r), var101, r) - let f_5 := mload(0x5e80) + let f_5 := mload(0x6e00) let var103 := mulmod(a_6, var0, r) let var104 := addmod(f_5, var103, r) let var105 := 0x2f5908b169c6cf1bd26dcf0f9e5105481f5164f3ece0582bf3098312167751a7 @@ -2501,7 +2599,7 @@ contract Halo2Verifier { let var118 := mulmod(var69, var22, r) let var119 := addmod(var117, var118, r) let var120 := addmod(mulmod(var102, q_trash_tau, r), var119, r) - let f_6 := mload(0x5ea0) + let f_6 := mload(0x6e20) let var121 := mulmod(a_7, var0, r) let var122 := addmod(f_6, var121, r) let var123 := 0x6d05a41959f539a7fc9ec0972ea1e3dbb6fc67dd51daf3414f7fbbb091c7274a @@ -2522,8 +2620,8 @@ contract Halo2Verifier { let var138 := mulmod(var69, var26, r) let var139 := addmod(var137, var138, r) let var140 := addmod(mulmod(var120, q_trash_tau, r), var139, r) - let f_7 := mload(0x5ec0) - let a_2_next_1 := mload(0x5c20) + let f_7 := mload(0x6e40) + let a_2_next_1 := mload(0x6ba0) let var141 := mulmod(a_2_next_1, var0, r) let var142 := addmod(f_7, var141, r) let var143 := 0x70d8f2a733a64d650faccc9b1c2a766a9544bb3ff1a11ee73cb43947ef386633 @@ -2546,12 +2644,12 @@ contract Halo2Verifier { let var160 := mulmod(var69, var30, r) let var161 := addmod(var159, var160, r) let var162 := addmod(mulmod(var140, q_trash_tau, r), var161, r) - let f_33 := mload(0x6080) + let f_33 := mload(0x7000) let q_trash_one_minus_selector := addmod(1, sub(r, f_33), r) - let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0x63c0), r) + let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0x7340), r) let q_trash_eval := addmod(var162, sub(r, q_trash_scaled), r) - mstore(0x70c0, mulmod(mload(0x70c0), y, r)) - mstore(0x70c0, addmod(mload(0x70c0), q_trash_eval, r)) + mstore(0x8040, mulmod(mload(0x8040), y, r)) + mstore(0x8040, addmod(mload(0x8040), q_trash_eval, r)) } } // Finish selector buckets by applying the codegen-known tail @@ -2563,69 +2661,69 @@ contract Halo2Verifier { // selector commitment in the linearized MSM. { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0520)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0520)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0500)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0500)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x04a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x04a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0480)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0480)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x80) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0440)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0440)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xa0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0420)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0420)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xc0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0400)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0400)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xe0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x03e0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x03e0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0100) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x03c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x03c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0120) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x03a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x03a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0140) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0360)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0360)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0160) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0320)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0320)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0180) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x02a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x02a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x01a0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x0280)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x0280)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x01c0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7100, 0x01c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x8080, 0x01c0)), r)) } // Fully evaluated identities are the constant-polynomial side // of the linearization query. Rust subtracts that grouped // scalar into expected_eval, so Solidity stores -nu_y(x). - let linearization_expected_eval := addmod(0, sub(r, mload(0x70c0)), r) + let linearization_expected_eval := addmod(0, sub(r, mload(0x8040)), r) mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) pop(y) } @@ -2729,44 +2827,44 @@ contract Halo2Verifier { { // q_eval_set[0]: 35 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x70c0, 0x5b20) - mstore(0x70e0, 0x62c0) - mstore(0x7100, 0x62e0) - mstore(0x7120, 0x6340) - mstore(0x7140, 0x6360) - mstore(0x7160, 0x63c0) - mstore(0x7180, 0x5e40) - mstore(0x71a0, 0x5e60) - mstore(0x71c0, 0x5e80) - mstore(0x71e0, 0x5ea0) - mstore(0x7200, 0x5ec0) - mstore(0x7220, 0x5ee0) - mstore(0x7240, 0x5f00) - mstore(0x7260, 0x5f20) - mstore(0x7280, 0x5f40) - mstore(0x72a0, 0x5f60) - mstore(0x72c0, 0x5f80) - mstore(0x72e0, 0x5fa0) - mstore(0x7300, 0x5fc0) - mstore(0x7320, 0x5fe0) - mstore(0x7340, 0x6000) - mstore(0x7360, 0x6020) - mstore(0x7380, 0x6040) - mstore(0x73a0, 0x6060) - mstore(0x73c0, 0x6080) - mstore(0x73e0, 0x60a0) - mstore(0x7400, 0x60c0) - mstore(0x7420, 0x60e0) - mstore(0x7440, 0x6100) - mstore(0x7460, 0x6120) - mstore(0x7480, 0x6140) - mstore(0x74a0, 0x6160) - mstore(0x74c0, 0x6180) - mstore(0x74e0, 0x61a0) - mstore(0x7500, QUOTIENT_EVAL_MPTR) - let q_eval_set_0 := mload(0x5b20) + mstore(0x8040, 0x6aa0) + mstore(0x8060, 0x7240) + mstore(0x8080, 0x7260) + mstore(0x80a0, 0x72c0) + mstore(0x80c0, 0x72e0) + mstore(0x80e0, 0x7340) + mstore(0x8100, 0x6dc0) + mstore(0x8120, 0x6de0) + mstore(0x8140, 0x6e00) + mstore(0x8160, 0x6e20) + mstore(0x8180, 0x6e40) + mstore(0x81a0, 0x6e60) + mstore(0x81c0, 0x6e80) + mstore(0x81e0, 0x6ea0) + mstore(0x8200, 0x6ec0) + mstore(0x8220, 0x6ee0) + mstore(0x8240, 0x6f00) + mstore(0x8260, 0x6f20) + mstore(0x8280, 0x6f40) + mstore(0x82a0, 0x6f60) + mstore(0x82c0, 0x6f80) + mstore(0x82e0, 0x6fa0) + mstore(0x8300, 0x6fc0) + mstore(0x8320, 0x6fe0) + mstore(0x8340, 0x7000) + mstore(0x8360, 0x7020) + mstore(0x8380, 0x7040) + mstore(0x83a0, 0x7060) + mstore(0x83c0, 0x7080) + mstore(0x83e0, 0x70a0) + mstore(0x8400, 0x70c0) + mstore(0x8420, 0x70e0) + mstore(0x8440, 0x7100) + mstore(0x8460, 0x7120) + mstore(0x8480, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x6aa0) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x70c0, 0x20) + let eval_p := add(0x8040, 0x20) for { let i := 1 } lt(i, 0x23) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2780,12 +2878,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[1]: 3 commitment(s) - let q_eval_set_0 := mload(0x6280) - let q_eval_set_1 := mload(0x62a0) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6300), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6320), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6380), mload(add(X1_POWERS_MPTR, 0x40)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x63a0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + let q_eval_set_0 := mload(0x7200) + let q_eval_set_1 := mload(0x7220) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x7280), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x72a0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x7300), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x7320), mload(add(X1_POWERS_MPTR, 0x40)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x20), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x40), q_eval_set_1) } @@ -2795,35 +2893,35 @@ contract Halo2Verifier { { // q_eval_set[2]: 8 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x70c0, 0x5b40) - mstore(0x70e0, 0x5be0) - mstore(0x7100, 0x5ce0) - mstore(0x7120, 0x5b60) - mstore(0x7140, 0x5c00) - mstore(0x7160, 0x5d00) - mstore(0x7180, 0x5b80) - mstore(0x71a0, 0x5c20) - mstore(0x71c0, 0x5da0) - mstore(0x71e0, 0x5ba0) - mstore(0x7200, 0x5d20) - mstore(0x7220, 0x5ca0) - mstore(0x7240, 0x5bc0) - mstore(0x7260, 0x5d40) - mstore(0x7280, 0x5cc0) - mstore(0x72a0, 0x5c40) - mstore(0x72c0, 0x5e00) - mstore(0x72e0, 0x5de0) - mstore(0x7300, 0x5c60) - mstore(0x7320, 0x5d80) - mstore(0x7340, 0x5d60) - mstore(0x7360, 0x5c80) - mstore(0x7380, 0x5e20) - mstore(0x73a0, 0x5dc0) - let q_eval_set_0 := mload(0x5b40) - let q_eval_set_1 := mload(0x5be0) - let q_eval_set_2 := mload(0x5ce0) + mstore(0x8040, 0x6ac0) + mstore(0x8060, 0x6b60) + mstore(0x8080, 0x6c60) + mstore(0x80a0, 0x6ae0) + mstore(0x80c0, 0x6b80) + mstore(0x80e0, 0x6c80) + mstore(0x8100, 0x6b00) + mstore(0x8120, 0x6ba0) + mstore(0x8140, 0x6d20) + mstore(0x8160, 0x6b20) + mstore(0x8180, 0x6ca0) + mstore(0x81a0, 0x6c20) + mstore(0x81c0, 0x6b40) + mstore(0x81e0, 0x6cc0) + mstore(0x8200, 0x6c40) + mstore(0x8220, 0x6bc0) + mstore(0x8240, 0x6d80) + mstore(0x8260, 0x6d60) + mstore(0x8280, 0x6be0) + mstore(0x82a0, 0x6d00) + mstore(0x82c0, 0x6ce0) + mstore(0x82e0, 0x6c00) + mstore(0x8300, 0x6da0) + mstore(0x8320, 0x6d40) + let q_eval_set_0 := mload(0x6ac0) + let q_eval_set_1 := mload(0x6b60) + let q_eval_set_2 := mload(0x6c60) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x70c0, 0x60) + let eval_p := add(0x8040, 0x60) for { let i := 1 } lt(i, 0x8) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2841,12 +2939,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[3]: 2 commitment(s) - let q_eval_set_0 := mload(0x61c0) - let q_eval_set_1 := mload(0x61e0) - let q_eval_set_2 := mload(0x6200) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6220), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6240), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x6260), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + let q_eval_set_0 := mload(0x7140) + let q_eval_set_1 := mload(0x7160) + let q_eval_set_2 := mload(0x7180) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x71a0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x71c0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x71e0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0xc0), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0xe0), q_eval_set_1) mstore(add(Q_EVAL_SET_MPTR, 0x100), q_eval_set_2) @@ -3014,145 +3112,145 @@ contract Halo2Verifier { v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), x4_pow_3, r), r) v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_4, r), r) - mcopy(0x70c0, 0x67e0, 0x80) - mstore(0x7140, mload(add(X1_POWERS_MPTR, 0x20))) - mcopy(0x7160, 0x6a60, 0x80) - mstore(0x71e0, mload(add(X1_POWERS_MPTR, 0x40))) - mcopy(0x7200, 0x6860, 0x80) - mstore(0x7280, mload(add(X1_POWERS_MPTR, 0x60))) - mcopy(0x72a0, 0x6ae0, 0x80) - mstore(0x7320, mload(add(X1_POWERS_MPTR, 0x80))) - mcopy(0x7340, 0x6c60, 0x80) - mstore(0x73c0, mload(add(X1_POWERS_MPTR, 0xa0))) - mcopy(0x73e0, 0x2ea0, 0x80) - mstore(0x7460, mload(add(X1_POWERS_MPTR, 0xc0))) - mcopy(0x7480, 0x2c20, 0x80) - mstore(0x7500, mload(add(X1_POWERS_MPTR, 0xe0))) - mcopy(0x7520, 0x2ca0, 0x80) - mstore(0x75a0, mload(add(X1_POWERS_MPTR, 0x100))) - mcopy(0x75c0, 0x2d20, 0x80) - mstore(0x7640, mload(add(X1_POWERS_MPTR, 0x120))) - mcopy(0x7660, 0x2da0, 0x80) - mstore(0x76e0, mload(add(X1_POWERS_MPTR, 0x140))) - mcopy(0x7700, 0x2e20, 0x80) - mstore(0x7780, mload(add(X1_POWERS_MPTR, 0x160))) - mcopy(0x77a0, 0x2a20, 0x80) - mstore(0x7820, mload(add(X1_POWERS_MPTR, 0x180))) - mcopy(0x7840, 0x2aa0, 0x80) - mstore(0x78c0, mload(add(X1_POWERS_MPTR, 0x1a0))) - mcopy(0x78e0, 0x2b20, 0x80) - mstore(0x7960, mload(add(X1_POWERS_MPTR, 0x1c0))) - mcopy(0x7980, 0x2ba0, 0x80) - mstore(0x7a00, mload(add(X1_POWERS_MPTR, 0x1e0))) - mcopy(0x7a20, 0x2f20, 0x80) - mstore(0x7aa0, mload(add(X1_POWERS_MPTR, 0x200))) - mcopy(0x7ac0, 0x2fa0, 0x80) - mstore(0x7b40, mload(add(X1_POWERS_MPTR, 0x220))) - mcopy(0x7b60, 0x3020, 0x80) - mstore(0x7be0, mload(add(X1_POWERS_MPTR, 0x240))) - mcopy(0x7c00, 0x30a0, 0x80) - mstore(0x7c80, mload(add(X1_POWERS_MPTR, 0x260))) - mcopy(0x7ca0, 0x3120, 0x80) - mstore(0x7d20, mload(add(X1_POWERS_MPTR, 0x280))) - mcopy(0x7d40, 0x31a0, 0x80) - mstore(0x7dc0, mload(add(X1_POWERS_MPTR, 0x2a0))) - mcopy(0x7de0, 0x33a0, 0x80) - mstore(0x7e60, mload(add(X1_POWERS_MPTR, 0x2c0))) - mcopy(0x7e80, 0x3420, 0x80) - mstore(0x7f00, mload(add(X1_POWERS_MPTR, 0x2e0))) - mcopy(0x7f20, 0x3aa0, 0x80) - mstore(0x7fa0, mload(add(X1_POWERS_MPTR, 0x300))) - mcopy(0x7fc0, 0x3b20, 0x80) - mstore(0x8040, mload(add(X1_POWERS_MPTR, 0x320))) - mcopy(0x8060, 0x3ba0, 0x80) - mstore(0x80e0, mload(add(X1_POWERS_MPTR, 0x340))) - mcopy(0x8100, 0x3c20, 0x80) - mstore(0x8180, mload(add(X1_POWERS_MPTR, 0x360))) - mcopy(0x81a0, 0x3ca0, 0x80) - mstore(0x8220, mload(add(X1_POWERS_MPTR, 0x380))) - mcopy(0x8240, 0x3d20, 0x80) - mstore(0x82c0, mload(add(X1_POWERS_MPTR, 0x3a0))) - mcopy(0x82e0, 0x3da0, 0x80) - mstore(0x8360, mload(add(X1_POWERS_MPTR, 0x3c0))) - mcopy(0x8380, 0x3e20, 0x80) - mstore(0x8400, mload(add(X1_POWERS_MPTR, 0x3e0))) - mcopy(0x8420, 0x3ea0, 0x80) - mstore(0x84a0, mload(add(X1_POWERS_MPTR, 0x400))) - mcopy(0x84c0, 0x3f20, 0x80) - mstore(0x8540, mload(add(X1_POWERS_MPTR, 0x420))) + mcopy(0x8040, 0x7760, 0x80) + mstore(0x80c0, mload(add(X1_POWERS_MPTR, 0x20))) + mcopy(0x80e0, 0x79e0, 0x80) + mstore(0x8160, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0x8180, 0x77e0, 0x80) + mstore(0x8200, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0x8220, 0x7a60, 0x80) + mstore(0x82a0, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0x82c0, 0x7be0, 0x80) + mstore(0x8340, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0x8360, 0x3e20, 0x80) + mstore(0x83e0, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0x8400, 0x3ba0, 0x80) + mstore(0x8480, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0x84a0, 0x3c20, 0x80) + mstore(0x8520, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0x8540, 0x3ca0, 0x80) + mstore(0x85c0, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0x85e0, 0x3d20, 0x80) + mstore(0x8660, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0x8680, 0x3da0, 0x80) + mstore(0x8700, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0x8720, 0x39a0, 0x80) + mstore(0x87a0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0x87c0, 0x3a20, 0x80) + mstore(0x8840, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0x8860, 0x3aa0, 0x80) + mstore(0x88e0, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0x8900, 0x3b20, 0x80) + mstore(0x8980, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0x89a0, 0x3ea0, 0x80) + mstore(0x8a20, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0x8a40, 0x3f20, 0x80) + mstore(0x8ac0, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0x8ae0, 0x3fa0, 0x80) + mstore(0x8b60, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0x8b80, 0x4020, 0x80) + mstore(0x8c00, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0x8c20, 0x40a0, 0x80) + mstore(0x8ca0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0x8cc0, 0x4120, 0x80) + mstore(0x8d40, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0x8d60, 0x4320, 0x80) + mstore(0x8de0, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0x8e00, 0x43a0, 0x80) + mstore(0x8e80, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0x8ea0, 0x4a20, 0x80) + mstore(0x8f20, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0x8f40, 0x4aa0, 0x80) + mstore(0x8fc0, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0x8fe0, 0x4b20, 0x80) + mstore(0x9060, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0x9080, 0x4ba0, 0x80) + mstore(0x9100, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0x9120, 0x4c20, 0x80) + mstore(0x91a0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0x91c0, 0x4ca0, 0x80) + mstore(0x9240, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0x9260, 0x4d20, 0x80) + mstore(0x92e0, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0x9300, 0x4da0, 0x80) + mstore(0x9380, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0x93a0, 0x4e20, 0x80) + mstore(0x9420, mload(add(X1_POWERS_MPTR, 0x400))) + mcopy(0x9440, 0x4ea0, 0x80) + mstore(0x94c0, mload(add(X1_POWERS_MPTR, 0x420))) let lin_query_scalar_33 := mload(add(X1_POWERS_MPTR, 0x440)) let lin_cur_scalar_33 := mulmod(lin_query_scalar_33, lin_one_minus_x_n, r) - mcopy(0x8560, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) - mstore(0x85e0, lin_cur_scalar_33) + mcopy(0x94e0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0x9560, lin_cur_scalar_33) lin_cur_scalar_33 := mulmod(lin_cur_scalar_33, lin_x_split, r) - mcopy(0x8600, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) - mstore(0x8680, lin_cur_scalar_33) + mcopy(0x9580, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0x9600, lin_cur_scalar_33) lin_cur_scalar_33 := mulmod(lin_cur_scalar_33, lin_x_split, r) - mcopy(0x86a0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) - mstore(0x8720, lin_cur_scalar_33) + mcopy(0x9620, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0x96a0, lin_cur_scalar_33) lin_cur_scalar_33 := mulmod(lin_cur_scalar_33, lin_x_split, r) - mcopy(0x8740, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) - mstore(0x87c0, lin_cur_scalar_33) - mcopy(0x87e0, 0x3220, 0x80) - mstore(0x8860, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) - mcopy(0x8880, 0x32a0, 0x80) - mstore(0x8900, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) - mcopy(0x8920, 0x3320, 0x80) - mstore(0x89a0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) - mcopy(0x89c0, 0x34a0, 0x80) - mstore(0x8a40, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) - mcopy(0x8a60, 0x3520, 0x80) - mstore(0x8ae0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) - mcopy(0x8b00, 0x35a0, 0x80) - mstore(0x8b80, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) - mcopy(0x8ba0, 0x3620, 0x80) - mstore(0x8c20, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) - mcopy(0x8c40, 0x36a0, 0x80) - mstore(0x8cc0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) - mcopy(0x8ce0, 0x3720, 0x80) - mstore(0x8d60, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) - mcopy(0x8d80, 0x37a0, 0x80) - mstore(0x8e00, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) - mcopy(0x8e20, 0x3820, 0x80) - mstore(0x8ea0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x140)), r)) - mcopy(0x8ec0, 0x38a0, 0x80) - mstore(0x8f40, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x160)), r)) - mcopy(0x8f60, 0x3920, 0x80) - mstore(0x8fe0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x180)), r)) - mcopy(0x9000, 0x39a0, 0x80) - mstore(0x9080, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x1a0)), r)) - mcopy(0x90a0, 0x3a20, 0x80) - mstore(0x9120, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x1c0)), r)) - mcopy(0x9140, 0x69e0, 0x80) - mstore(0x91c0, x4_pow_1) - mcopy(0x91e0, 0x6b60, 0x80) - mstore(0x9260, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) - mcopy(0x9280, 0x6be0, 0x80) - mstore(0x9300, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) - mcopy(0x9320, 0x63e0, 0x80) - mstore(0x93a0, x4_pow_2) - mcopy(0x93c0, 0x6460, 0x80) - mstore(0x9440, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) - mcopy(0x9460, 0x64e0, 0x80) - mstore(0x94e0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) - mcopy(0x9500, 0x6560, 0x80) - mstore(0x9580, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_2, r)) - mcopy(0x95a0, 0x65e0, 0x80) - mstore(0x9620, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_2, r)) - mcopy(0x9640, 0x6660, 0x80) - mstore(0x96c0, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_2, r)) - mcopy(0x96e0, 0x66e0, 0x80) - mstore(0x9760, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_2, r)) - mcopy(0x9780, 0x6760, 0x80) - mstore(0x9800, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_2, r)) - mcopy(0x9820, 0x68e0, 0x80) - mstore(0x98a0, x4_pow_3) - mcopy(0x98c0, 0x6960, 0x80) - mstore(0x9940, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) - mcopy(0x9960, F_COM_MPTR, 0x80) - mstore(0x99e0, x4_pow_4) + mcopy(0x96c0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0x9740, lin_cur_scalar_33) + mcopy(0x9760, 0x41a0, 0x80) + mstore(0x97e0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0x9800, 0x4220, 0x80) + mstore(0x9880, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0x98a0, 0x42a0, 0x80) + mstore(0x9920, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0x9940, 0x4420, 0x80) + mstore(0x99c0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0x99e0, 0x44a0, 0x80) + mstore(0x9a60, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) + mcopy(0x9a80, 0x4520, 0x80) + mstore(0x9b00, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) + mcopy(0x9b20, 0x45a0, 0x80) + mstore(0x9ba0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) + mcopy(0x9bc0, 0x4620, 0x80) + mstore(0x9c40, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) + mcopy(0x9c60, 0x46a0, 0x80) + mstore(0x9ce0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) + mcopy(0x9d00, 0x4720, 0x80) + mstore(0x9d80, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) + mcopy(0x9da0, 0x47a0, 0x80) + mstore(0x9e20, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x140)), r)) + mcopy(0x9e40, 0x4820, 0x80) + mstore(0x9ec0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x160)), r)) + mcopy(0x9ee0, 0x48a0, 0x80) + mstore(0x9f60, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x180)), r)) + mcopy(0x9f80, 0x4920, 0x80) + mstore(0xa000, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x1a0)), r)) + mcopy(0xa020, 0x49a0, 0x80) + mstore(0xa0a0, mulmod(lin_query_scalar_33, mload(add(SELECTOR_ACC_MPTR, 0x1c0)), r)) + mcopy(0xa0c0, 0x7960, 0x80) + mstore(0xa140, x4_pow_1) + mcopy(0xa160, 0x7ae0, 0x80) + mstore(0xa1e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0xa200, 0x7b60, 0x80) + mstore(0xa280, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0xa2a0, 0x7360, 0x80) + mstore(0xa320, x4_pow_2) + mcopy(0xa340, 0x73e0, 0x80) + mstore(0xa3c0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0xa3e0, 0x7460, 0x80) + mstore(0xa460, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) + mcopy(0xa480, 0x74e0, 0x80) + mstore(0xa500, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_2, r)) + mcopy(0xa520, 0x7560, 0x80) + mstore(0xa5a0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_2, r)) + mcopy(0xa5c0, 0x75e0, 0x80) + mstore(0xa640, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_2, r)) + mcopy(0xa660, 0x7660, 0x80) + mstore(0xa6e0, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_2, r)) + mcopy(0xa700, 0x76e0, 0x80) + mstore(0xa780, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_2, r)) + mcopy(0xa7a0, 0x7860, 0x80) + mstore(0xa820, x4_pow_3) + mcopy(0xa840, 0x78e0, 0x80) + mstore(0xa8c0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) + mcopy(0xa8e0, F_COM_MPTR, 0x80) + mstore(0xa960, x4_pow_4) if success { - success := staticcall(gas(), 0x0c, 0x70c0, 0x2940, FINAL_COM_MPTR, 0x80) + success := staticcall(gas(), 0x0c, 0x8040, 0x2940, FINAL_COM_MPTR, 0x80) success := and(success, eq(returndatasize(), 0x80)) } mstore(V_MPTR, v) @@ -3164,28 +3262,28 @@ contract Halo2Verifier { // Scale z*pi - vG before the final pairing check // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) - mcopy(0x80, G1_BASE_MPTR, 0x80) - mstore(0x100, addmod(0, sub(r, mload(V_MPTR)), r)) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) if success { - success := staticcall(gas(), 0x0c, 0x80, 0xa0, 0x80, 0x80) + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, FINAL_COM_MPTR, 0x80) + mcopy(0x1080, FINAL_COM_MPTR, 0x80) if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, PI_MPTR, 0x80) - mstore(0x180, mload(X3_MPTR)) + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) if success { - success := staticcall(gas(), 0x0c, 0x100, 0xa0, 0x100, 0x80) + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) success := and(success, eq(returndatasize(), 0x80)) } if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(PAIRING_RHS_MPTR, 0x80, 0x80) + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) } } @@ -3223,6 +3321,12 @@ contract Halo2Verifier { // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) } diff --git a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol index 17578c207..fcfb39dad 100644 --- a/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol +++ b/proofs/solidity-verifier/target/hybrid-mt-fixture-dump/Halo2VerifyingKey.sol @@ -94,8 +94,8 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x03a0), 0x00000000000000000000000000000000006d57f79a18220d1e5ef04bd519e995) // neg_s_g2_y_c1_hi mstore(add(payload, 0x03c0), 0x9a9cc71553bb761b5422a6b6971b75c8d3695bfa07b861c4b1c958da426efc45) // neg_s_g2_y_c1_lo mstore(add(payload, 0x03e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // quotient_const - mstore(add(payload, 0x0400), 0x0000000000000000000000000000000000000000000000000000040000000000) // quotient_const - mstore(add(payload, 0x0420), 0x0000000000000000000000000000000000000000000000000000000000100000) // quotient_const + mstore(add(payload, 0x0400), 0x0000000000000000000000000000000000000000000000000000000000100000) // quotient_const + mstore(add(payload, 0x0420), 0x0000000000000000000000000000000000000000000000000000040000000000) // quotient_const mstore(add(payload, 0x0440), 0x0000000000000000000000000000000000000000000000000000000000000002) // quotient_const mstore(add(payload, 0x0460), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffefff00001) // quotient_const mstore(add(payload, 0x0480), 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffe00001) // quotient_const @@ -119,19 +119,19 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x06c0), 0x0000000000000000000000000000000000000000000000000000000000040011) // quotient_const mstore(add(payload, 0x06e0), 0x0000000000000000000000000000000000000000000000000000001100000000) // quotient_const mstore(add(payload, 0x0700), 0x0000000000000000000000000000000000000000000000000000000044000000) // quotient_const - mstore(add(payload, 0x0720), 0x0000000000000000000000000000000000000000000000000000000000200000) // quotient_const - mstore(add(payload, 0x0740), 0x0000000000000000000000000000000000000000000000000000000000000400) // quotient_const - mstore(add(payload, 0x0760), 0x0000000000000000000000000000000000000000000000000000000000400000) // quotient_const + mstore(add(payload, 0x0720), 0x0000000000000000000000000000000000000000000000000000000000000400) // quotient_const + mstore(add(payload, 0x0740), 0x0000000000000000000000000000000000000000000000000000000000200000) // quotient_const + mstore(add(payload, 0x0760), 0x0000000000000000000000000000000000000000000000000000000000000004) // quotient_const mstore(add(payload, 0x0780), 0x0000000000000000000000000000000000000000000000000000000000002000) // quotient_const - mstore(add(payload, 0x07a0), 0x0000000000000000000000000000000000000000000000000000000000000004) // quotient_const - mstore(add(payload, 0x07c0), 0x0000000000000000000000000000000000000000000000000000100000000000) // quotient_const + mstore(add(payload, 0x07a0), 0x0000000000000000000000000000000000000000000000000000000000400000) // quotient_const + mstore(add(payload, 0x07c0), 0x0000000000000000000000000000000000000000000000000000000000000010) // quotient_const mstore(add(payload, 0x07e0), 0x0000000000000000000000000000000000000000000000000000000004000000) // quotient_const - mstore(add(payload, 0x0800), 0x0000000000000000000000000000000000000000000000000000000000000010) // quotient_const - mstore(add(payload, 0x0820), 0x0000000000000000000000000000000000000000000000000000000002000000) // quotient_const + mstore(add(payload, 0x0800), 0x0000000000000000000000000000000000000000000000000000100000000000) // quotient_const + mstore(add(payload, 0x0820), 0x0000000000000000000000000000000000000000000000000000000000000040) // quotient_const mstore(add(payload, 0x0840), 0x0000000000000000000000000000000000000000000000000000000000000800) // quotient_const - mstore(add(payload, 0x0860), 0x0000000000000000000000000000000000000000000000000000000000000040) // quotient_const - mstore(add(payload, 0x0880), 0x0000000000000000000000000000000000000000000000000004000000000000) // quotient_const - mstore(add(payload, 0x08a0), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x0860), 0x0000000000000000000000000000000000000000000000000000000002000000) // quotient_const + mstore(add(payload, 0x0880), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x08a0), 0x0000000000000000000000000000000000000000000000000004000000000000) // quotient_const mstore(add(payload, 0x08c0), 0x0000000000000000000000000000000000000000000000000000000000040000) // quotient_const mstore(add(payload, 0x08e0), 0x0000000000000000000000000000000000000000000000000000000000000080) // quotient_const mstore(add(payload, 0x0900), 0x0000000000000000000000000000000000000000000000000000000000000008) // quotient_const @@ -141,25 +141,25 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x0980), 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140) // quotient_const mstore(add(payload, 0x09a0), 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a) // quotient_const mstore(add(payload, 0x09c0), 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db) // quotient_const - mstore(add(payload, 0x09e0), 0x055b80105ea0055c2008060d000b020001055ca0105cc0105ba00901115d0013) // quotient_program - mstore(add(payload, 0x0a00), 0x5b6002105c000901115ce0135b4002105be00d030608060d000b030000055ca0) // quotient_program - mstore(add(payload, 0x0a20), 0x105cc00901115d00135b6002105c000901115ce0135b4002105be00d03060806) // quotient_program - mstore(add(payload, 0x0a40), 0x0d000b040000055b80105ba0055bc008060d000b0400011b00001b0001210001) // quotient_program - mstore(add(payload, 0x0a60), 0x00000700045b40055b60065b80075ba0085bc0095be00a5c000b5c200c5ca00d) // quotient_program - mstore(add(payload, 0x0a80), 0x5cc00e5ce00f5d00105d20115d400b07000021000100000700045b40055b6012) // quotient_program - mstore(add(payload, 0x0aa0), 0x5b80135ba0145bc0095be00a5c00155c20165ca0175cc00e5ce00f5d00185d20) // quotient_program - mstore(add(payload, 0x0ac0), 0x195d400b080000091a115d60135c601b105d80055da008060d000b090000091c) // quotient_program - mstore(add(payload, 0x0ae0), 0x115d60135dc01d135c601e105c80055da008060d000b0a0000091f115ce0135d) // quotient_program - mstore(add(payload, 0x0b00), 0x0020135b4021105b60055b8008060d000b0a00010922115d60135dc01d135c60) // quotient_program - mstore(add(payload, 0x0b20), 0x23135c8024105d80055da008060d000b0b00000925115ce0135d0020135b401c) // quotient_program - mstore(add(payload, 0x0b40), 0x135b6026105be0055b8008060d000b0b00011c275c40285c60295c80025d601b) // quotient_program - mstore(add(payload, 0x0b60), 0x5dc02a5de02b5e00105d80055da008060d000b0c0000090008105de0115de00d) // quotient_program - mstore(add(payload, 0x0b80), 0x000b0c0001090008105c40115c400d000b0c0001090008105e00115e000d000b) // quotient_program - mstore(add(payload, 0x0ba0), 0x0c00011c005ba0005bc0005c20005ca0005cc0005d20005d40055da0135e202c) // quotient_program - mstore(add(payload, 0x0bc0), 0x08060d000b0d0000055b40115b40115b40055ba008060d000b0e0000055b6011) // quotient_program - mstore(add(payload, 0x0be0), 0x5b60115b60055bc008060d000b0e0001055b80115b80115b80055c4008060d00) // quotient_program - mstore(add(payload, 0x0c00), 0x0b0e00011b00021b0003055c2008105f40055b40115b40115ba00d2d06055b60) // quotient_program - mstore(add(payload, 0x0c20), 0x115b60115bc00d2e06055b80115b80115c400d2f060d000b0e0001191f000000) // quotient_program + mstore(add(payload, 0x09e0), 0x056b00106e20056ba008060d000b020001056c20106c40106b200902116c8013) // quotient_program + mstore(add(payload, 0x0a00), 0x6ae001106b800902116c60136ac001106b600d030608060d000b030000056c20) // quotient_program + mstore(add(payload, 0x0a20), 0x106c400902116c80136ae001106b800902116c60136ac001106b600d03060806) // quotient_program + mstore(add(payload, 0x0a40), 0x0d000b040000056b00106b20056b4008060d000b0400011b00001b0001210001) // quotient_program + mstore(add(payload, 0x0a60), 0x00000700046ac0056ae0066b00076b20086b40096b600a6b800b6ba00c6c200d) // quotient_program + mstore(add(payload, 0x0a80), 0x6c400e6c600f6c80106ca0116cc00b07000021000100000700046ac0056ae012) // quotient_program + mstore(add(payload, 0x0aa0), 0x6b00136b20146b40096b600a6b80156ba0166c20176c400e6c600f6c80186ca0) // quotient_program + mstore(add(payload, 0x0ac0), 0x196cc00b080000091b116ce0136be01a106d00056d2008060d000b090000091e) // quotient_program + mstore(add(payload, 0x0ae0), 0x116ce0136d401d136be01c106c00056d2008060d000b0a00000921116c60136c) // quotient_program + mstore(add(payload, 0x0b00), 0x8020136ac01f106ae0056b0008060d000b0a00010924116ce0136d401d136be0) // quotient_program + mstore(add(payload, 0x0b20), 0x23136c0022106d00056d2008060d000b0b00000926116c60136c8020136ac01e) // quotient_program + mstore(add(payload, 0x0b40), 0x136ae025106b60056b0008060d000b0b00011c276bc0286be0296c00016ce01a) // quotient_program + mstore(add(payload, 0x0b60), 0x6d402a6d602b6d80106d00056d2008060d000b0c0000090008106d60116d600d) // quotient_program + mstore(add(payload, 0x0b80), 0x000b0c0001090008106bc0116bc00d000b0c0001090008106d80116d800d000b) // quotient_program + mstore(add(payload, 0x0ba0), 0x0c00011c006b20006b40006ba0006c20006c40006ca0006cc0056d20136da02c) // quotient_program + mstore(add(payload, 0x0bc0), 0x08060d000b0d0000056ac0116ac0116ac0056b2008060d000b0e0000056ae011) // quotient_program + mstore(add(payload, 0x0be0), 0x6ae0116ae0056b4008060d000b0e0001056b00116b00116b00056bc008060d00) // quotient_program + mstore(add(payload, 0x0c00), 0x0b0e00011b00021b0003056ba008106ec0056ac0116ac0116b200d2d06056ae0) // quotient_program + mstore(add(payload, 0x0c20), 0x116ae0116b400d2e06056b00116b00116bc00d2f060d000b0e0001191f000000) // quotient_program // Fixed-column commitment 0, stored as one // EIP-2537 padded uncompressed G1 slot. mstore(add(payload, 0x0c40), 0x0000000000000000000000000000000002586b66bd923976cc5f5c9ff774e3c2) // fixed_comms[0].x_hi diff --git a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2QuotientEvaluator.sol b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2QuotientEvaluator.sol index 6e74608d2..c2bc6699d 100644 --- a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2QuotientEvaluator.sol +++ b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2QuotientEvaluator.sol @@ -47,53 +47,53 @@ contract Halo2QuotientEvaluator { // Start of the copied verifier-key payload in memory. The VK payload also // carries the compact quotient VM constant/program tables used by the // included numerator block. - uint256 internal constant VK_MPTR = 0x2700; + uint256 internal constant VK_MPTR = 0x3680; // Fiat-Shamir challenge slots. Halo2Verifier sampled these in transcript // order before the external call. The evaluator only reads them. - uint256 internal constant CHALLENGE_MPTR = 0x6980; - uint256 internal constant THETA_MPTR = 0x6980; - uint256 internal constant BETA_MPTR = 0x69a0; - uint256 internal constant GAMMA_MPTR = 0x69c0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x69e0; - uint256 internal constant Y_MPTR = 0x6a00; - uint256 internal constant X_MPTR = 0x6a20; - uint256 internal constant X1_MPTR = 0x6a40; - uint256 internal constant X2_MPTR = 0x6a60; - uint256 internal constant X3_MPTR = 0x6a80; - uint256 internal constant X4_MPTR = 0x6aa0; + uint256 internal constant CHALLENGE_MPTR = 0x7900; + uint256 internal constant THETA_MPTR = 0x7900; + uint256 internal constant BETA_MPTR = 0x7920; + uint256 internal constant GAMMA_MPTR = 0x7940; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x7960; + uint256 internal constant Y_MPTR = 0x7980; + uint256 internal constant X_MPTR = 0x79a0; + uint256 internal constant X1_MPTR = 0x79c0; + uint256 internal constant X2_MPTR = 0x79e0; + uint256 internal constant X3_MPTR = 0x7a00; + uint256 internal constant X4_MPTR = 0x7a20; // Common polynomial values at x. Halo2Verifier computes these once after // sampling x and places them in the frame so the numerator block can share // the exact Rust verifier inputs. - uint256 internal constant X_N_MPTR = 0x6cc0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x6ce0; - uint256 internal constant L_LAST_MPTR = 0x6d00; - uint256 internal constant L_BLIND_MPTR = 0x6d20; - uint256 internal constant L_0_MPTR = 0x6d40; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x6d60; - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x6d80; + uint256 internal constant X_N_MPTR = 0x7c40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x7c60; + uint256 internal constant L_LAST_MPTR = 0x7c80; + uint256 internal constant L_BLIND_MPTR = 0x7ca0; + uint256 internal constant L_0_MPTR = 0x7cc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x7ce0; + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x7d00; // Proof evaluation table. Values are already decoded as canonical Fr words // by Halo2Verifier. The generated numerator code indexes this table by the // same query order as the Rust verifier. - uint256 internal constant REVERSED_EVALS_MPTR = 0x8500; + uint256 internal constant REVERSED_EVALS_MPTR = 0x9480; // Scratch/output region for simple-selector linearization accumulators. // The numerator block writes one bucket per simple selector, then the // fallback copies those buckets into the compact return frame. - uint256 internal constant SELECTOR_ACC_MPTR = 0xa1c0; + uint256 internal constant SELECTOR_ACC_MPTR = 0xb140; // Callee-local scratch for trace hooks. Trace-enabled verifier builds call // this evaluator with CALL so quotient identity logs can be compared with // the native Rust trace. Production verifier builds keep using STATICCALL // and render this evaluator without trace hooks. - uint256 internal constant TRACE_U256_MPTR = 0x80; - uint256 internal constant QUOTIENT_OUTPUT_MPTR = 0x80; + uint256 internal constant TRACE_U256_MPTR = 0x1000; + uint256 internal constant QUOTIENT_OUTPUT_MPTR = 0x1000; // External-call frame metadata. The main verifier calls this contract with // exactly QUOTIENT_FRAME_LEN bytes starting at // QUOTIENT_FRAME_BASE, then checks the return length and QUOTIENT_MAGIC. - uint256 internal constant QUOTIENT_FRAME_BASE = 0x2700; + uint256 internal constant QUOTIENT_FRAME_BASE = 0x3680; uint256 internal constant QUOTIENT_FRAME_LEN = 0x6ac0; uint256 internal constant QUOTIENT_OUTPUT_LEN = 0x0180; uint256 internal constant QUOTIENT_MAGIC = 0x00000000000000000000000000000000000000000000000051554556414c0001; @@ -251,15 +251,15 @@ contract Halo2QuotientEvaluator { // q_const_mptr points to Fr constants used by the VM. // q_program_mptr points to the bytecode stream. // Constants are stored as consecutive 32-byte Fr words. - let q_const_mptr := 0x2ae0 + let q_const_mptr := 0x3a60 // Program bytes are also stored in the VK payload, packed into // 32-byte words by PackedProgramCodec. - let q_program_mptr := 0x4120 + let q_program_mptr := 0x50a0 // Running Horner accumulator for fully evaluated identities. // After all identities, this is nu_y(x) for the `None` // identity group. // Initialize A = 0 before scanning the identity stream. - mstore(0xa300, 0) + mstore(0xb280, 0) // Simple selectors are grouped into separate linearization // buckets. They start at zero for every proof. // q_sel_zero_off walks selector bucket byte offsets. @@ -274,12 +274,19 @@ contract Halo2QuotientEvaluator { { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0xb2c0, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 49) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) // Store y^i at selector_power_mptr + 32*i. - mstore(add(0xa340, shl(5, q_y_power_i)), q_y_power) + mstore(add(0xb2c0, shl(5, q_y_power_i)), q_y_power) } } @@ -288,102 +295,102 @@ contract Halo2QuotientEvaluator { // VM/native identities, so they occupy the same y-batch order. { let var0 := 0x1 - let f_3 := mload(0x8b40) - let f_4 := mload(0x8a40) - let a_0 := mload(0x8520) + let f_3 := mload(0x9ac0) + let f_4 := mload(0x99c0) + let a_0 := mload(0x94a0) let var1 := mulmod(f_4, a_0, r) let var2 := addmod(f_3, var1, r) - let f_5 := mload(0x8a60) - let a_1 := mload(0x8540) + let f_5 := mload(0x99e0) + let a_1 := mload(0x94c0) let var3 := mulmod(f_5, a_1, r) let var4 := addmod(var2, var3, r) - let f_6 := mload(0x8a80) - let a_2 := mload(0x8560) + let f_6 := mload(0x9a00) + let a_2 := mload(0x94e0) let var5 := mulmod(f_6, a_2, r) let var6 := addmod(var4, var5, r) - let f_7 := mload(0x8aa0) - let a_3 := mload(0x8580) + let f_7 := mload(0x9a20) + let a_3 := mload(0x9500) let var7 := mulmod(f_7, a_3, r) let var8 := addmod(var6, var7, r) - let f_8 := mload(0x8ac0) - let a_4 := mload(0x85a0) + let f_8 := mload(0x9a40) + let a_4 := mload(0x9520) let var9 := mulmod(f_8, a_4, r) let var10 := addmod(var8, var9, r) - let f_0 := mload(0x8ae0) - let a_0_next_1 := mload(0x85c0) + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) let var11 := mulmod(f_0, a_0_next_1, r) let var12 := addmod(var10, var11, r) - let f_1 := mload(0x8b00) + let f_1 := mload(0x9a80) let var13 := mulmod(f_1, a_0, r) let var14 := mulmod(var13, a_1, r) let var15 := addmod(var12, var14, r) - let f_2 := mload(0x8b20) + let f_2 := mload(0x9aa0) let var16 := mulmod(f_2, a_0, r) let var17 := mulmod(var16, a_2, r) let var18 := addmod(var15, var17, r) let var19 := mulmod(var0, var18, r) - mstore(0xa960, var19) + mstore(0xb8e0, var19) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } { let var0 := 0x1 - let a_1 := mload(0x8540) - let a_2 := mload(0x8560) + let a_1 := mload(0x94c0) + let a_2 := mload(0x94e0) let var1 := addmod(a_1, a_2, r) - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var2 := addmod(0, sub(r, a_3), r) let var3 := addmod(var1, var2, r) - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var4 := addmod(0, sub(r, a_4), r) let var5 := addmod(var3, var4, r) let var6 := mulmod(var0, var5, r) - mstore(0xa960, var6) + mstore(0xb8e0, var6) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } { let var0 := 0x1 - let a_0 := mload(0x8520) - let f_4 := mload(0x8a40) + let a_0 := mload(0x94a0) + let f_4 := mload(0x99c0) let var1 := addmod(a_0, f_4, r) - let a_0_next_1 := mload(0x85c0) + let a_0_next_1 := mload(0x9540) let var2 := addmod(0, sub(r, a_0_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0xa960, var4) + mstore(0xb8e0, var4) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } { let var0 := 0x1 - let a_1 := mload(0x8540) - let f_5 := mload(0x8a60) + let a_1 := mload(0x94c0) + let f_5 := mload(0x99e0) let var1 := addmod(a_1, f_5, r) - let a_1_next_1 := mload(0x85e0) + let a_1_next_1 := mload(0x9560) let var2 := addmod(0, sub(r, a_1_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0xa960, var4) + mstore(0xb8e0, var4) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0xa340, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } // VM registers: @@ -403,7 +410,7 @@ contract Halo2QuotientEvaluator { // q_end is an exclusive byte pointer for the VM loop. let q_end := add(q_program_mptr, 0x11cf) // q_sp starts at the first free stack word. - let q_sp := 0xa960 + let q_sp := 0xb8e0 // q_top is meaningless until q_has_top is set. let q_top := 0 // q_has_top = 0 means the VM stack is empty. @@ -677,82 +684,82 @@ contract Halo2QuotientEvaluator { // stack. The Rust memory planner must reserve enough // words for structured_permutation_scratch_words(meta) // whenever this opcode can appear. - q_sp := 0xa960 + q_sp := 0xb8e0 // The generated lines below call the same fold snippets // used by interpreted expressions, so trace IDs and // y-batch positions remain contiguous. { let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 - let q_perm_vals := 0xa960 - let q_perm_sigmas := 0xaba0 - let q_perm_z_cur := 0xade0 - let q_perm_z_next := 0xaea0 - let q_perm_z_last := 0xaf60 - let q_perm_delta_base_ptr := 0xb000 + let q_perm_vals := 0xb8e0 + let q_perm_sigmas := 0xbb20 + let q_perm_z_cur := 0xbd60 + let q_perm_z_next := 0xbe20 + let q_perm_z_last := 0xbee0 + let q_perm_delta_base_ptr := 0xbf80 let q_perm_num_cols := 18 let q_perm_num_sets := 6 let q_perm_chunk_len := 3 let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 - mstore(add(q_perm_vals, 0x0), mload(0x8a20)) + mstore(add(q_perm_vals, 0x0), mload(0x99a0)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x8520, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x94a0, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0xc0), mload(0x8500)) + mstore(add(q_perm_vals, 0xc0), mload(0x9480)) mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 9) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x100), q_perm_val_load_dst_off), mload(add(0x8620, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x100), q_perm_val_load_dst_off), mload(add(0x95a0, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0x220), mload(0x8a00)) + mstore(add(q_perm_vals, 0x220), mload(0x9980)) { for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 18) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off - mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x8c40, q_perm_sigma_load_src_off))) + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x9bc0, q_perm_sigma_load_src_off))) } } { for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 6) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) - mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x8e80, q_perm_z_cur_load_src_off))) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x9e00, q_perm_z_cur_load_src_off))) } } { for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 6) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) - mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x8ea0, q_perm_z_next_load_src_off))) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x9e20, q_perm_z_next_load_src_off))) } } { for { let q_perm_z_last_load_i := 0 } lt(q_perm_z_last_load_i, 5) { q_perm_z_last_load_i := add(q_perm_z_last_load_i, 1) } { let q_perm_z_last_load_dst_off := shl(5, q_perm_z_last_load_i) let q_perm_z_last_load_src_off := mul(q_perm_z_last_load_i, 0x60) - mstore(add(add(q_perm_z_last, 0x0), q_perm_z_last_load_dst_off), mload(add(0x8ec0, q_perm_z_last_load_src_off))) + mstore(add(add(q_perm_z_last, 0x0), q_perm_z_last_load_dst_off), mload(add(0x9e40, q_perm_z_last_load_src_off))) } } let q_perm_eval := 0 q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_perm_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) let q_perm_zn := mload(add(q_perm_z_cur, 0xa0)) q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_perm_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) for { let q_perm_i := 1 } lt(q_perm_i, 6) { q_perm_i := add(q_perm_i, 1) } { let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_perm_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) } mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) for { let q_perm_set := 0 } lt(q_perm_set, 6) { q_perm_set := add(q_perm_set, 1) } { @@ -771,8 +778,8 @@ contract Halo2QuotientEvaluator { q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) } q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_perm_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_perm_eval, r)) mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) } } @@ -793,13 +800,13 @@ contract Halo2QuotientEvaluator { // f+beta/prefix/suffix scratch rather than as a // conventional VM stack. The Rust memory planner must // reserve structured_lookup_scratch_words(meta). - q_sp := 0xa960 + q_sp := 0xb8e0 // Generated LogUp code follows the same y-batch order // as the Rust identity stream. { - let q_lookup_f := 0xa960 - let q_lookup_prefix := 0xa9e0 - let q_lookup_suffix := 0xaa60 + let q_lookup_f := 0xb8e0 + let q_lookup_prefix := 0xb960 + let q_lookup_suffix := 0xb9e0 let q_lookup_l0 := mload(L_0_MPTR) let q_lookup_llast := mload(L_LAST_MPTR) let q_lookup_lblind := mload(L_BLIND_MPTR) @@ -809,17 +816,17 @@ contract Halo2QuotientEvaluator { let q_lookup_theta := mload(THETA_MPTR) { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x90e0), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa060), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } { - let f_10 := mload(0x8b60) + let f_10 := mload(0x9ae0) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) let var1 := mulmod(var0, q_lookup_theta, r) for { let q_lookup_shared_i := 0 } lt(q_lookup_shared_i, 4) { q_lookup_shared_i := add(q_lookup_shared_i, 1) } { let q_lookup_shared_off := shl(5, q_lookup_shared_i) - let q_lookup_shared_tail := mload(add(0x8540, q_lookup_shared_off)) + let q_lookup_shared_tail := mload(add(0x94c0, q_lookup_shared_off)) let q_lookup_shared_compressed := addmod(var1, q_lookup_shared_tail, r) mstore(add(q_lookup_f, q_lookup_shared_off), addmod(q_lookup_shared_compressed, q_lookup_beta, r)) } @@ -841,130 +848,130 @@ contract Halo2QuotientEvaluator { for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 4) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) } - let q_lookup_eval := addmod(mulmod(mload(0x90c0), q_lookup_product, r), sub(r, q_lookup_sum), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0xa040), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x90c0) - let f_17 := mload(0x8be0) - let f_11 := mload(0x8b80) + let q_lookup_sum_h := mload(0xa040) + let f_17 := mload(0x9b60) + let f_11 := mload(0x9b00) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) - let f_12 := mload(0x8ba0) + let f_12 := mload(0x9b20) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) let q_lookup_s_sum_h := mulmod(f_17, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x9100), sub(r, addmod(mload(0x90e0), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0xa080), sub(r, addmod(mload(0xa060), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x90a0), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa020), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } } { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x9160), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0xa0e0), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } { - let a_14 := mload(0x8a00) + let a_14 := mload(0x9980) let var0 := addmod(mulmod(0, q_lookup_theta, r), a_14, r) - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_0, r) - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var2 := addmod(mulmod(var1, q_lookup_theta, r), a_1, r) - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var3 := addmod(mulmod(var2, q_lookup_theta, r), a_2, r) - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var4 := addmod(mulmod(var3, q_lookup_theta, r), a_3, r) - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var5 := addmod(mulmod(var4, q_lookup_theta, r), a_4, r) - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var6 := addmod(mulmod(var5, q_lookup_theta, r), a_5, r) - let a_6 := mload(0x8640) + let a_6 := mload(0x95c0) let var7 := addmod(mulmod(var6, q_lookup_theta, r), a_6, r) - let a_7 := mload(0x8660) + let a_7 := mload(0x95e0) let var8 := addmod(mulmod(var7, q_lookup_theta, r), a_7, r) - let a_8 := mload(0x8680) + let a_8 := mload(0x9600) let var9 := addmod(mulmod(var8, q_lookup_theta, r), a_8, r) - let a_9 := mload(0x86a0) + let a_9 := mload(0x9620) let var10 := addmod(mulmod(var9, q_lookup_theta, r), a_9, r) - let a_10 := mload(0x86c0) + let a_10 := mload(0x9640) let var11 := addmod(mulmod(var10, q_lookup_theta, r), a_10, r) - let a_11 := mload(0x86e0) + let a_11 := mload(0x9660) let var12 := addmod(mulmod(var11, q_lookup_theta, r), a_11, r) - let a_12 := mload(0x8700) + let a_12 := mload(0x9680) let var13 := addmod(mulmod(var12, q_lookup_theta, r), a_12, r) - let a_13 := mload(0x8720) + let a_13 := mload(0x96a0) let var14 := addmod(mulmod(var13, q_lookup_theta, r), a_13, r) - let f_13 := mload(0x8bc0) + let f_13 := mload(0x9b40) let var15 := addmod(mulmod(var14, q_lookup_theta, r), f_13, r) - let q_lookup_eval := addmod(mulmod(mload(0x9140), addmod(var15, q_lookup_beta, r), r), sub(r, 1), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0xa0c0), addmod(var15, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x9140) + let q_lookup_sum_h := mload(0xa0c0) let var0 := 0x1 - let f_26 := mload(0x8c20) + let f_26 := mload(0x9ba0) let var1 := addmod(0, sub(r, f_26), r) let var2 := addmod(var0, var1, r) - let a_14 := mload(0x8a00) + let a_14 := mload(0x9980) let var3 := mulmod(var2, a_14, r) let var4 := addmod(mulmod(0, q_lookup_theta, r), var3, r) - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var5 := mulmod(var2, a_0, r) let var6 := addmod(mulmod(var4, q_lookup_theta, r), var5, r) - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var7 := mulmod(var2, a_1, r) let var8 := addmod(mulmod(var6, q_lookup_theta, r), var7, r) - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var9 := mulmod(var2, a_2, r) let var10 := addmod(mulmod(var8, q_lookup_theta, r), var9, r) - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var11 := mulmod(var2, a_3, r) let var12 := addmod(mulmod(var10, q_lookup_theta, r), var11, r) - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var13 := mulmod(var2, a_4, r) let var14 := addmod(mulmod(var12, q_lookup_theta, r), var13, r) - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var15 := mulmod(var2, a_5, r) let var16 := addmod(mulmod(var14, q_lookup_theta, r), var15, r) - let a_6 := mload(0x8640) + let a_6 := mload(0x95c0) let var17 := mulmod(var2, a_6, r) let var18 := addmod(mulmod(var16, q_lookup_theta, r), var17, r) - let a_7 := mload(0x8660) + let a_7 := mload(0x95e0) let var19 := mulmod(var2, a_7, r) let var20 := addmod(mulmod(var18, q_lookup_theta, r), var19, r) - let a_8 := mload(0x8680) + let a_8 := mload(0x9600) let var21 := mulmod(var2, a_8, r) let var22 := addmod(mulmod(var20, q_lookup_theta, r), var21, r) - let a_9 := mload(0x86a0) + let a_9 := mload(0x9620) let var23 := mulmod(var2, a_9, r) let var24 := addmod(mulmod(var22, q_lookup_theta, r), var23, r) - let a_10 := mload(0x86c0) + let a_10 := mload(0x9640) let var25 := mulmod(var2, a_10, r) let var26 := addmod(mulmod(var24, q_lookup_theta, r), var25, r) - let a_11 := mload(0x86e0) + let a_11 := mload(0x9660) let var27 := mulmod(var2, a_11, r) let var28 := addmod(mulmod(var26, q_lookup_theta, r), var27, r) - let a_12 := mload(0x8700) + let a_12 := mload(0x9680) let var29 := mulmod(var2, a_12, r) let var30 := addmod(mulmod(var28, q_lookup_theta, r), var29, r) - let a_13 := mload(0x8720) + let a_13 := mload(0x96a0) let var31 := mulmod(var2, a_13, r) let var32 := addmod(mulmod(var30, q_lookup_theta, r), var31, r) - let f_13 := mload(0x8bc0) + let f_13 := mload(0x9b40) let var33 := mulmod(var2, f_13, r) let var34 := addmod(mulmod(var32, q_lookup_theta, r), var33, r) let q_lookup_s_sum_h := mulmod(var0, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x9180), sub(r, addmod(mload(0x9160), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0xa100), sub(r, addmod(mload(0xa0e0), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var34, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x9120), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0xa0a0), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_lookup_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_lookup_eval, r)) } } } @@ -985,104 +992,104 @@ contract Halo2QuotientEvaluator { // interpreter stack before dispatching. q_top := 0 q_has_top := 0 - q_sp := 0xa960 + q_sp := 0xb8e0 // Native identity sub-cases are generated from selected heavy gate identities. switch q_native_idx case 0 { { let var0 := 0x1 - let f_0 := mload(0x8ae0) - let a_0_next_1 := mload(0x85c0) + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) let var1 := addmod(0, sub(r, a_0_next_1), r) let var2 := addmod(f_0, var1, r) let var3 := 0x1b8114c381b922fd5d6d241210e2d8a68ad5744053ba9e776118de4107b51ace - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0x3df32e4cc4cb2ed20e5d21899cf5331775990ccaec4c09b4e3717213fcc0d763 - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0xa960, var18) + mstore(0xb8e0, var18) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0xa340, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } } case 1 { { let var0 := 0x1 - let f_1 := mload(0x8b00) - let a_1_next_1 := mload(0x85e0) + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) let var1 := addmod(0, sub(r, a_1_next_1), r) let var2 := addmod(f_1, var1, r) let var3 := 0x404d21073985d14e432a4ad76d3fae06ca74314b950fe7b1d7f501cd31a8b374 - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0xb2cc8704264c6bd81bc620e9e524d4b73e9b2317679422ff7fa1603955649f1 - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0xa960, var18) + mstore(0xb8e0, var18) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0xa340, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0xb2c0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } } case 2 { { let var0 := 0x1 - let a_0 := mload(0x8520) - let a_0_next_1 := mload(0x85c0) + let a_0 := mload(0x94a0) + let a_0_next_1 := mload(0x9540) let var1 := mulmod(a_0, a_0_next_1, r) let var2 := 0x100000000000000 - let a_1_next_1 := mload(0x85e0) + let a_1_next_1 := mload(0x9560) let var3 := mulmod(a_0, a_1_next_1, r) let var4 := mulmod(var2, var3, r) let var5 := addmod(var1, var4, r) let var6 := 0x10000000000000000000000000000 - let a_2_next_1 := mload(0x8600) + let a_2_next_1 := mload(0x9580) let var7 := mulmod(a_0, a_2_next_1, r) let var8 := mulmod(var6, var7, r) let var9 := addmod(var5, var8, r) - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var10 := mulmod(a_1, a_0_next_1, r) let var11 := mulmod(var2, var10, r) let var12 := addmod(var9, var11, r) @@ -1090,15 +1097,15 @@ contract Halo2QuotientEvaluator { let var14 := mulmod(var6, var13, r) let var15 := addmod(var12, var14, r) let var16 := 0x3212e00cde6d2002b119d800000347fcb8 - let a_6_next_1 := mload(0x87a0) + let a_6_next_1 := mload(0x9720) let var17 := mulmod(a_1, a_6_next_1, r) let var18 := mulmod(var16, var17, r) let var19 := addmod(var15, var18, r) - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var20 := mulmod(a_2, a_0_next_1, r) let var21 := mulmod(var6, var20, r) let var22 := addmod(var19, var21, r) - let a_5_next_1 := mload(0x8780) + let a_5_next_1 := mload(0x9700) let var23 := mulmod(a_2, a_5_next_1, r) let var24 := mulmod(var16, var23, r) let var25 := addmod(var22, var24, r) @@ -1106,8 +1113,8 @@ contract Halo2QuotientEvaluator { let var27 := mulmod(a_2, a_6_next_1, r) let var28 := mulmod(var26, var27, r) let var29 := addmod(var25, var28, r) - let a_3 := mload(0x8580) - let a_4_next_1 := mload(0x8760) + let a_3 := mload(0x9500) + let a_4_next_1 := mload(0x96e0) let var30 := mulmod(a_3, a_4_next_1, r) let var31 := mulmod(var16, var30, r) let var32 := addmod(var29, var31, r) @@ -1118,8 +1125,8 @@ contract Halo2QuotientEvaluator { let var37 := mulmod(a_3, a_6_next_1, r) let var38 := mulmod(var36, var37, r) let var39 := addmod(var35, var38, r) - let a_4 := mload(0x85a0) - let a_3_next_1 := mload(0x8740) + let a_4 := mload(0x9520) + let a_3_next_1 := mload(0x96c0) let var40 := mulmod(a_4, a_3_next_1, r) let var41 := mulmod(var16, var40, r) let var42 := addmod(var39, var41, r) @@ -1133,7 +1140,7 @@ contract Halo2QuotientEvaluator { let var50 := mulmod(a_4, a_6_next_1, r) let var51 := mulmod(var49, var50, r) let var52 := addmod(var48, var51, r) - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var53 := mulmod(a_5, a_2_next_1, r) let var54 := mulmod(var16, var53, r) let var55 := addmod(var52, var54, r) @@ -1150,7 +1157,7 @@ contract Halo2QuotientEvaluator { let var66 := mulmod(a_5, a_6_next_1, r) let var67 := mulmod(var65, var66, r) let var68 := addmod(var64, var67, r) - let a_6 := mload(0x8640) + let a_6 := mload(0x95c0) let var69 := mulmod(a_6, a_1_next_1, r) let var70 := mulmod(var16, var69, r) let var71 := addmod(var68, var70, r) @@ -1180,23 +1187,23 @@ contract Halo2QuotientEvaluator { let var95 := mulmod(var6, a_2_next_1, r) let var96 := addmod(var94, var95, r) let var97 := addmod(var92, var96, r) - let a_7 := mload(0x8660) - let a_8 := mload(0x8680) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) let var98 := mulmod(var2, a_8, r) let var99 := addmod(a_7, var98, r) - let a_9 := mload(0x86a0) + let a_9 := mload(0x9620) let var100 := mulmod(var6, a_9, r) let var101 := addmod(var99, var100, r) let var102 := addmod(0, sub(r, var101), r) let var103 := addmod(var97, var102, r) - let a_7_next_1 := mload(0x87c0) + let a_7_next_1 := mload(0x9740) let var104 := 0x241eabfffeb153ffffb9feffffffffaaab let var105 := mulmod(a_7_next_1, var104, r) let var106 := addmod(0, sub(r, var105), r) let var107 := addmod(var103, var106, r) let var108 := addmod(0, sub(r, var16), r) let var109 := addmod(var107, var108, r) - let a_8_next_1 := mload(0x87e0) + let a_8_next_1 := mload(0x9760) let var110 := 0x73eda753299d7d483339d80809a1d80553b9202d7ffe85d4800008bb20000001 let var111 := addmod(a_8_next_1, var110, r) let var112 := 0x4000000000000000000000000000000000 @@ -1204,42 +1211,42 @@ contract Halo2QuotientEvaluator { let var114 := addmod(0, sub(r, var113), r) let var115 := addmod(var109, var114, r) let var116 := mulmod(var0, var115, r) - mstore(0xa960, var116) + mstore(0xb8e0, var116) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x80) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } } case 3 { { let var0 := 0x1 - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var1 := 0x10000000000000000000000000000 let var2 := addmod(a_0, var1, r) let var3 := 0x100000000000000 - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var4 := addmod(a_1, var1, r) let var5 := mulmod(var3, var4, r) let var6 := addmod(var2, var5, r) - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var7 := addmod(a_2, var1, r) let var8 := mulmod(var1, var7, r) let var9 := addmod(var6, var8, r) - let a_7 := mload(0x8660) - let a_8 := mload(0x8680) + let a_7 := mload(0x95e0) + let a_8 := mload(0x9600) let var10 := mulmod(var3, a_8, r) let var11 := addmod(a_7, var10, r) - let a_9 := mload(0x86a0) + let a_9 := mload(0x9620) let var12 := mulmod(var1, a_9, r) let var13 := addmod(var11, var12, r) let var14 := addmod(0, sub(r, var13), r) let var15 := addmod(var9, var14, r) let var16 := addmod(0, sub(r, var1), r) let var17 := addmod(var15, var16, r) - let a_7_next_1 := mload(0x87c0) + let a_7_next_1 := mload(0x9740) let var18 := 0x241eabfffeb153ffffb9feffffffffaaab let var19 := mulmod(a_7_next_1, var18, r) let var20 := addmod(0, sub(r, var19), r) @@ -1247,7 +1254,7 @@ contract Halo2QuotientEvaluator { let var22 := 0xd9d44a30b019261257667fde3844a8cd6 let var23 := addmod(0, sub(r, var22), r) let var24 := addmod(var21, var23, r) - let a_8_next_1 := mload(0x87e0) + let a_8_next_1 := mload(0x9760) let var25 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5b6e855000003ab00002 let var26 := addmod(a_8_next_1, var25, r) let var27 := 0x4000000000000000000000000000000000 @@ -1255,13 +1262,13 @@ contract Halo2QuotientEvaluator { let var29 := addmod(0, sub(r, var28), r) let var30 := addmod(var24, var29, r) let var31 := mulmod(var0, var30, r) - mstore(0xa960, var31) + mstore(0xb8e0, var31) } - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xa0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xa960), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0xb8e0), r)) } } default { revert(0, 0) } @@ -1285,14 +1292,14 @@ contract Halo2QuotientEvaluator { // The global fully-evaluated accumulator is still // multiplied by y so later main identities land at the // same y powers as Rust's reverse fold. - mstore(0xa300, mulmod(mload(0xa300), y, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) let q_sel_acc := mload(q_target_ptr) if q_sel_gap { // Selector buckets are sparse in the global // identity stream. Precomputed y^gap advances only // this selector's local accumulator. - q_sel_acc := mulmod(q_sel_acc, mload(add(0xa340, shl(5, q_sel_gap))), r) + q_sel_acc := mulmod(q_sel_acc, mload(add(0xb2c0, shl(5, q_sel_gap))), r) } mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) } @@ -1307,6 +1314,12 @@ contract Halo2QuotientEvaluator { // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0xb8e0)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -1318,52 +1331,52 @@ contract Halo2QuotientEvaluator { { let q_trash_tau := mload(TRASH_CHALLENGE_MPTR) { - let f_0 := mload(0x8ae0) - let a_0_next_1 := mload(0x85c0) + let f_0 := mload(0x9a60) + let a_0_next_1 := mload(0x9540) let var0 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 let var1 := mulmod(a_0_next_1, var0, r) let var2 := addmod(f_0, var1, r) let var3 := 0x590ba402032e82eb1f660ef09796c5686345a5054ed96dae8e2d233633788771 - let a_0 := mload(0x8520) + let a_0 := mload(0x94a0) let var4 := mulmod(var3, a_0, r) let var5 := addmod(var2, var4, r) let var6 := 0x52f789e4afc3801f7411102ee2f47cc5954a744e71cac98e75ea962a55a0a76f - let a_1 := mload(0x8540) + let a_1 := mload(0x94c0) let var7 := mulmod(var6, a_1, r) let var8 := addmod(var5, var7, r) let var9 := 0x3509dd2fe3aac0080783557fec090fb1cb4b2b0901253c55282024331d1fe1a8 - let a_2 := mload(0x8560) + let a_2 := mload(0x94e0) let var10 := q_pow5(a_2) let var11 := mulmod(var9, var10, r) let var12 := addmod(var8, var11, r) let var13 := 0x333f8046ece5579cbd6872449c57f2703dfc8864cfadc06d587ff104a0d0c1f2 - let a_3 := mload(0x8580) + let a_3 := mload(0x9500) let var14 := q_pow5(a_3) let var15 := mulmod(var13, var14, r) let var16 := addmod(var12, var15, r) let var17 := 0x412c98232b6ab8a47aa76ee814ef7ec6261987c9802f2cfc490e007951a60ca5 - let a_4 := mload(0x85a0) + let a_4 := mload(0x9520) let var18 := q_pow5(a_4) let var19 := mulmod(var17, var18, r) let var20 := addmod(var16, var19, r) let var21 := 0x53fded36d490ba6b05a5d10fd99ffe5456baec6a6a8753199d5ebdc33c99790e - let a_5 := mload(0x8620) + let a_5 := mload(0x95a0) let var22 := q_pow5(a_5) let var23 := mulmod(var21, var22, r) let var24 := addmod(var20, var23, r) let var25 := 0x6ccb1c7d87f3c12a2bde4e68ac7f1e8b03481ba15d7f88f9a7f9b8310dd6d34 - let a_6 := mload(0x8640) + let a_6 := mload(0x95c0) let var26 := q_pow5(a_6) let var27 := mulmod(var25, var26, r) let var28 := addmod(var24, var27, r) let var29 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_7 := mload(0x8660) + let a_7 := mload(0x95e0) let var30 := q_pow5(a_7) let var31 := mulmod(var29, var30, r) let var32 := addmod(var28, var31, r) let var33 := addmod(mulmod(0, q_trash_tau, r), var32, r) - let f_1 := mload(0x8b00) - let a_1_next_1 := mload(0x85e0) + let f_1 := mload(0x9a80) + let a_1_next_1 := mload(0x9560) let var34 := mulmod(a_1_next_1, var0, r) let var35 := addmod(f_1, var34, r) let var36 := 0x5b1fc262a28cbb8bf75d9b1a6edaa74591ec24cd9a209512213cec3a3c0f1a5d @@ -1391,7 +1404,7 @@ contract Halo2QuotientEvaluator { let var58 := mulmod(var57, var30, r) let var59 := addmod(var56, var58, r) let var60 := addmod(mulmod(var33, q_trash_tau, r), var59, r) - let f_2 := mload(0x8b20) + let f_2 := mload(0x9aa0) let var61 := mulmod(a_3, var0, r) let var62 := addmod(f_2, var61, r) let var63 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 @@ -1404,7 +1417,7 @@ contract Halo2QuotientEvaluator { let var70 := mulmod(var69, var10, r) let var71 := addmod(var68, var70, r) let var72 := addmod(mulmod(var60, q_trash_tau, r), var71, r) - let f_3 := mload(0x8b40) + let f_3 := mload(0x9ac0) let var73 := mulmod(a_4, var0, r) let var74 := addmod(f_3, var73, r) let var75 := 0x222e83e70453dfee19b402e9fa8dfe2c4987b034d0be3ceb478b3022e97934c1 @@ -1419,7 +1432,7 @@ contract Halo2QuotientEvaluator { let var84 := mulmod(var69, var14, r) let var85 := addmod(var83, var84, r) let var86 := addmod(mulmod(var72, q_trash_tau, r), var85, r) - let f_4 := mload(0x8a40) + let f_4 := mload(0x99c0) let var87 := mulmod(a_5, var0, r) let var88 := addmod(f_4, var87, r) let var89 := 0x726df1506749848155630b86ae25a82b281ecd050fe3a52d85a181fa87202e4b @@ -1436,7 +1449,7 @@ contract Halo2QuotientEvaluator { let var100 := mulmod(var69, var18, r) let var101 := addmod(var99, var100, r) let var102 := addmod(mulmod(var86, q_trash_tau, r), var101, r) - let f_5 := mload(0x8a60) + let f_5 := mload(0x99e0) let var103 := mulmod(a_6, var0, r) let var104 := addmod(f_5, var103, r) let var105 := 0x2f5908b169c6cf1bd26dcf0f9e5105481f5164f3ece0582bf3098312167751a7 @@ -1455,7 +1468,7 @@ contract Halo2QuotientEvaluator { let var118 := mulmod(var69, var22, r) let var119 := addmod(var117, var118, r) let var120 := addmod(mulmod(var102, q_trash_tau, r), var119, r) - let f_6 := mload(0x8a80) + let f_6 := mload(0x9a00) let var121 := mulmod(a_7, var0, r) let var122 := addmod(f_6, var121, r) let var123 := 0x6d05a41959f539a7fc9ec0972ea1e3dbb6fc67dd51daf3414f7fbbb091c7274a @@ -1476,8 +1489,8 @@ contract Halo2QuotientEvaluator { let var138 := mulmod(var69, var26, r) let var139 := addmod(var137, var138, r) let var140 := addmod(mulmod(var120, q_trash_tau, r), var139, r) - let f_7 := mload(0x8aa0) - let a_2_next_1 := mload(0x8600) + let f_7 := mload(0x9a20) + let a_2_next_1 := mload(0x9580) let var141 := mulmod(a_2_next_1, var0, r) let var142 := addmod(f_7, var141, r) let var143 := 0x70d8f2a733a64d650faccc9b1c2a766a9544bb3ff1a11ee73cb43947ef386633 @@ -1500,12 +1513,12 @@ contract Halo2QuotientEvaluator { let var160 := mulmod(var69, var30, r) let var161 := addmod(var159, var160, r) let var162 := addmod(mulmod(var140, q_trash_tau, r), var161, r) - let f_19 := mload(0x8c00) + let f_19 := mload(0x9b80) let q_trash_one_minus_selector := addmod(1, sub(r, f_19), r) - let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0x91a0), r) + let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0xa120), r) let q_trash_eval := addmod(var162, sub(r, q_trash_scaled), r) - mstore(0xa300, mulmod(mload(0xa300), y, r)) - mstore(0xa300, addmod(mload(0xa300), q_trash_eval, r)) + mstore(0xb280, mulmod(mload(0xb280), y, r)) + mstore(0xb280, addmod(mload(0xb280), q_trash_eval, r)) } } // Finish selector buckets by applying the codegen-known tail @@ -1517,49 +1530,49 @@ contract Halo2QuotientEvaluator { // selector commitment in the linearized MSM. { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0600)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0600)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x05e0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x05e0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0580)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0580)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x04c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x04c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x80) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0460)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0460)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xa0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0400)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0400)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xc0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x03a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x03a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xe0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0340)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0340)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0100) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x02e0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x02e0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0120) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xa340, 0x0280)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0xb2c0, 0x0280)), r)) } // Fully evaluated identities are the constant-polynomial side // of the linearization query. Rust subtracts that grouped // scalar into expected_eval, so Solidity stores -nu_y(x). - let linearization_expected_eval := addmod(0, sub(r, mload(0xa300)), r) + let linearization_expected_eval := addmod(0, sub(r, mload(0xb280)), r) mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) pop(y) } diff --git a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2Verifier.sol index 8dd0fcf29..6f66ad7c7 100644 --- a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2Verifier.sol @@ -43,15 +43,15 @@ contract Halo2Verifier { // EXPECTED_VK_PAYLOAD_LENGTH. uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 17024; uint256 internal constant EXPECTED_VK_LENGTH = 17025; - uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x0a489a96da94a3dde90d99d69bd4464d5d5731a89f202154559e68a17fac1c21; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x67bac137fa7e479c25b63324812752e4b6e13d9841d5bf83c322170bf91c0f88; bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); /// @notice Quotient evaluator contract authorized for split quotient reconstruction. /// @dev The evaluator returns the linearization expected scalar and selector buckets; its runtime may be pinned by generated constants. address public immutable AUTHORIZED_QUOTIENT; // Expected split evaluator runtime metadata. It is checked at deployment // and again immediately before each external quotient reconstruction. - uint256 internal constant EXPECTED_QUOTIENT_LENGTH = 9531; - uint256 internal constant EXPECTED_QUOTIENT_CODEHASH_WORD = 0x02f1ea00260a78e72dc51376f5e3053ae6c83deae7219acb89c6a0bca5dc8f1c; + uint256 internal constant EXPECTED_QUOTIENT_LENGTH = 9552; + uint256 internal constant EXPECTED_QUOTIENT_CODEHASH_WORD = 0x6a7ec82a9b0683783c459f4b43e924f8ceba256731bf2b9782ab5747e801ea2c; bytes32 internal constant EXPECTED_QUOTIENT_CODEHASH = bytes32(EXPECTED_QUOTIENT_CODEHASH_WORD); // Solidity ABI calldata cursors. The generated verifier accepts exactly @@ -63,8 +63,8 @@ contract Halo2Verifier { uint256 internal constant INSTANCE_CPTR = 0x1ee4; // First general-purpose memory words reserved by the generated verifier. // RETURN_MPTR is a single word set to 1 on success. - uint256 internal constant TRANSCRIPT_MPTR = 0x80; - uint256 internal constant RETURN_MPTR = 0x80; + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; // ---------------------------------------------------------------------- // Verifying-key memory map. The VK header lives at VK_MPTR, followed @@ -72,84 +72,84 @@ contract Halo2Verifier { // runtime comes the challenge slots (challenge_mptr..) and the // per-stage scratch (theta_mptr..). // ---------------------------------------------------------------------- - uint256 internal constant VK_MPTR = 0x2700; - uint256 internal constant VK_DIGEST_MPTR = 0x2700; - uint256 internal constant NUM_INSTANCES_MPTR = 0x2720; - uint256 internal constant K_MPTR = 0x2740; - uint256 internal constant N_INV_MPTR = 0x2760; - uint256 internal constant OMEGA_MPTR = 0x2780; - uint256 internal constant OMEGA_INV_MPTR = 0x27a0; - uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x27c0; - uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x27e0; - uint256 internal constant ACC_OFFSET_MPTR = 0x2800; - uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x2820; - uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x2840; - uint256 internal constant G1_BASE_MPTR = 0x2860; - uint256 internal constant G2_BASE_MPTR = 0x28e0; - uint256 internal constant NEG_S_G2_BASE_MPTR = 0x29e0; - - uint256 internal constant CHALLENGE_MPTR = 0x6980; + uint256 internal constant VK_MPTR = 0x3680; + uint256 internal constant VK_DIGEST_MPTR = 0x3680; + uint256 internal constant NUM_INSTANCES_MPTR = 0x36a0; + uint256 internal constant K_MPTR = 0x36c0; + uint256 internal constant N_INV_MPTR = 0x36e0; + uint256 internal constant OMEGA_MPTR = 0x3700; + uint256 internal constant OMEGA_INV_MPTR = 0x3720; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x3740; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x3760; + uint256 internal constant ACC_OFFSET_MPTR = 0x3780; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x37a0; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x37c0; + uint256 internal constant G1_BASE_MPTR = 0x37e0; + uint256 internal constant G2_BASE_MPTR = 0x3860; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x3960; + + uint256 internal constant CHALLENGE_MPTR = 0x7900; // Challenge layout. Squeeze order in midnight-proofs: // user_phase challenges (variable count) // theta -> beta, gamma -> trash_challenge -> y -> x -> // x1, x2 -> x3 -> x4 - uint256 internal constant THETA_MPTR = 0x6980; - uint256 internal constant BETA_MPTR = 0x69a0; - uint256 internal constant GAMMA_MPTR = 0x69c0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x69e0; - uint256 internal constant Y_MPTR = 0x6a00; - uint256 internal constant X_MPTR = 0x6a20; - uint256 internal constant X1_MPTR = 0x6a40; - uint256 internal constant X2_MPTR = 0x6a60; - uint256 internal constant X3_MPTR = 0x6a80; - uint256 internal constant X4_MPTR = 0x6aa0; + uint256 internal constant THETA_MPTR = 0x7900; + uint256 internal constant BETA_MPTR = 0x7920; + uint256 internal constant GAMMA_MPTR = 0x7940; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x7960; + uint256 internal constant Y_MPTR = 0x7980; + uint256 internal constant X_MPTR = 0x79a0; + uint256 internal constant X1_MPTR = 0x79c0; + uint256 internal constant X2_MPTR = 0x79e0; + uint256 internal constant X3_MPTR = 0x7a00; + uint256 internal constant X4_MPTR = 0x7a20; // Batch-open commitments live in 4-word EIP-2537 padded slots. - uint256 internal constant F_COM_MPTR = 0x6ac0; - uint256 internal constant PI_MPTR = 0x6b40; + uint256 internal constant F_COM_MPTR = 0x7a40; + uint256 internal constant PI_MPTR = 0x7ac0; // Accumulator (KZG IVC). - uint256 internal constant ACC_LHS_MPTR = 0x6bc0; - uint256 internal constant ACC_RHS_MPTR = 0x6c40; + uint256 internal constant ACC_LHS_MPTR = 0x7b40; + uint256 internal constant ACC_RHS_MPTR = 0x7bc0; // Lagrange / linearization scratch. - uint256 internal constant X_N_MPTR = 0x6cc0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x6ce0; - uint256 internal constant L_LAST_MPTR = 0x6d00; - uint256 internal constant L_BLIND_MPTR = 0x6d20; - uint256 internal constant L_0_MPTR = 0x6d40; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x6d60; + uint256 internal constant X_N_MPTR = 0x7c40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x7c60; + uint256 internal constant L_LAST_MPTR = 0x7c80; + uint256 internal constant L_BLIND_MPTR = 0x7ca0; + uint256 internal constant L_0_MPTR = 0x7cc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x7ce0; // Legacy name: this is not h(x). It stores the expected opening // scalar for the linearized commitment, i.e. the negated y-batched // identity numerator reconstructed from the alleged evals at x. - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x6d80; - uint256 internal constant QUOTIENT_MPTR = 0x6da0; // 4 words - uint256 internal constant F_EVAL_MPTR = 0x6e40; - uint256 internal constant V_MPTR = 0x6e60; - uint256 internal constant FINAL_COM_MPTR = 0x6e80; // 4 words - uint256 internal constant PAIRING_LHS_MPTR = 0x6f00; // 4 words - uint256 internal constant PAIRING_RHS_MPTR = 0x6f80; // 4 words + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x7d00; + uint256 internal constant QUOTIENT_MPTR = 0x7d20; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x7dc0; + uint256 internal constant V_MPTR = 0x7de0; + uint256 internal constant FINAL_COM_MPTR = 0x7e00; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x7e80; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x7f00; // 4 words // Multi-prepare scratch (sized at codegen time). - uint256 internal constant ROT_POINTS_MPTR = 0x7000; - uint256 internal constant X1_POWERS_MPTR = 0x7380; + uint256 internal constant ROT_POINTS_MPTR = 0x7f80; + uint256 internal constant X1_POWERS_MPTR = 0x8300; // Q_COM materialization is currently fused into the final MSM scratch, // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero // reserved capacity until a future emitter starts writing Q_COM_MPTR. - uint256 internal constant Q_COM_MPTR = 0x7ba0; - uint256 internal constant Q_EVAL_SET_MPTR = 0x7ba0; + uint256 internal constant Q_COM_MPTR = 0x8b20; + uint256 internal constant Q_EVAL_SET_MPTR = 0x8b20; // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals // block of the proof; we keep it as a memory slot for symmetry. - uint256 internal constant Q_EVAL_CPTR_MPTR = 0x82a0; + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x9220; // Reserved 4-word slot for the G1 identity (point at infinity) in // EIP-2537 padded form. EVM memory is zero-initialised, and we // never write to this region, so the four `mload`s below produce // 0,0,0,0 which is exactly the identity encoding the EIP-2537 // ec_add / ec_mul precompiles accept. - uint256 internal constant G1_IDENTITY_MPTR = 0x83a0; + uint256 internal constant G1_IDENTITY_MPTR = 0x9320; // Decoded polynomial-eval buffer (Optimisation H3). The off-chain // Solidity proof shim rewrites proof scalars into canonical BE words, @@ -157,11 +157,11 @@ contract Halo2Verifier { // side `evaluations` loop range-checks and spills that value here so // downstream eval references (gate evaluator + PCS q_eval Horner) // become 3-gas `mload(...)` instead of calldata reads. - uint256 internal constant REVERSED_EVALS_MPTR = 0x8500; - uint256 internal constant SELECTOR_ACC_MPTR = 0xa1c0; - uint256 internal constant QUOTIENT_RETURN_MPTR = 0x80; - uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0xa1c0; - uint256 internal constant TRACE_U256_MPTR = 0xd3c0; + uint256 internal constant REVERSED_EVALS_MPTR = 0x9480; + uint256 internal constant SELECTOR_ACC_MPTR = 0xb140; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0xb140; + uint256 internal constant TRACE_U256_MPTR = 0xe340; // ---------------------------------------------------------------------- // Per-category bases for EIP-2537 padded G1 commitments. The proof @@ -178,13 +178,13 @@ contract Halo2Verifier { // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans // ---------------------------------------------------------------------- - uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x91c0; - uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x9940; - uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x9a40; - uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x9d40; - uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x9e40; - uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x9f40; - uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x9fc0; + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0xa140; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0xa8c0; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0xa9c0; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0xacc0; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0xadc0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0xaec0; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0xaf40; // BLS12-381 scalar-field modulus, used for transcript challenges and all // Halo2 verifier arithmetic. @@ -206,7 +206,7 @@ contract Halo2Verifier { function require_eip2537_precompiles() private view { assembly ("memory-safe") { // Scratch is reused for every runtime-prerequisite probe. - let scratch := 0x80 + let scratch := 0x1000 // MCOPY must be available because the verifier uses it for // proof-time point/scratch staging. Execute the opcode here so a @@ -230,11 +230,44 @@ contract Halo2Verifier { revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair // smoke call. - let msm_scratch := 0xa1c0 + let msm_scratch := 0xb140 for { let off := 0 } lt(off, 0x30c0) { off := add(off, 0x20) } { mstore(add(msm_scratch, off), 0) } @@ -344,9 +377,15 @@ contract Halo2Verifier { // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } - let p := 0x2600 + let p := 0x3580 // EIP-198 modexp frame: // [base_len, exp_len, mod_len, base, exponent, modulus] mstore(add(p, 0x00), 0x20) // base len @@ -481,6 +520,13 @@ contract Halo2Verifier { // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -527,6 +573,12 @@ contract Halo2Verifier { mstore(add(gp_mptr, 0xa0), r) ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -551,21 +603,30 @@ contract Halo2Verifier { // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0300 + let scratch := 0x0320 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } @@ -597,7 +658,13 @@ contract Halo2Verifier { // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -823,11 +890,16 @@ contract Halo2Verifier { out := and(out, lhs_ok) // Shared scratch for one-pair LHS validation and the later // variable-length RHS MSM. - let acc_scratch := 0xa1c0 + let acc_scratch := 0xb140 { // Carried-scalar layout: the circuit exposes the scalar // that multiplies the carried LHS point. let lhs_scalar := calldataload(lhs_scalar_ptr) + // Canonicality is enforced here rather than relying on the + // later instance-absorption loop: G1MSM reduces scalars + // mod r implicitly, so s and s+r would be indistinguishable + // inside this helper. + out := and(out, lt(lhs_scalar, r)) // Identity status is useful for decoding checks above, but // validation still goes through G1MSM for all points. pop(lhs_is_id) @@ -862,6 +934,7 @@ contract Halo2Verifier { { // Explicit carried RHS scalar. let rhs_scalar := calldataload(rhs_scalar_ptr) + out := and(out, lt(rhs_scalar, r)) pop(rhs_is_id) // Keep the carried RHS point in the MSM input even when // it is encoded as identity or has scalar 0/1, so EIP-2537 @@ -1275,6 +1348,16 @@ contract Halo2Verifier { // ---- x3 ---- // x3 is the PCS evaluation point for f_com. buf_len := squeeze_to(buf_len, X3_MPTR) + // truncated-challenges mirrors midnight-proofs + // proofs/src/poly/kzg/mod.rs: + // - x3 is the f_com evaluation point and is truncated + // immediately after squeeze. + // - x1 and x4 remain full squeezed Fr words, but later PCS + // batching stores truncate(x1^i) and truncate(x4^i) while + // keeping the internal power accumulators full precision. + // This direct x3 mask is therefore one part of the PCS truncation + // rule, not the only truncated value used by the verifier. + mstore(X3_MPTR, and(mload(X3_MPTR), 0xffffffffffffffffffffffffffffffff)) // ---- q_evals (one Fq per point set) ---- // q_evals are not spilled into REVERSED_EVALS_MPTR because the PCS @@ -1424,7 +1507,7 @@ contract Halo2Verifier { eq(extcodesize(quotientEvaluator), EXPECTED_QUOTIENT_LENGTH), eq(extcodehash(quotientEvaluator), EXPECTED_QUOTIENT_CODEHASH_WORD) )) { revert(0, 0) } - if iszero(staticcall(gas(), quotientEvaluator, 0x2700, 0x6ac0, q_out, 0x0180)) { revert(0, 0) } + if iszero(staticcall(gas(), quotientEvaluator, 0x3680, 0x6ac0, q_out, 0x0180)) { revert(0, 0) } if iszero(eq(returndatasize(), 0x0180)) { revert(0, 0) } if iszero(eq(mload(q_out), 0x00000000000000000000000000000000000000000000000051554556414c0001)) { revert(0, 0) } // Word 1 is the negated y-batched identity numerator, stored @@ -1529,7 +1612,7 @@ contract Halo2Verifier { for { let i := 0 } lt(i, 0x2a) { i := add(i, 1) } { p := add(p, 0x20) acc := mulmod(acc, x1, r) - mstore(p, acc) + mstore(p, and(acc, 0xffffffffffffffffffffffffffffffff)) } } // Generated PCS sub-block 3. These lines are @@ -1538,52 +1621,52 @@ contract Halo2Verifier { { // q_eval_set[0]: 43 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0xa300, 0x8a00) - mstore(0xa320, 0x8500) - mstore(0xa340, 0x90a0) - mstore(0xa360, 0x90c0) - mstore(0xa380, 0x9120) - mstore(0xa3a0, 0x9140) - mstore(0xa3c0, 0x91a0) - mstore(0xa3e0, 0x8a20) - mstore(0xa400, 0x8a40) - mstore(0xa420, 0x8a60) - mstore(0xa440, 0x8a80) - mstore(0xa460, 0x8aa0) - mstore(0xa480, 0x8ac0) - mstore(0xa4a0, 0x8ae0) - mstore(0xa4c0, 0x8b00) - mstore(0xa4e0, 0x8b20) - mstore(0xa500, 0x8b40) - mstore(0xa520, 0x8b60) - mstore(0xa540, 0x8b80) - mstore(0xa560, 0x8ba0) - mstore(0xa580, 0x8bc0) - mstore(0xa5a0, 0x8be0) - mstore(0xa5c0, 0x8c00) - mstore(0xa5e0, 0x8c20) - mstore(0xa600, 0x8c40) - mstore(0xa620, 0x8c60) - mstore(0xa640, 0x8c80) - mstore(0xa660, 0x8ca0) - mstore(0xa680, 0x8cc0) - mstore(0xa6a0, 0x8ce0) - mstore(0xa6c0, 0x8d00) - mstore(0xa6e0, 0x8d20) - mstore(0xa700, 0x8d40) - mstore(0xa720, 0x8d60) - mstore(0xa740, 0x8d80) - mstore(0xa760, 0x8da0) - mstore(0xa780, 0x8dc0) - mstore(0xa7a0, 0x8de0) - mstore(0xa7c0, 0x8e00) - mstore(0xa7e0, 0x8e20) - mstore(0xa800, 0x8e40) - mstore(0xa820, 0x8e60) - mstore(0xa840, QUOTIENT_EVAL_MPTR) - let q_eval_set_0 := mload(0x8a00) + mstore(0xb280, 0x9980) + mstore(0xb2a0, 0x9480) + mstore(0xb2c0, 0xa020) + mstore(0xb2e0, 0xa040) + mstore(0xb300, 0xa0a0) + mstore(0xb320, 0xa0c0) + mstore(0xb340, 0xa120) + mstore(0xb360, 0x99a0) + mstore(0xb380, 0x99c0) + mstore(0xb3a0, 0x99e0) + mstore(0xb3c0, 0x9a00) + mstore(0xb3e0, 0x9a20) + mstore(0xb400, 0x9a40) + mstore(0xb420, 0x9a60) + mstore(0xb440, 0x9a80) + mstore(0xb460, 0x9aa0) + mstore(0xb480, 0x9ac0) + mstore(0xb4a0, 0x9ae0) + mstore(0xb4c0, 0x9b00) + mstore(0xb4e0, 0x9b20) + mstore(0xb500, 0x9b40) + mstore(0xb520, 0x9b60) + mstore(0xb540, 0x9b80) + mstore(0xb560, 0x9ba0) + mstore(0xb580, 0x9bc0) + mstore(0xb5a0, 0x9be0) + mstore(0xb5c0, 0x9c00) + mstore(0xb5e0, 0x9c20) + mstore(0xb600, 0x9c40) + mstore(0xb620, 0x9c60) + mstore(0xb640, 0x9c80) + mstore(0xb660, 0x9ca0) + mstore(0xb680, 0x9cc0) + mstore(0xb6a0, 0x9ce0) + mstore(0xb6c0, 0x9d00) + mstore(0xb6e0, 0x9d20) + mstore(0xb700, 0x9d40) + mstore(0xb720, 0x9d60) + mstore(0xb740, 0x9d80) + mstore(0xb760, 0x9da0) + mstore(0xb780, 0x9dc0) + mstore(0xb7a0, 0x9de0) + mstore(0xb7c0, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x9980) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0xa300, 0x20) + let eval_p := add(0xb280, 0x20) for { let i := 1 } lt(i, 0x2b) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -1597,12 +1680,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[1]: 3 commitment(s) - let q_eval_set_0 := mload(0x86e0) - let q_eval_set_1 := mload(0x89a0) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x8700), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x89c0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x8720), mload(add(X1_POWERS_MPTR, 0x40)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x89e0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + let q_eval_set_0 := mload(0x9660) + let q_eval_set_1 := mload(0x9920) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x9680), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9940), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x96a0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9960), mload(add(X1_POWERS_MPTR, 0x40)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x20), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x40), q_eval_set_1) } @@ -1611,12 +1694,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[2]: 3 commitment(s) - let q_eval_set_0 := mload(0x9060) - let q_eval_set_1 := mload(0x9080) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x90e0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9100), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x9160), mload(add(X1_POWERS_MPTR, 0x40)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x9180), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + let q_eval_set_0 := mload(0x9fe0) + let q_eval_set_1 := mload(0xa000) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa060), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa080), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0xa0e0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0xa100), mload(add(X1_POWERS_MPTR, 0x40)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x60), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x80), q_eval_set_1) } @@ -1626,44 +1709,44 @@ contract Halo2Verifier { { // q_eval_set[3]: 11 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0xa300, 0x8520) - mstore(0xa320, 0x85c0) - mstore(0xa340, 0x8840) - mstore(0xa360, 0x8540) - mstore(0xa380, 0x85e0) - mstore(0xa3a0, 0x8860) - mstore(0xa3c0, 0x8560) - mstore(0xa3e0, 0x8600) - mstore(0xa400, 0x8880) - mstore(0xa420, 0x8580) - mstore(0xa440, 0x8740) - mstore(0xa460, 0x88a0) - mstore(0xa480, 0x85a0) - mstore(0xa4a0, 0x8760) - mstore(0xa4c0, 0x88c0) - mstore(0xa4e0, 0x8620) - mstore(0xa500, 0x8780) - mstore(0xa520, 0x88e0) - mstore(0xa540, 0x8640) - mstore(0xa560, 0x87a0) - mstore(0xa580, 0x8900) - mstore(0xa5a0, 0x8660) - mstore(0xa5c0, 0x87c0) - mstore(0xa5e0, 0x8920) - mstore(0xa600, 0x8680) - mstore(0xa620, 0x87e0) - mstore(0xa640, 0x8940) - mstore(0xa660, 0x86a0) - mstore(0xa680, 0x8800) - mstore(0xa6a0, 0x8960) - mstore(0xa6c0, 0x86c0) - mstore(0xa6e0, 0x8820) - mstore(0xa700, 0x8980) - let q_eval_set_0 := mload(0x8520) - let q_eval_set_1 := mload(0x85c0) - let q_eval_set_2 := mload(0x8840) + mstore(0xb280, 0x94a0) + mstore(0xb2a0, 0x9540) + mstore(0xb2c0, 0x97c0) + mstore(0xb2e0, 0x94c0) + mstore(0xb300, 0x9560) + mstore(0xb320, 0x97e0) + mstore(0xb340, 0x94e0) + mstore(0xb360, 0x9580) + mstore(0xb380, 0x9800) + mstore(0xb3a0, 0x9500) + mstore(0xb3c0, 0x96c0) + mstore(0xb3e0, 0x9820) + mstore(0xb400, 0x9520) + mstore(0xb420, 0x96e0) + mstore(0xb440, 0x9840) + mstore(0xb460, 0x95a0) + mstore(0xb480, 0x9700) + mstore(0xb4a0, 0x9860) + mstore(0xb4c0, 0x95c0) + mstore(0xb4e0, 0x9720) + mstore(0xb500, 0x9880) + mstore(0xb520, 0x95e0) + mstore(0xb540, 0x9740) + mstore(0xb560, 0x98a0) + mstore(0xb580, 0x9600) + mstore(0xb5a0, 0x9760) + mstore(0xb5c0, 0x98c0) + mstore(0xb5e0, 0x9620) + mstore(0xb600, 0x9780) + mstore(0xb620, 0x98e0) + mstore(0xb640, 0x9640) + mstore(0xb660, 0x97a0) + mstore(0xb680, 0x9900) + let q_eval_set_0 := mload(0x94a0) + let q_eval_set_1 := mload(0x9540) + let q_eval_set_2 := mload(0x97c0) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0xa300, 0x60) + let eval_p := add(0xb280, 0x60) for { let i := 1 } lt(i, 0xb) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -1682,26 +1765,26 @@ contract Halo2Verifier { { // q_eval_set[4]: 5 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0xa300, 0x8e80) - mstore(0xa320, 0x8ea0) - mstore(0xa340, 0x8ec0) - mstore(0xa360, 0x8ee0) - mstore(0xa380, 0x8f00) - mstore(0xa3a0, 0x8f20) - mstore(0xa3c0, 0x8f40) - mstore(0xa3e0, 0x8f60) - mstore(0xa400, 0x8f80) - mstore(0xa420, 0x8fa0) - mstore(0xa440, 0x8fc0) - mstore(0xa460, 0x8fe0) - mstore(0xa480, 0x9000) - mstore(0xa4a0, 0x9020) - mstore(0xa4c0, 0x9040) - let q_eval_set_0 := mload(0x8e80) - let q_eval_set_1 := mload(0x8ea0) - let q_eval_set_2 := mload(0x8ec0) + mstore(0xb280, 0x9e00) + mstore(0xb2a0, 0x9e20) + mstore(0xb2c0, 0x9e40) + mstore(0xb2e0, 0x9e60) + mstore(0xb300, 0x9e80) + mstore(0xb320, 0x9ea0) + mstore(0xb340, 0x9ec0) + mstore(0xb360, 0x9ee0) + mstore(0xb380, 0x9f00) + mstore(0xb3a0, 0x9f20) + mstore(0xb3c0, 0x9f40) + mstore(0xb3e0, 0x9f60) + mstore(0xb400, 0x9f80) + mstore(0xb420, 0x9fa0) + mstore(0xb440, 0x9fc0) + let q_eval_set_0 := mload(0x9e00) + let q_eval_set_1 := mload(0x9e20) + let q_eval_set_2 := mload(0x9e40) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0xa300, 0x60) + let eval_p := add(0xb280, 0x60) for { let i := 1 } lt(i, 0x5) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -1892,181 +1975,186 @@ contract Halo2Verifier { let lin_x_split := mload(QUOTIENT_MPTR) let lin_one_minus_x_n := mload(add(QUOTIENT_MPTR, 0x20)) let Q_EVAL_CPTR := mload(Q_EVAL_CPTR_MPTR) - let x4_pow_0 := 1 - let x4_pow_1 := mulmod(x4_pow_0, x4, r) - let x4_pow_2 := mulmod(x4_pow_1, x4, r) - let x4_pow_3 := mulmod(x4_pow_2, x4, r) - let x4_pow_4 := mulmod(x4_pow_3, x4, r) - let x4_pow_5 := mulmod(x4_pow_4, x4, r) + let x4_pow_full := 1 + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_1 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_2 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_3 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_4 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) + x4_pow_full := mulmod(x4_pow_full, x4, r) + let x4_pow_5 := and(x4_pow_full, 0xffffffffffffffffffffffffffffffff) let v := calldataload(Q_EVAL_CPTR) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), x4_pow_1, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), x4_pow_3, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x80)), x4_pow_4, r), r) v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_5, r), r) - mcopy(0xa300, 0x98c0, 0x80) - mstore(0xa380, 1) - mcopy(0xa3a0, 0x9940, 0x80) - mstore(0xa420, mload(add(X1_POWERS_MPTR, 0x40))) - mcopy(0xa440, 0x9d40, 0x80) - mstore(0xa4c0, mload(add(X1_POWERS_MPTR, 0x60))) - mcopy(0xa4e0, 0x99c0, 0x80) - mstore(0xa560, mload(add(X1_POWERS_MPTR, 0x80))) - mcopy(0xa580, 0x9dc0, 0x80) - mstore(0xa600, mload(add(X1_POWERS_MPTR, 0xa0))) - mcopy(0xa620, 0x9f40, 0x80) - mstore(0xa6a0, mload(add(X1_POWERS_MPTR, 0xc0))) - mcopy(0xa6c0, 0x5780, 0x80) - mstore(0xa740, mload(add(X1_POWERS_MPTR, 0xe0))) - mcopy(0xa760, 0x5500, 0x80) - mstore(0xa7e0, mload(add(X1_POWERS_MPTR, 0x100))) - mcopy(0xa800, 0x5580, 0x80) - mstore(0xa880, mload(add(X1_POWERS_MPTR, 0x120))) - mcopy(0xa8a0, 0x5600, 0x80) - mstore(0xa920, mload(add(X1_POWERS_MPTR, 0x140))) - mcopy(0xa940, 0x5680, 0x80) - mstore(0xa9c0, mload(add(X1_POWERS_MPTR, 0x160))) - mcopy(0xa9e0, 0x5700, 0x80) - mstore(0xaa60, mload(add(X1_POWERS_MPTR, 0x180))) - mcopy(0xaa80, 0x5300, 0x80) - mstore(0xab00, mload(add(X1_POWERS_MPTR, 0x1a0))) - mcopy(0xab20, 0x5380, 0x80) - mstore(0xaba0, mload(add(X1_POWERS_MPTR, 0x1c0))) - mcopy(0xabc0, 0x5400, 0x80) - mstore(0xac40, mload(add(X1_POWERS_MPTR, 0x1e0))) - mcopy(0xac60, 0x5480, 0x80) - mstore(0xace0, mload(add(X1_POWERS_MPTR, 0x200))) - mcopy(0xad00, 0x5800, 0x80) - mstore(0xad80, mload(add(X1_POWERS_MPTR, 0x220))) - mcopy(0xada0, 0x5880, 0x80) - mstore(0xae20, mload(add(X1_POWERS_MPTR, 0x240))) - mcopy(0xae40, 0x5900, 0x80) - mstore(0xaec0, mload(add(X1_POWERS_MPTR, 0x260))) - mcopy(0xaee0, 0x5980, 0x80) - mstore(0xaf60, mload(add(X1_POWERS_MPTR, 0x280))) - mcopy(0xaf80, 0x5b80, 0x80) - mstore(0xb000, mload(add(X1_POWERS_MPTR, 0x2a0))) - mcopy(0xb020, 0x5c80, 0x80) - mstore(0xb0a0, mload(add(X1_POWERS_MPTR, 0x2c0))) - mcopy(0xb0c0, 0x6000, 0x80) - mstore(0xb140, mload(add(X1_POWERS_MPTR, 0x2e0))) - mcopy(0xb160, 0x6080, 0x80) - mstore(0xb1e0, mload(add(X1_POWERS_MPTR, 0x300))) - mcopy(0xb200, 0x6100, 0x80) - mstore(0xb280, mload(add(X1_POWERS_MPTR, 0x320))) - mcopy(0xb2a0, 0x6180, 0x80) - mstore(0xb320, mload(add(X1_POWERS_MPTR, 0x340))) - mcopy(0xb340, 0x6200, 0x80) - mstore(0xb3c0, mload(add(X1_POWERS_MPTR, 0x360))) - mcopy(0xb3e0, 0x6280, 0x80) - mstore(0xb460, mload(add(X1_POWERS_MPTR, 0x380))) - mcopy(0xb480, 0x6300, 0x80) - mstore(0xb500, mload(add(X1_POWERS_MPTR, 0x3a0))) - mcopy(0xb520, 0x6380, 0x80) - mstore(0xb5a0, mload(add(X1_POWERS_MPTR, 0x3c0))) - mcopy(0xb5c0, 0x6400, 0x80) - mstore(0xb640, mload(add(X1_POWERS_MPTR, 0x3e0))) - mcopy(0xb660, 0x6480, 0x80) - mstore(0xb6e0, mload(add(X1_POWERS_MPTR, 0x400))) - mcopy(0xb700, 0x6500, 0x80) - mstore(0xb780, mload(add(X1_POWERS_MPTR, 0x420))) - mcopy(0xb7a0, 0x6580, 0x80) - mstore(0xb820, mload(add(X1_POWERS_MPTR, 0x440))) - mcopy(0xb840, 0x6600, 0x80) - mstore(0xb8c0, mload(add(X1_POWERS_MPTR, 0x460))) - mcopy(0xb8e0, 0x6680, 0x80) - mstore(0xb960, mload(add(X1_POWERS_MPTR, 0x480))) - mcopy(0xb980, 0x6700, 0x80) - mstore(0xba00, mload(add(X1_POWERS_MPTR, 0x4a0))) - mcopy(0xba20, 0x6780, 0x80) - mstore(0xbaa0, mload(add(X1_POWERS_MPTR, 0x4c0))) - mcopy(0xbac0, 0x6800, 0x80) - mstore(0xbb40, mload(add(X1_POWERS_MPTR, 0x4e0))) - mcopy(0xbb60, 0x6880, 0x80) - mstore(0xbbe0, mload(add(X1_POWERS_MPTR, 0x500))) - mcopy(0xbc00, 0x6900, 0x80) - mstore(0xbc80, mload(add(X1_POWERS_MPTR, 0x520))) + mcopy(0xb280, 0xa840, 0x80) + mstore(0xb300, 1) + mcopy(0xb320, 0xa8c0, 0x80) + mstore(0xb3a0, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0xb3c0, 0xacc0, 0x80) + mstore(0xb440, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0xb460, 0xa940, 0x80) + mstore(0xb4e0, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0xb500, 0xad40, 0x80) + mstore(0xb580, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0xb5a0, 0xaec0, 0x80) + mstore(0xb620, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0xb640, 0x6700, 0x80) + mstore(0xb6c0, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0xb6e0, 0x6480, 0x80) + mstore(0xb760, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0xb780, 0x6500, 0x80) + mstore(0xb800, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0xb820, 0x6580, 0x80) + mstore(0xb8a0, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0xb8c0, 0x6600, 0x80) + mstore(0xb940, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0xb960, 0x6680, 0x80) + mstore(0xb9e0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0xba00, 0x6280, 0x80) + mstore(0xba80, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0xbaa0, 0x6300, 0x80) + mstore(0xbb20, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0xbb40, 0x6380, 0x80) + mstore(0xbbc0, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0xbbe0, 0x6400, 0x80) + mstore(0xbc60, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0xbc80, 0x6780, 0x80) + mstore(0xbd00, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0xbd20, 0x6800, 0x80) + mstore(0xbda0, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0xbdc0, 0x6880, 0x80) + mstore(0xbe40, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0xbe60, 0x6900, 0x80) + mstore(0xbee0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0xbf00, 0x6b00, 0x80) + mstore(0xbf80, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0xbfa0, 0x6c00, 0x80) + mstore(0xc020, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0xc040, 0x6f80, 0x80) + mstore(0xc0c0, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0xc0e0, 0x7000, 0x80) + mstore(0xc160, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0xc180, 0x7080, 0x80) + mstore(0xc200, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0xc220, 0x7100, 0x80) + mstore(0xc2a0, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0xc2c0, 0x7180, 0x80) + mstore(0xc340, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0xc360, 0x7200, 0x80) + mstore(0xc3e0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0xc400, 0x7280, 0x80) + mstore(0xc480, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0xc4a0, 0x7300, 0x80) + mstore(0xc520, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0xc540, 0x7380, 0x80) + mstore(0xc5c0, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0xc5e0, 0x7400, 0x80) + mstore(0xc660, mload(add(X1_POWERS_MPTR, 0x400))) + mcopy(0xc680, 0x7480, 0x80) + mstore(0xc700, mload(add(X1_POWERS_MPTR, 0x420))) + mcopy(0xc720, 0x7500, 0x80) + mstore(0xc7a0, mload(add(X1_POWERS_MPTR, 0x440))) + mcopy(0xc7c0, 0x7580, 0x80) + mstore(0xc840, mload(add(X1_POWERS_MPTR, 0x460))) + mcopy(0xc860, 0x7600, 0x80) + mstore(0xc8e0, mload(add(X1_POWERS_MPTR, 0x480))) + mcopy(0xc900, 0x7680, 0x80) + mstore(0xc980, mload(add(X1_POWERS_MPTR, 0x4a0))) + mcopy(0xc9a0, 0x7700, 0x80) + mstore(0xca20, mload(add(X1_POWERS_MPTR, 0x4c0))) + mcopy(0xca40, 0x7780, 0x80) + mstore(0xcac0, mload(add(X1_POWERS_MPTR, 0x4e0))) + mcopy(0xcae0, 0x7800, 0x80) + mstore(0xcb60, mload(add(X1_POWERS_MPTR, 0x500))) + mcopy(0xcb80, 0x7880, 0x80) + mstore(0xcc00, mload(add(X1_POWERS_MPTR, 0x520))) let lin_query_scalar_41 := mload(add(X1_POWERS_MPTR, 0x540)) let lin_cur_scalar_41 := mulmod(lin_query_scalar_41, lin_one_minus_x_n, r) - mcopy(0xbca0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) - mstore(0xbd20, lin_cur_scalar_41) + mcopy(0xcc20, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0xcca0, lin_cur_scalar_41) lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) - mcopy(0xbd40, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) - mstore(0xbdc0, lin_cur_scalar_41) + mcopy(0xccc0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0xcd40, lin_cur_scalar_41) lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) - mcopy(0xbde0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) - mstore(0xbe60, lin_cur_scalar_41) + mcopy(0xcd60, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0xcde0, lin_cur_scalar_41) lin_cur_scalar_41 := mulmod(lin_cur_scalar_41, lin_x_split, r) - mcopy(0xbe80, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) - mstore(0xbf00, lin_cur_scalar_41) - mcopy(0xbf20, 0x5a00, 0x80) - mstore(0xbfa0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) - mcopy(0xbfc0, 0x5a80, 0x80) - mstore(0xc040, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) - mcopy(0xc060, 0x5b00, 0x80) - mstore(0xc0e0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) - mcopy(0xc100, 0x5c00, 0x80) - mstore(0xc180, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) - mcopy(0xc1a0, 0x5d00, 0x80) - mstore(0xc220, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) - mcopy(0xc240, 0x5d80, 0x80) - mstore(0xc2c0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) - mcopy(0xc2e0, 0x5e00, 0x80) - mstore(0xc360, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) - mcopy(0xc380, 0x5e80, 0x80) - mstore(0xc400, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) - mcopy(0xc420, 0x5f00, 0x80) - mstore(0xc4a0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) - mcopy(0xc4c0, 0x5f80, 0x80) - mstore(0xc540, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) - mcopy(0xc560, 0x9740, 0x80) - mstore(0xc5e0, x4_pow_1) - mcopy(0xc600, 0x97c0, 0x80) - mstore(0xc680, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) - mcopy(0xc6a0, 0x9840, 0x80) - mstore(0xc720, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) - mcopy(0xc740, 0x9cc0, 0x80) - mstore(0xc7c0, x4_pow_2) - mcopy(0xc7e0, 0x9e40, 0x80) - mstore(0xc860, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) - mcopy(0xc880, 0x9ec0, 0x80) - mstore(0xc900, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) - mcopy(0xc920, 0x91c0, 0x80) - mstore(0xc9a0, x4_pow_3) - mcopy(0xc9c0, 0x9240, 0x80) - mstore(0xca40, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) - mcopy(0xca60, 0x92c0, 0x80) - mstore(0xcae0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_3, r)) - mcopy(0xcb00, 0x9340, 0x80) - mstore(0xcb80, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_3, r)) - mcopy(0xcba0, 0x93c0, 0x80) - mstore(0xcc20, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_3, r)) - mcopy(0xcc40, 0x9440, 0x80) - mstore(0xccc0, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_3, r)) - mcopy(0xcce0, 0x94c0, 0x80) - mstore(0xcd60, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_3, r)) - mcopy(0xcd80, 0x9540, 0x80) - mstore(0xce00, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_3, r)) - mcopy(0xce20, 0x95c0, 0x80) - mstore(0xcea0, mulmod(mload(add(X1_POWERS_MPTR, 0x100)), x4_pow_3, r)) - mcopy(0xcec0, 0x9640, 0x80) - mstore(0xcf40, mulmod(mload(add(X1_POWERS_MPTR, 0x120)), x4_pow_3, r)) - mcopy(0xcf60, 0x96c0, 0x80) - mstore(0xcfe0, mulmod(mload(add(X1_POWERS_MPTR, 0x140)), x4_pow_3, r)) - mcopy(0xd000, 0x9a40, 0x80) - mstore(0xd080, x4_pow_4) - mcopy(0xd0a0, 0x9ac0, 0x80) - mstore(0xd120, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_4, r)) - mcopy(0xd140, 0x9b40, 0x80) - mstore(0xd1c0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_4, r)) - mcopy(0xd1e0, 0x9bc0, 0x80) - mstore(0xd260, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_4, r)) - mcopy(0xd280, 0x9c40, 0x80) - mstore(0xd300, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_4, r)) - mcopy(0xd320, F_COM_MPTR, 0x80) - mstore(0xd3a0, x4_pow_5) + mcopy(0xce00, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0xce80, lin_cur_scalar_41) + mcopy(0xcea0, 0x6980, 0x80) + mstore(0xcf20, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0xcf40, 0x6a00, 0x80) + mstore(0xcfc0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0xcfe0, 0x6a80, 0x80) + mstore(0xd060, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0xd080, 0x6b80, 0x80) + mstore(0xd100, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0xd120, 0x6c80, 0x80) + mstore(0xd1a0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) + mcopy(0xd1c0, 0x6d00, 0x80) + mstore(0xd240, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) + mcopy(0xd260, 0x6d80, 0x80) + mstore(0xd2e0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) + mcopy(0xd300, 0x6e00, 0x80) + mstore(0xd380, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) + mcopy(0xd3a0, 0x6e80, 0x80) + mstore(0xd420, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) + mcopy(0xd440, 0x6f00, 0x80) + mstore(0xd4c0, mulmod(lin_query_scalar_41, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) + mcopy(0xd4e0, 0xa6c0, 0x80) + mstore(0xd560, x4_pow_1) + mcopy(0xd580, 0xa740, 0x80) + mstore(0xd600, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0xd620, 0xa7c0, 0x80) + mstore(0xd6a0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0xd6c0, 0xac40, 0x80) + mstore(0xd740, x4_pow_2) + mcopy(0xd760, 0xadc0, 0x80) + mstore(0xd7e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0xd800, 0xae40, 0x80) + mstore(0xd880, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) + mcopy(0xd8a0, 0xa140, 0x80) + mstore(0xd920, x4_pow_3) + mcopy(0xd940, 0xa1c0, 0x80) + mstore(0xd9c0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) + mcopy(0xd9e0, 0xa240, 0x80) + mstore(0xda60, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_3, r)) + mcopy(0xda80, 0xa2c0, 0x80) + mstore(0xdb00, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_3, r)) + mcopy(0xdb20, 0xa340, 0x80) + mstore(0xdba0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_3, r)) + mcopy(0xdbc0, 0xa3c0, 0x80) + mstore(0xdc40, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_3, r)) + mcopy(0xdc60, 0xa440, 0x80) + mstore(0xdce0, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_3, r)) + mcopy(0xdd00, 0xa4c0, 0x80) + mstore(0xdd80, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_3, r)) + mcopy(0xdda0, 0xa540, 0x80) + mstore(0xde20, mulmod(mload(add(X1_POWERS_MPTR, 0x100)), x4_pow_3, r)) + mcopy(0xde40, 0xa5c0, 0x80) + mstore(0xdec0, mulmod(mload(add(X1_POWERS_MPTR, 0x120)), x4_pow_3, r)) + mcopy(0xdee0, 0xa640, 0x80) + mstore(0xdf60, mulmod(mload(add(X1_POWERS_MPTR, 0x140)), x4_pow_3, r)) + mcopy(0xdf80, 0xa9c0, 0x80) + mstore(0xe000, x4_pow_4) + mcopy(0xe020, 0xaa40, 0x80) + mstore(0xe0a0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_4, r)) + mcopy(0xe0c0, 0xaac0, 0x80) + mstore(0xe140, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_4, r)) + mcopy(0xe160, 0xab40, 0x80) + mstore(0xe1e0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_4, r)) + mcopy(0xe200, 0xabc0, 0x80) + mstore(0xe280, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_4, r)) + mcopy(0xe2a0, F_COM_MPTR, 0x80) + mstore(0xe320, x4_pow_5) if success { - success := staticcall(gas(), 0x0c, 0xa300, 0x30c0, FINAL_COM_MPTR, 0x80) + success := staticcall(gas(), 0x0c, 0xb280, 0x30c0, FINAL_COM_MPTR, 0x80) success := and(success, eq(returndatasize(), 0x80)) } mstore(V_MPTR, v) @@ -2078,28 +2166,28 @@ contract Halo2Verifier { // Scale z*pi - vG before the final pairing check // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) - mcopy(0x80, G1_BASE_MPTR, 0x80) - mstore(0x100, addmod(0, sub(r, mload(V_MPTR)), r)) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) if success { - success := staticcall(gas(), 0x0c, 0x80, 0xa0, 0x80, 0x80) + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, FINAL_COM_MPTR, 0x80) + mcopy(0x1080, FINAL_COM_MPTR, 0x80) if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, PI_MPTR, 0x80) - mstore(0x180, mload(X3_MPTR)) + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) if success { - success := staticcall(gas(), 0x0c, 0x100, 0xa0, 0x100, 0x80) + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) success := and(success, eq(returndatasize(), 0x80)) } if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(PAIRING_RHS_MPTR, 0x80, 0x80) + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) } } @@ -2181,6 +2269,12 @@ contract Halo2Verifier { // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) } diff --git a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2VerifyingKey.sol b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2VerifyingKey.sol index 7aac985fc..f8c08652b 100644 --- a/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2VerifyingKey.sol +++ b/proofs/solidity-verifier/target/ivc-keccak-solidity-dump/Halo2VerifyingKey.sol @@ -271,149 +271,149 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x19c0), 0x0456a29a706afacf2158850711006fe0acf6a437e9477bf6f782dfac86f2cf7b) // quotient_const mstore(add(payload, 0x19e0), 0x0397cc06bc030aab970dabe70cd498bbeea8daaea65607bb6a872125fec74a10) // quotient_const mstore(add(payload, 0x1a00), 0x73eda753299d7d4833351088b4af7508df8b737010b26e15294bfcbb91950003) // quotient_const - mstore(add(payload, 0x1a20), 0x058560108a8005860008060d000b02000105852011852011852005858008060d) // quotient_program - mstore(add(payload, 0x1a40), 0x000b0300000585401185401185400585a008060d000b03000105856011856011) // quotient_program - mstore(add(payload, 0x1a60), 0x856005862008060d000b0300011b00001b000105860008108b20058520118520) // quotient_program - mstore(add(payload, 0x1a80), 0x1185800d01060585401185401185a00d02060585601185601186200d03060d00) // quotient_program - mstore(add(payload, 0x1aa0), 0x0b0300011b000221020403070002000085200585400685600785800885a00085) // quotient_program - mstore(add(payload, 0x1ac0), 0xc00585e00686000986200a86400b86600c86800d86a00e86c00f86e010870011) // quotient_program - mstore(add(payload, 0x1ae0), 0x87200787400887600987800a87a085200085c00585e006860007874008876009) // quotient_program - mstore(add(payload, 0x1b00), 0x87800a87a085400585c00685e00786000887400987600a87801287a085600685) // quotient_program - mstore(add(payload, 0x1b20), 0xc00785e00886000987400a87601287801387a085800785c00885e00986000a87) // quotient_program - mstore(add(payload, 0x1b40), 0x401287601387801487a085a00885c00985e00a86001287401387601487801587) // quotient_program - mstore(add(payload, 0x1b60), 0xa086200985c00a85e01286001387401487601587801687a086400a85c01285e0) // quotient_program - mstore(add(payload, 0x1b80), 0x1386001487401587601687801787a01887c01988000b04000121021a03070001) // quotient_program - mstore(add(payload, 0x1ba0), 0x000085200585400685601b85801c85a00085c00585e00686001d86201e86400b) // quotient_program - mstore(add(payload, 0x1bc0), 0x86600c86800d86a01f86c02086e02187002287201b87401c87601d87801e87a0) // quotient_program - mstore(add(payload, 0x1be0), 0x85200085c00585e00686001b87401c87601d87801e87a085400585c00685e01b) // quotient_program - mstore(add(payload, 0x1c00), 0x86001c87401d87601e87802387a085600685c01b85e01c86001d87401e876023) // quotient_program - mstore(add(payload, 0x1c20), 0x87802487a085801b85c01c85e01d86001e87402387602487802587a085a01c85) // quotient_program - mstore(add(payload, 0x1c40), 0xc01d85e01e86002387402487602587802687a086201d85c01e85e02386002487) // quotient_program - mstore(add(payload, 0x1c60), 0x402587602687802787a086401e85c02385e02486002587402687602787802887) // quotient_program - mstore(add(payload, 0x1c80), 0xa02987c00b0400011b000321022a01000009000986200a86400b86600c86800d) // quotient_program - mstore(add(payload, 0x1ca0), 0x86a00e86c00f86e00085200585400685600785800885a01087001187201887c0) // quotient_program - mstore(add(payload, 0x1cc0), 0x1988000b05000121022b01000008001d86201e86400b86600c86800d86a01f86) // quotient_program - mstore(add(payload, 0x1ce0), 0xc02086e00085200585400685601b85801c85a02187002287202987c00b050001) // quotient_program - mstore(add(payload, 0x1d00), 0x210388202c0000000b2b0b85200c85400d85602d85c02e85e02f86000b86600c) // quotient_program - mstore(add(payload, 0x1d20), 0x86800d86a03087c03187e00b852086600c852086800d852086a00c854086600d) // quotient_program - mstore(add(payload, 0x1d40), 0x8540868032854087200d856086603285608700338560872032858086e0338580) // quotient_program - mstore(add(payload, 0x1d60), 0x870034858087203285a086c03385a086e03485a087003585a087200085c085c0) // quotient_program - mstore(add(payload, 0x1d80), 0x2e85c085e02f85c086000685e085e03685e087a0368600878037860087a03286) // quotient_program - mstore(add(payload, 0x1da0), 0x2086a033862086c034862086e035862087003886208720328640868033864086) // quotient_program - mstore(add(payload, 0x1dc0), 0xa034864086c035864086e038864087003986408720368740876037874087803a) // quotient_program - mstore(add(payload, 0x1de0), 0x874087a03b876087603a876087803c876087a03d878087803e878087a03f87a0) // quotient_program - mstore(add(payload, 0x1e00), 0x87a00d000b060000210388204003080002150b85200c85400d85600e85800f85) // quotient_program - mstore(add(payload, 0x1e20), 0xa02d85c02e85e02f86001086201186400b86600c86800d86a00e86c00f86e010) // quotient_program - mstore(add(payload, 0x1e40), 0x87001187204187404287604387804487a085200b86600c86800d86a00e86c00f) // quotient_program - mstore(add(payload, 0x1e60), 0x86e010870011872085400c86600d86800e86a00f86c01086e011870045872085) // quotient_program - mstore(add(payload, 0x1e80), 0x600d86600e86800f86a01086c01186e045870046872085800e86600f86801086) // quotient_program - mstore(add(payload, 0x1ea0), 0xa01186c04586e046870047872085a00f86601086801186a04586c04686e04787) // quotient_program - mstore(add(payload, 0x1ec0), 0x0048872085c00085c02e85e02f86004187404287604387804487a08620108660) // quotient_program - mstore(add(payload, 0x1ee0), 0x1186804586a04686c04786e048870049872086401186604586804686a04786c0) // quotient_program - mstore(add(payload, 0x1f00), 0x4886e04987004a87201887c01988000685e085e04185e086004285e087404385) // quotient_program - mstore(add(payload, 0x1f20), 0xe087604485e087804b85e087a00886008600438600874044860087604b860087) // quotient_program - mstore(add(payload, 0x1f40), 0x804c860087a00a874087404b874087604c874087804d874087a013876087604d) // quotient_program - mstore(add(payload, 0x1f60), 0x876087804e876087a015878087804f878087a01787a087a00d000b0600012103) // quotient_program - mstore(add(payload, 0x1f80), 0x88205003080001150b85200c85400d85601f85802085a02d85c02e85e02f8600) // quotient_program - mstore(add(payload, 0x1fa0), 0x2186202286400b86600c86800d86a01f86c02086e02187002287205187405287) // quotient_program - mstore(add(payload, 0x1fc0), 0x605387805487a085200b86600c86800d86a01f86c02086e02187002287208540) // quotient_program - mstore(add(payload, 0x1fe0), 0x0c86600d86801f86a02086c02186e022870055872085600d86601f86802086a0) // quotient_program - mstore(add(payload, 0x2000), 0x2186c02286e055870056872085801f86602086802186a02286c05586e0568700) // quotient_program - mstore(add(payload, 0x2020), 0x57872085a02086602186802286a05586c05686e057870058872085c00085c02e) // quotient_program - mstore(add(payload, 0x2040), 0x85e02f86005187405287605387805487a086202186602286805586a05686c057) // quotient_program - mstore(add(payload, 0x2060), 0x86e058870059872086402286605586805686a05786c05886e05987005a872029) // quotient_program - mstore(add(payload, 0x2080), 0x87c00685e085e05185e086005285e087405385e087605485e087805b85e087a0) // quotient_program - mstore(add(payload, 0x20a0), 0x1c86008600538600874054860087605b860087805c860087a01e874087405b87) // quotient_program - mstore(add(payload, 0x20c0), 0x4087605c874087805d874087a024876087605d876087805e876087a026878087) // quotient_program - mstore(add(payload, 0x20e0), 0x805f878087a02887a087a00d000b06000121038820600000000c390b85200c85) // quotient_program - mstore(add(payload, 0x2100), 0x400d85603087c03187e00088200088400588600688800b89200c89400d896000) // quotient_program - mstore(add(payload, 0x2120), 0x85c088400585c088600685c088800b85c089200c85c089400d85c089600585e0) // quotient_program - mstore(add(payload, 0x2140), 0x88400685e088606185e089000c85e089200d85e089403285e089e00686008840) // quotient_program - mstore(add(payload, 0x2160), 0x61860088e03b860089000d8600892032860089c033860089e000866088200586) // quotient_program - mstore(add(payload, 0x2180), 0x8088200686a0882061874088c03b874088e0628740890032874089a033874089) // quotient_program - mstore(add(payload, 0x21a0), 0xc034874089e061876088a03b876088c062876088e03d87608900328760898033) // quotient_program - mstore(add(payload, 0x21c0), 0x876089a034876089c035876089e061878088803b878088a062878088c03d8780) // quotient_program - mstore(add(payload, 0x21e0), 0x88e063878089003287808960338780898034878089a035878089c038878089e0) // quotient_program - mstore(add(payload, 0x2200), 0x6187a088603b87a088806287a088a03d87a088c06387a088e03f87a089003287) // quotient_program - mstore(add(payload, 0x2220), 0xa089403387a089603487a089803587a089a03887a089c03987a089e00d000b07) // quotient_program - mstore(add(payload, 0x2240), 0x00002103882064020f000a001988000088200088400588600688800788a00888) // quotient_program - mstore(add(payload, 0x2260), 0xc00988e00a89000b89200c89400d89600e89800f89a088200086600586800686) // quotient_program - mstore(add(payload, 0x2280), 0xa00786c00886e00987000a872088400085c00585e00686000787400887600987) // quotient_program - mstore(add(payload, 0x22a0), 0x800a87a088600585c00685e00786000887400987600a87801287a088800685c0) // quotient_program - mstore(add(payload, 0x22c0), 0x0785e00886000987400a87601287801387a088a00785c00885e00986000a8740) // quotient_program - mstore(add(payload, 0x22e0), 0x1287601387801487a088c00885c00985e00a86001287401387601487801587a0) // quotient_program - mstore(add(payload, 0x2300), 0x88e00985c00a85e01286001387401487601587801687a089000a85c01285e013) // quotient_program - mstore(add(payload, 0x2320), 0x86001487401587601687801787a085c00b89200c89400d89600e89800f89a010) // quotient_program - mstore(add(payload, 0x2340), 0x89c01189e085e00c89200d89400e89600f89801089a01189c04589e086000d89) // quotient_program - mstore(add(payload, 0x2360), 0x200e89400f89601089801189a04589c04689e087400e89200f89401089601189) // quotient_program - mstore(add(payload, 0x2380), 0x804589a04689c04789e087600f89201089401189604589804689a04789c04889) // quotient_program - mstore(add(payload, 0x23a0), 0xe087801089201189404589604689804789a04889c04989e087a0118920458940) // quotient_program - mstore(add(payload, 0x23c0), 0x4689604789804889a04989c04a89e00b85200c85400d85600e85800f85a01086) // quotient_program - mstore(add(payload, 0x23e0), 0x201186401887c01089c01189e00d000b0700012103882065020f000900008820) // quotient_program - mstore(add(payload, 0x2400), 0x0088400588600688801b88a01c88c01d88e01e89000b89200c89400d89601f89) // quotient_program - mstore(add(payload, 0x2420), 0x802089a02189c088200086600586800686a01b86c01c86e01d87001e87208840) // quotient_program - mstore(add(payload, 0x2440), 0x0085c00585e00686001b87401c87601d87801e87a088600585c00685e01b8600) // quotient_program - mstore(add(payload, 0x2460), 0x1c87401d87601e87802387a088800685c01b85e01c86001d87401e8760238780) // quotient_program - mstore(add(payload, 0x2480), 0x2487a088a01b85c01c85e01d86001e87402387602487802587a088c01c85c01d) // quotient_program - mstore(add(payload, 0x24a0), 0x85e01e86002387402487602587802687a088e01d85c01e85e023860024874025) // quotient_program - mstore(add(payload, 0x24c0), 0x87602687802787a089001e85c02385e02486002587402687602787802887a085) // quotient_program - mstore(add(payload, 0x24e0), 0xc00b89200c89400d89601f89802089a02189c02289e085e00c89200d89401f89) // quotient_program - mstore(add(payload, 0x2500), 0x602089802189a02289c05589e086000d89201f89402089602189802289a05589) // quotient_program - mstore(add(payload, 0x2520), 0xc05689e087401f89202089402189602289805589a05689c05789e08760208920) // quotient_program - mstore(add(payload, 0x2540), 0x2189402289605589805689a05789c05889e08780218920228940558960568980) // quotient_program - mstore(add(payload, 0x2560), 0x5789a05889c05989e087a02289205589405689605789805889a05989c05a89e0) // quotient_program - mstore(add(payload, 0x2580), 0x0b85200c85400d85601f85802085a02186202286402987c02289e00d000b0700) // quotient_program - mstore(add(payload, 0x25a0), 0x0121038820660000000b2b6785206885406985606a85c06b85e06c86006a8660) // quotient_program - mstore(add(payload, 0x25c0), 0x6b86806c86a03087c03187e06d85208520688520854069852085606e85408540) // quotient_program - mstore(add(payload, 0x25e0), 0x6f854086406f8560862070856086406f858085a0708580862071858086407285) // quotient_program - mstore(add(payload, 0x2600), 0xa085a07185a086207385a086406a85c086606b85c086806c85c086a06b85e086) // quotient_program - mstore(add(payload, 0x2620), 0x606c85e086807485e087206c8600866074860087007586008720768620862077) // quotient_program - mstore(add(payload, 0x2640), 0x86208640788640864074868087a07486a087807586a087a07486c087607586c0) // quotient_program - mstore(add(payload, 0x2660), 0x87807986c087a07486e087407586e087607986e087807a86e087a07587008740) // quotient_program - mstore(add(payload, 0x2680), 0x79870087607a870087807b870087a079872087407a872087607b872087807c87) // quotient_program - mstore(add(payload, 0x26a0), 0x2087a00d000b080000210388207d03080002156785206885406985607e85807f) // quotient_program - mstore(add(payload, 0x26c0), 0x85a06a85c06b85e06c86008086208186406a86606b86806c86a08286c08386e0) // quotient_program - mstore(add(payload, 0x26e0), 0x8487008587208287408387608487808587a085206d85206885406985607e8580) // quotient_program - mstore(add(payload, 0x2700), 0x7f85a080862081864085c06a86606b86806c86a08286c08386e0848700858720) // quotient_program - mstore(add(payload, 0x2720), 0x85e06b86606c86808286a08386c08486e085870086872086006c866082868083) // quotient_program - mstore(add(payload, 0x2740), 0x86a08486c08586e086870087872087408286608386808486a08586c08686e087) // quotient_program - mstore(add(payload, 0x2760), 0x870088872087608386608486808586a08686c08786e088870089872087808486) // quotient_program - mstore(add(payload, 0x2780), 0x608586808686a08786c08886e08987008a872087a08586608686808786a08886) // quotient_program - mstore(add(payload, 0x27a0), 0xc08986e08a87008b87201887c01988006e854085407e854085607f8540858080) // quotient_program - mstore(add(payload, 0x27c0), 0x854085a081854086208c854086408d85608560808560858081856085a08c8560) // quotient_program - mstore(add(payload, 0x27e0), 0x86208e856086408f858085808c858085a08e8580862090858086409185a085a0) // quotient_program - mstore(add(payload, 0x2800), 0x9085a086209285a086409386208620948620864095864086400d000b08000121) // quotient_program - mstore(add(payload, 0x2820), 0x0388209603080001156785206885406985609785809885a06a85c06b85e06c86) // quotient_program - mstore(add(payload, 0x2840), 0x009986209a86406a86606b86806c86a09b86c09c86e09d87009e87209b87409c) // quotient_program - mstore(add(payload, 0x2860), 0x87609d87809e87a085206d85206885406985609785809885a09986209a864085) // quotient_program - mstore(add(payload, 0x2880), 0xc06a86606b86806c86a09b86c09c86e09d87009e872085e06b86606c86809b86) // quotient_program - mstore(add(payload, 0x28a0), 0xa09c86c09d86e09e87009f872086006c86609b86809c86a09d86c09e86e09f87) // quotient_program - mstore(add(payload, 0x28c0), 0x00a0872087409b86609c86809d86a09e86c09f86e0a08700a1872087609c8660) // quotient_program - mstore(add(payload, 0x28e0), 0x9d86809e86a09f86c0a086e0a18700a2872087809d86609e86809f86a0a086c0) // quotient_program - mstore(add(payload, 0x2900), 0xa186e0a28700a3872087a09e86609f8680a086a0a186c0a286e0a38700a48720) // quotient_program - mstore(add(payload, 0x2920), 0x2987c06e854085409785408560988540858099854085a09a85408620a5854086) // quotient_program - mstore(add(payload, 0x2940), 0x40a68560856099856085809a856085a0a585608620a785608640a885808580a5) // quotient_program - mstore(add(payload, 0x2960), 0x858085a0a785808620a985808640aa85a085a0a985a08620ab85a08640ac8620) // quotient_program - mstore(add(payload, 0x2980), 0x8620ad86208640ae864086400d000b08000121038820af0000000e1000852005) // quotient_program - mstore(add(payload, 0x29a0), 0x85400685606a85c06b85e06c86000086600586800686a03087c03187e0008840) // quotient_program - mstore(add(payload, 0x29c0), 0x0588600688800b85c085c06b85c085e06c85c086000d85e085e07485e087a074) // quotient_program - mstore(add(payload, 0x29e0), 0x8600878075860087a07487408760758740878079874087a03387608760798760) // quotient_program - mstore(add(payload, 0x2a00), 0x87807a876087a035878087807b878087a03987a087a00d000b09000021038820) // quotient_program - mstore(add(payload, 0x2a20), 0xb004010002150085200585400685600785800885a06a85c06b85e06c86000986) // quotient_program - mstore(add(payload, 0x2a40), 0x200a86400086600586800686a00786c00886e00987000a872082874083876084) // quotient_program - mstore(add(payload, 0x2a60), 0x87808587a00088400588600688800788a00888c00988e00a890085c00b85c06b) // quotient_program - mstore(add(payload, 0x2a80), 0x85e06c86008287408387608487808587a01887c01988000d85e085e08285e086) // quotient_program - mstore(add(payload, 0x2aa0), 0x008385e087408485e087608585e087808685e087a00f86008600848600874085) // quotient_program - mstore(add(payload, 0x2ac0), 0x86008760868600878087860087a0118740874086874087608787408780888740) // quotient_program - mstore(add(payload, 0x2ae0), 0x87a04687608760888760878089876087a048878087808a878087a04a87a087a0) // quotient_program - mstore(add(payload, 0x2b00), 0x0d000b09000121038820b104010001150085200585400685601b85801c85a06a) // quotient_program - mstore(add(payload, 0x2b20), 0x85c06b85e06c86001d86201e86400086600586800686a01b86c01c86e01d8700) // quotient_program - mstore(add(payload, 0x2b40), 0x1e87209b87409c87609d87809e87a00088400588600688801b88a01c88c01d88) // quotient_program - mstore(add(payload, 0x2b60), 0xe01e890085c00b85c06b85e06c86009b87409c87609d87809e87a02987c00d85) // quotient_program - mstore(add(payload, 0x2b80), 0xe085e09b85e086009c85e087409d85e087609e85e087809f85e087a020860086) // quotient_program - mstore(add(payload, 0x2ba0), 0x009d860087409e860087609f86008780a0860087a022874087409f87408760a0) // quotient_program - mstore(add(payload, 0x2bc0), 0x87408780a1874087a05687608760a187608780a2876087a05887808780a38780) // quotient_program - mstore(add(payload, 0x2be0), 0x87a05a87a087a00d000b090001191f0000000000000000000000000000000000) // quotient_program + mstore(add(payload, 0x1a20), 0x0594e0109a0005958008060d000b0200010594a01194a01194a005950008060d) // quotient_program + mstore(add(payload, 0x1a40), 0x000b0300000594c01194c01194c005952008060d000b0300010594e01194e011) // quotient_program + mstore(add(payload, 0x1a60), 0x94e00595a008060d000b0300011b00001b000105958008109aa00594a01194a0) // quotient_program + mstore(add(payload, 0x1a80), 0x1195000d01060594c01194c01195200d02060594e01194e01195a00d03060d00) // quotient_program + mstore(add(payload, 0x1aa0), 0x0b0300011b000221020403070002000094a00594c00694e00795000895200095) // quotient_program + mstore(add(payload, 0x1ac0), 0x400595600695800995a00a95c00b95e00c96000d96200e96400f966010968011) // quotient_program + mstore(add(payload, 0x1ae0), 0x96a00796c00896e00997000a972094a00095400595600695800796c00896e009) // quotient_program + mstore(add(payload, 0x1b00), 0x97000a972094c00595400695600795800896c00996e00a970012972094e00695) // quotient_program + mstore(add(payload, 0x1b20), 0x400795600895800996c00a96e012970013972095000795400895600995800a96) // quotient_program + mstore(add(payload, 0x1b40), 0xc01296e013970014972095200895400995600a95801296c01396e01497001597) // quotient_program + mstore(add(payload, 0x1b60), 0x2095a00995400a95601295801396c01496e015970016972095c00a9540129560) // quotient_program + mstore(add(payload, 0x1b80), 0x1395801496c01596e01697001797201897401997800b04000121021a03070001) // quotient_program + mstore(add(payload, 0x1ba0), 0x000094a00594c00694e01b95001c95200095400595600695801d95a01e95c00b) // quotient_program + mstore(add(payload, 0x1bc0), 0x95e00c96000d96201f96402096602196802296a01b96c01c96e01d97001e9720) // quotient_program + mstore(add(payload, 0x1be0), 0x94a00095400595600695801b96c01c96e01d97001e972094c00595400695601b) // quotient_program + mstore(add(payload, 0x1c00), 0x95801c96c01d96e01e970023972094e00695401b95601c95801d96c01e96e023) // quotient_program + mstore(add(payload, 0x1c20), 0x970024972095001b95401c95601d95801e96c02396e024970025972095201c95) // quotient_program + mstore(add(payload, 0x1c40), 0x401d95601e95802396c02496e025970026972095a01d95401e95602395802496) // quotient_program + mstore(add(payload, 0x1c60), 0xc02596e026970027972095c01e95402395602495802596c02696e02797002897) // quotient_program + mstore(add(payload, 0x1c80), 0x202997400b0400011b000321022a01000009000995a00a95c00b95e00c96000d) // quotient_program + mstore(add(payload, 0x1ca0), 0x96200e96400f96600094a00594c00694e00795000895201096801196a0189740) // quotient_program + mstore(add(payload, 0x1cc0), 0x1997800b05000121022b01000008001d95a01e95c00b95e00c96000d96201f96) // quotient_program + mstore(add(payload, 0x1ce0), 0x402096600094a00594c00694e01b95001c95202196802296a02997400b050001) // quotient_program + mstore(add(payload, 0x1d00), 0x210397a02c0000000b2b0b94a00c94c00d94e02d95402e95602f95800b95e00c) // quotient_program + mstore(add(payload, 0x1d20), 0x96000d96203097403197600b94a095e00c94a096000d94a096200c94c095e00d) // quotient_program + mstore(add(payload, 0x1d40), 0x94c096003294c096a00d94e095e03294e096803394e096a03295009660339500) // quotient_program + mstore(add(payload, 0x1d60), 0x968034950096a032952096403395209660349520968035952096a00095409540) // quotient_program + mstore(add(payload, 0x1d80), 0x2e954095602f9540958006956095603695609720369580970037958097203295) // quotient_program + mstore(add(payload, 0x1da0), 0xa096203395a096403495a096603595a096803895a096a03295c096003395c096) // quotient_program + mstore(add(payload, 0x1dc0), 0x203495c096403595c096603895c096803995c096a03696c096e03796c097003a) // quotient_program + mstore(add(payload, 0x1de0), 0x96c097203b96e096e03a96e097003c96e097203d970097003e970097203f9720) // quotient_program + mstore(add(payload, 0x1e00), 0x97200d000b060000210397a04003080002150b94a00c94c00d94e00e95000f95) // quotient_program + mstore(add(payload, 0x1e20), 0x202d95402e95602f95801095a01195c00b95e00c96000d96200e96400f966010) // quotient_program + mstore(add(payload, 0x1e40), 0x96801196a04196c04296e043970044972094a00b95e00c96000d96200e96400f) // quotient_program + mstore(add(payload, 0x1e60), 0x96601096801196a094c00c95e00d96000e96200f96401096601196804596a094) // quotient_program + mstore(add(payload, 0x1e80), 0xe00d95e00e96000f96201096401196604596804696a095000e95e00f96001096) // quotient_program + mstore(add(payload, 0x1ea0), 0x201196404596604696804796a095200f95e01096001196204596404696604796) // quotient_program + mstore(add(payload, 0x1ec0), 0x804896a095400095402e95602f95804196c04296e043970044972095a01095e0) // quotient_program + mstore(add(payload, 0x1ee0), 0x1196004596204696404796604896804996a095c01195e0459600469620479640) // quotient_program + mstore(add(payload, 0x1f00), 0x4896604996804a96a01897401997800695609560419560958042956096c04395) // quotient_program + mstore(add(payload, 0x1f20), 0x6096e044956097004b95609720089580958043958096c044958096e04b958097) // quotient_program + mstore(add(payload, 0x1f40), 0x004c958097200a96c096c04b96c096e04c96c097004d96c097201396e096e04d) // quotient_program + mstore(add(payload, 0x1f60), 0x96e097004e96e0972015970097004f9700972017972097200d000b0600012103) // quotient_program + mstore(add(payload, 0x1f80), 0x97a05003080001150b94a00c94c00d94e01f95002095202d95402e95602f9580) // quotient_program + mstore(add(payload, 0x1fa0), 0x2195a02295c00b95e00c96000d96201f96402096602196802296a05196c05296) // quotient_program + mstore(add(payload, 0x1fc0), 0xe053970054972094a00b95e00c96000d96201f96402096602196802296a094c0) // quotient_program + mstore(add(payload, 0x1fe0), 0x0c95e00d96001f96202096402196602296805596a094e00d95e01f9600209620) // quotient_program + mstore(add(payload, 0x2000), 0x2196402296605596805696a095001f95e0209600219620229640559660569680) // quotient_program + mstore(add(payload, 0x2020), 0x5796a095202095e02196002296205596405696605796805896a095400095402e) // quotient_program + mstore(add(payload, 0x2040), 0x95602f95805196c05296e053970054972095a02195e022960055962056964057) // quotient_program + mstore(add(payload, 0x2060), 0x96605896805996a095c02295e05596005696205796405896605996805a96a029) // quotient_program + mstore(add(payload, 0x2080), 0x97400695609560519560958052956096c053956096e054956097005b95609720) // quotient_program + mstore(add(payload, 0x20a0), 0x1c9580958053958096c054958096e05b958097005c958097201e96c096c05b96) // quotient_program + mstore(add(payload, 0x20c0), 0xc096e05c96c097005d96c097202496e096e05d96e097005e96e0972026970097) // quotient_program + mstore(add(payload, 0x20e0), 0x005f9700972028972097200d000b060001210397a0600000000c390b94a00c94) // quotient_program + mstore(add(payload, 0x2100), 0xc00d94e03097403197600097a00097c00597e00698000b98a00c98c00d98e000) // quotient_program + mstore(add(payload, 0x2120), 0x954097c005954097e006954098000b954098a00c954098c00d954098e0059560) // quotient_program + mstore(add(payload, 0x2140), 0x97c006956097e061956098800c956098a00d956098c0329560996006958097c0) // quotient_program + mstore(add(payload, 0x2160), 0x61958098603b958098800d958098a0329580994033958099600095e097a00596) // quotient_program + mstore(add(payload, 0x2180), 0x0097a006962097a06196c098403b96c098606296c098803296c099203396c099) // quotient_program + mstore(add(payload, 0x21a0), 0x403496c099606196e098203b96e098406296e098603d96e098803296e0990033) // quotient_program + mstore(add(payload, 0x21c0), 0x96e099203496e099403596e0996061970098003b9700982062970098403d9700) // quotient_program + mstore(add(payload, 0x21e0), 0x9860639700988032970098e03397009900349700992035970099403897009960) // quotient_program + mstore(add(payload, 0x2200), 0x61972097e03b9720980062972098203d9720984063972098603f972098803297) // quotient_program + mstore(add(payload, 0x2220), 0x2098c033972098e034972099003597209920389720994039972099600d000b07) // quotient_program + mstore(add(payload, 0x2240), 0x0000210397a064020f000a001997800097a00097c00597e00698000798200898) // quotient_program + mstore(add(payload, 0x2260), 0x400998600a98800b98a00c98c00d98e00e99000f992097a00095e00596000696) // quotient_program + mstore(add(payload, 0x2280), 0x200796400896600996800a96a097c00095400595600695800796c00896e00997) // quotient_program + mstore(add(payload, 0x22a0), 0x000a972097e00595400695600795800896c00996e00a97001297209800069540) // quotient_program + mstore(add(payload, 0x22c0), 0x0795600895800996c00a96e012970013972098200795400895600995800a96c0) // quotient_program + mstore(add(payload, 0x22e0), 0x1296e013970014972098400895400995600a95801296c01396e0149700159720) // quotient_program + mstore(add(payload, 0x2300), 0x98600995400a95601295801396c01496e015970016972098800a954012956013) // quotient_program + mstore(add(payload, 0x2320), 0x95801496c01596e016970017972095400b98a00c98c00d98e00e99000f992010) // quotient_program + mstore(add(payload, 0x2340), 0x994011996095600c98a00d98c00e98e00f990010992011994045996095800d98) // quotient_program + mstore(add(payload, 0x2360), 0xa00e98c00f98e010990011992045994046996096c00e98a00f98c01098e01199) // quotient_program + mstore(add(payload, 0x2380), 0x0045992046994047996096e00f98a01098c01198e04599004699204799404899) // quotient_program + mstore(add(payload, 0x23a0), 0x6097001098a01198c04598e046990047992048994049996097201198a04598c0) // quotient_program + mstore(add(payload, 0x23c0), 0x4698e04799004899204999404a99600b94a00c94c00d94e00e95000f95201095) // quotient_program + mstore(add(payload, 0x23e0), 0xa01195c01897401099401199600d000b070001210397a065020f0009000097a0) // quotient_program + mstore(add(payload, 0x2400), 0x0097c00597e00698001b98201c98401d98601e98800b98a00c98c00d98e01f99) // quotient_program + mstore(add(payload, 0x2420), 0x0020992021994097a00095e00596000696201b96401c96601d96801e96a097c0) // quotient_program + mstore(add(payload, 0x2440), 0x0095400595600695801b96c01c96e01d97001e972097e00595400695601b9580) // quotient_program + mstore(add(payload, 0x2460), 0x1c96c01d96e01e970023972098000695401b95601c95801d96c01e96e0239700) // quotient_program + mstore(add(payload, 0x2480), 0x24972098201b95401c95601d95801e96c02396e024970025972098401c95401d) // quotient_program + mstore(add(payload, 0x24a0), 0x95601e95802396c02496e025970026972098601d95401e95602395802496c025) // quotient_program + mstore(add(payload, 0x24c0), 0x96e026970027972098801e95402395602495802596c02696e027970028972095) // quotient_program + mstore(add(payload, 0x24e0), 0x400b98a00c98c00d98e01f990020992021994022996095600c98a00d98c01f98) // quotient_program + mstore(add(payload, 0x2500), 0xe020990021992022994055996095800d98a01f98c02098e02199002299205599) // quotient_program + mstore(add(payload, 0x2520), 0x4056996096c01f98a02098c02198e022990055992056994057996096e02098a0) // quotient_program + mstore(add(payload, 0x2540), 0x2198c02298e055990056992057994058996097002198a02298c05598e0569900) // quotient_program + mstore(add(payload, 0x2560), 0x57992058994059996097202298a05598c05698e05799005899205999405a9960) // quotient_program + mstore(add(payload, 0x2580), 0x0b94a00c94c00d94e01f95002095202195a02295c02997402299600d000b0700) // quotient_program + mstore(add(payload, 0x25a0), 0x01210397a0660000000b2b6794a06894c06994e06a95406b95606c95806a95e0) // quotient_program + mstore(add(payload, 0x25c0), 0x6b96006c96203097403197606d94a094a06894a094c06994a094e06e94c094c0) // quotient_program + mstore(add(payload, 0x25e0), 0x6f94c095c06f94e095a07094e095c06f9500952070950095a071950095c07295) // quotient_program + mstore(add(payload, 0x2600), 0x20952071952095a073952095c06a954095e06b954096006c954096206b956095) // quotient_program + mstore(add(payload, 0x2620), 0xe06c9560960074956096a06c958095e0749580968075958096a07695a095a077) // quotient_program + mstore(add(payload, 0x2640), 0x95a095c07895c095c074960097207496209700759620972074964096e0759640) // quotient_program + mstore(add(payload, 0x2660), 0x9700799640972074966096c075966096e079966097007a9660972075968096c0) // quotient_program + mstore(add(payload, 0x2680), 0x79968096e07a968097007b968097207996a096c07a96a096e07b96a097007c96) // quotient_program + mstore(add(payload, 0x26a0), 0xa097200d000b080000210397a07d03080002156794a06894c06994e07e95007f) // quotient_program + mstore(add(payload, 0x26c0), 0x95206a95406b95606c95808095a08195c06a95e06b96006c9620829640839660) // quotient_program + mstore(add(payload, 0x26e0), 0x8496808596a08296c08396e084970085972094a06d94a06894c06994e07e9500) // quotient_program + mstore(add(payload, 0x2700), 0x7f95208095a08195c095406a95e06b96006c96208296408396608496808596a0) // quotient_program + mstore(add(payload, 0x2720), 0x95606b95e06c96008296208396408496608596808696a095806c95e082960083) // quotient_program + mstore(add(payload, 0x2740), 0x96208496408596608696808796a096c08295e083960084962085964086966087) // quotient_program + mstore(add(payload, 0x2760), 0x96808896a096e08395e08496008596208696408796608896808996a097008495) // quotient_program + mstore(add(payload, 0x2780), 0xe08596008696208796408896608996808a96a097208595e08696008796208896) // quotient_program + mstore(add(payload, 0x27a0), 0x408996608a96808b96a01897401997806e94c094c07e94c094e07f94c0950080) // quotient_program + mstore(add(payload, 0x27c0), 0x94c095208194c095a08c94c095c08d94e094e08094e095008194e095208c94e0) // quotient_program + mstore(add(payload, 0x27e0), 0x95a08e94e095c08f950095008c950095208e950095a090950095c09195209520) // quotient_program + mstore(add(payload, 0x2800), 0x90952095a092952095c09395a095a09495a095c09595c095c00d000b08000121) // quotient_program + mstore(add(payload, 0x2820), 0x0397a09603080001156794a06894c06994e09795009895206a95406b95606c95) // quotient_program + mstore(add(payload, 0x2840), 0x809995a09a95c06a95e06b96006c96209b96409c96609d96809e96a09b96c09c) // quotient_program + mstore(add(payload, 0x2860), 0x96e09d97009e972094a06d94a06894c06994e09795009895209995a09a95c095) // quotient_program + mstore(add(payload, 0x2880), 0x406a95e06b96006c96209b96409c96609d96809e96a095606b95e06c96009b96) // quotient_program + mstore(add(payload, 0x28a0), 0x209c96409d96609e96809f96a095806c95e09b96009c96209d96409e96609f96) // quotient_program + mstore(add(payload, 0x28c0), 0x80a096a096c09b95e09c96009d96209e96409f9660a09680a196a096e09c95e0) // quotient_program + mstore(add(payload, 0x28e0), 0x9d96009e96209f9640a09660a19680a296a097009d95e09e96009f9620a09640) // quotient_program + mstore(add(payload, 0x2900), 0xa19660a29680a396a097209e95e09f9600a09620a19640a29660a39680a496a0) // quotient_program + mstore(add(payload, 0x2920), 0x2997406e94c094c09794c094e09894c095009994c095209a94c095a0a594c095) // quotient_program + mstore(add(payload, 0x2940), 0xc0a694e094e09994e095009a94e09520a594e095a0a794e095c0a895009500a5) // quotient_program + mstore(add(payload, 0x2960), 0x95009520a7950095a0a9950095c0aa95209520a9952095a0ab952095c0ac95a0) // quotient_program + mstore(add(payload, 0x2980), 0x95a0ad95a095c0ae95c095c00d000b080001210397a0af0000000e100094a005) // quotient_program + mstore(add(payload, 0x29a0), 0x94c00694e06a95406b95606c95800095e00596000696203097403197600097c0) // quotient_program + mstore(add(payload, 0x29c0), 0x0597e00698000b954095406b954095606c954095800d95609560749560972074) // quotient_program + mstore(add(payload, 0x29e0), 0x9580970075958097207496c096e07596c097007996c097203396e096e07996e0) // quotient_program + mstore(add(payload, 0x2a00), 0x97007a96e0972035970097007b9700972039972097200d000b090000210397a0) // quotient_program + mstore(add(payload, 0x2a20), 0xb004010002150094a00594c00694e00795000895206a95406b95606c95800995) // quotient_program + mstore(add(payload, 0x2a40), 0xa00a95c00095e00596000696200796400896600996800a96a08296c08396e084) // quotient_program + mstore(add(payload, 0x2a60), 0x97008597200097c00597e00698000798200898400998600a988095400b95406b) // quotient_program + mstore(add(payload, 0x2a80), 0x95606c95808296c08396e08497008597201897401997800d9560956082956095) // quotient_program + mstore(add(payload, 0x2aa0), 0x8083956096c084956096e0859560970086956097200f9580958084958096c085) // quotient_program + mstore(add(payload, 0x2ac0), 0x958096e0869580970087958097201196c096c08696c096e08796c097008896c0) // quotient_program + mstore(add(payload, 0x2ae0), 0x97204696e096e08896e097008996e0972048970097008a970097204a97209720) // quotient_program + mstore(add(payload, 0x2b00), 0x0d000b090001210397a0b104010001150094a00594c00694e01b95001c95206a) // quotient_program + mstore(add(payload, 0x2b20), 0x95406b95606c95801d95a01e95c00095e00596000696201b96401c96601d9680) // quotient_program + mstore(add(payload, 0x2b40), 0x1e96a09b96c09c96e09d97009e97200097c00597e00698001b98201c98401d98) // quotient_program + mstore(add(payload, 0x2b60), 0x601e988095400b95406b95606c95809b96c09c96e09d97009e97202997400d95) // quotient_program + mstore(add(payload, 0x2b80), 0x6095609b956095809c956096c09d956096e09e956097009f9560972020958095) // quotient_program + mstore(add(payload, 0x2ba0), 0x809d958096c09e958096e09f95809700a0958097202296c096c09f96c096e0a0) // quotient_program + mstore(add(payload, 0x2bc0), 0x96c09700a196c097205696e096e0a196e09700a296e097205897009700a39700) // quotient_program + mstore(add(payload, 0x2be0), 0x97205a972097200d000b090001191f0000000000000000000000000000000000) // quotient_program // Fixed-column commitment 0, stored as one // EIP-2537 padded uncompressed G1 slot. mstore(add(payload, 0x2c00), 0x00000000000000000000000000000000055f7961345dce7ce57401dd993cc81a) // fixed_comms[0].x_hi diff --git a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol index 3660c9092..ee59832c1 100644 --- a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2Verifier.sol @@ -43,7 +43,7 @@ contract Halo2Verifier { // EXPECTED_VK_PAYLOAD_LENGTH. uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 4576; uint256 internal constant EXPECTED_VK_LENGTH = 4577; - uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0xf2c10777bfd922a3700fb18f2e9a2894dfa51dd920f6261bba71ed8b5363e495; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x233a1a8063ef1a9bfd30983d3034d3d90ab75ff6ac95c0e72d770bf08dcff51b; bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); // Solidity ABI calldata cursors. The generated verifier accepts exactly @@ -55,8 +55,8 @@ contract Halo2Verifier { uint256 internal constant INSTANCE_CPTR = 0x1164; // First general-purpose memory words reserved by the generated verifier. // RETURN_MPTR is a single word set to 1 on success. - uint256 internal constant TRANSCRIPT_MPTR = 0x80; - uint256 internal constant RETURN_MPTR = 0x80; + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; // ---------------------------------------------------------------------- // Verifying-key memory map. The VK header lives at VK_MPTR, followed @@ -64,84 +64,84 @@ contract Halo2Verifier { // runtime comes the challenge slots (challenge_mptr..) and the // per-stage scratch (theta_mptr..). // ---------------------------------------------------------------------- - uint256 internal constant VK_MPTR = 0x1980; - uint256 internal constant VK_DIGEST_MPTR = 0x1980; - uint256 internal constant NUM_INSTANCES_MPTR = 0x19a0; - uint256 internal constant K_MPTR = 0x19c0; - uint256 internal constant N_INV_MPTR = 0x19e0; - uint256 internal constant OMEGA_MPTR = 0x1a00; - uint256 internal constant OMEGA_INV_MPTR = 0x1a20; - uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x1a40; - uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x1a60; - uint256 internal constant ACC_OFFSET_MPTR = 0x1a80; - uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x1aa0; - uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x1ac0; - uint256 internal constant G1_BASE_MPTR = 0x1ae0; - uint256 internal constant G2_BASE_MPTR = 0x1b60; - uint256 internal constant NEG_S_G2_BASE_MPTR = 0x1c60; - - uint256 internal constant CHALLENGE_MPTR = 0x2b60; + uint256 internal constant VK_MPTR = 0x2900; + uint256 internal constant VK_DIGEST_MPTR = 0x2900; + uint256 internal constant NUM_INSTANCES_MPTR = 0x2920; + uint256 internal constant K_MPTR = 0x2940; + uint256 internal constant N_INV_MPTR = 0x2960; + uint256 internal constant OMEGA_MPTR = 0x2980; + uint256 internal constant OMEGA_INV_MPTR = 0x29a0; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x29c0; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x29e0; + uint256 internal constant ACC_OFFSET_MPTR = 0x2a00; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x2a20; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x2a40; + uint256 internal constant G1_BASE_MPTR = 0x2a60; + uint256 internal constant G2_BASE_MPTR = 0x2ae0; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x2be0; + + uint256 internal constant CHALLENGE_MPTR = 0x3ae0; // Challenge layout. Squeeze order in midnight-proofs: // user_phase challenges (variable count) // theta -> beta, gamma -> trash_challenge -> y -> x -> // x1, x2 -> x3 -> x4 - uint256 internal constant THETA_MPTR = 0x2b60; - uint256 internal constant BETA_MPTR = 0x2b80; - uint256 internal constant GAMMA_MPTR = 0x2ba0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x2bc0; - uint256 internal constant Y_MPTR = 0x2be0; - uint256 internal constant X_MPTR = 0x2c00; - uint256 internal constant X1_MPTR = 0x2c20; - uint256 internal constant X2_MPTR = 0x2c40; - uint256 internal constant X3_MPTR = 0x2c60; - uint256 internal constant X4_MPTR = 0x2c80; + uint256 internal constant THETA_MPTR = 0x3ae0; + uint256 internal constant BETA_MPTR = 0x3b00; + uint256 internal constant GAMMA_MPTR = 0x3b20; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x3b40; + uint256 internal constant Y_MPTR = 0x3b60; + uint256 internal constant X_MPTR = 0x3b80; + uint256 internal constant X1_MPTR = 0x3ba0; + uint256 internal constant X2_MPTR = 0x3bc0; + uint256 internal constant X3_MPTR = 0x3be0; + uint256 internal constant X4_MPTR = 0x3c00; // Batch-open commitments live in 4-word EIP-2537 padded slots. - uint256 internal constant F_COM_MPTR = 0x2ca0; - uint256 internal constant PI_MPTR = 0x2d20; + uint256 internal constant F_COM_MPTR = 0x3c20; + uint256 internal constant PI_MPTR = 0x3ca0; // Accumulator (KZG IVC). - uint256 internal constant ACC_LHS_MPTR = 0x2da0; - uint256 internal constant ACC_RHS_MPTR = 0x2e20; + uint256 internal constant ACC_LHS_MPTR = 0x3d20; + uint256 internal constant ACC_RHS_MPTR = 0x3da0; // Lagrange / linearization scratch. - uint256 internal constant X_N_MPTR = 0x2ea0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x2ec0; - uint256 internal constant L_LAST_MPTR = 0x2ee0; - uint256 internal constant L_BLIND_MPTR = 0x2f00; - uint256 internal constant L_0_MPTR = 0x2f20; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x2f40; + uint256 internal constant X_N_MPTR = 0x3e20; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x3e40; + uint256 internal constant L_LAST_MPTR = 0x3e60; + uint256 internal constant L_BLIND_MPTR = 0x3e80; + uint256 internal constant L_0_MPTR = 0x3ea0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x3ec0; // Legacy name: this is not h(x). It stores the expected opening // scalar for the linearized commitment, i.e. the negated y-batched // identity numerator reconstructed from the alleged evals at x. - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x2f60; - uint256 internal constant QUOTIENT_MPTR = 0x2f80; // 4 words - uint256 internal constant F_EVAL_MPTR = 0x3020; - uint256 internal constant V_MPTR = 0x3040; - uint256 internal constant FINAL_COM_MPTR = 0x3060; // 4 words - uint256 internal constant PAIRING_LHS_MPTR = 0x30e0; // 4 words - uint256 internal constant PAIRING_RHS_MPTR = 0x3160; // 4 words + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x3ee0; + uint256 internal constant QUOTIENT_MPTR = 0x3f00; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x3fa0; + uint256 internal constant V_MPTR = 0x3fc0; + uint256 internal constant FINAL_COM_MPTR = 0x3fe0; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x4060; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x40e0; // 4 words // Multi-prepare scratch (sized at codegen time). - uint256 internal constant ROT_POINTS_MPTR = 0x31e0; - uint256 internal constant X1_POWERS_MPTR = 0x3560; + uint256 internal constant ROT_POINTS_MPTR = 0x4160; + uint256 internal constant X1_POWERS_MPTR = 0x44e0; // Q_COM materialization is currently fused into the final MSM scratch, // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero // reserved capacity until a future emitter starts writing Q_COM_MPTR. - uint256 internal constant Q_COM_MPTR = 0x3d80; - uint256 internal constant Q_EVAL_SET_MPTR = 0x3d80; + uint256 internal constant Q_COM_MPTR = 0x4d00; + uint256 internal constant Q_EVAL_SET_MPTR = 0x4d00; // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals // block of the proof; we keep it as a memory slot for symmetry. - uint256 internal constant Q_EVAL_CPTR_MPTR = 0x4480; + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x5400; // Reserved 4-word slot for the G1 identity (point at infinity) in // EIP-2537 padded form. EVM memory is zero-initialised, and we // never write to this region, so the four `mload`s below produce // 0,0,0,0 which is exactly the identity encoding the EIP-2537 // ec_add / ec_mul precompiles accept. - uint256 internal constant G1_IDENTITY_MPTR = 0x4580; + uint256 internal constant G1_IDENTITY_MPTR = 0x5500; // Decoded polynomial-eval buffer (Optimisation H3). The off-chain // Solidity proof shim rewrites proof scalars into canonical BE words, @@ -149,11 +149,11 @@ contract Halo2Verifier { // side `evaluations` loop range-checks and spills that value here so // downstream eval references (gate evaluator + PCS q_eval Horner) // become 3-gas `mload(...)` instead of calldata reads. - uint256 internal constant REVERSED_EVALS_MPTR = 0x46e0; - uint256 internal constant SELECTOR_ACC_MPTR = 0x5660; - uint256 internal constant QUOTIENT_RETURN_MPTR = 0x80; - uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x5660; - uint256 internal constant TRACE_U256_MPTR = 0x7440; + uint256 internal constant REVERSED_EVALS_MPTR = 0x5660; + uint256 internal constant SELECTOR_ACC_MPTR = 0x65e0; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x65e0; + uint256 internal constant TRACE_U256_MPTR = 0x83c0; // ---------------------------------------------------------------------- // Per-category bases for EIP-2537 padded G1 commitments. The proof @@ -170,13 +170,13 @@ contract Halo2Verifier { // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans // ---------------------------------------------------------------------- - uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x4ce0; - uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x50e0; - uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x5160; - uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x52e0; - uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x5360; - uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x53e0; - uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x5460; + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x5c60; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x6060; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x60e0; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x6260; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x62e0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x6360; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x63e0; // BLS12-381 scalar-field modulus, used for transcript challenges and all // Halo2 verifier arithmetic. @@ -198,7 +198,7 @@ contract Halo2Verifier { function require_eip2537_precompiles() private view { assembly ("memory-safe") { // Scratch is reused for every runtime-prerequisite probe. - let scratch := 0x80 + let scratch := 0x1000 // MCOPY must be available because the verifier uses it for // proof-time point/scratch staging. Execute the opcode here so a @@ -222,11 +222,44 @@ contract Halo2Verifier { revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair // smoke call. - let msm_scratch := 0x5660 + let msm_scratch := 0x65e0 for { let off := 0 } lt(off, 0x1d60) { off := add(off, 0x20) } { mstore(add(msm_scratch, off), 0) } @@ -321,9 +354,15 @@ contract Halo2Verifier { // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } - let p := 0x1880 + let p := 0x2800 // EIP-198 modexp frame: // [base_len, exp_len, mod_len, base, exponent, modulus] mstore(add(p, 0x00), 0x20) // base len @@ -458,6 +497,13 @@ contract Halo2Verifier { // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -478,16 +524,34 @@ contract Halo2Verifier { // Forward pass: scratch stores prefix products up to, but not // including, the final element. `gp` becomes the total product. + // + // Match the single-element path: reject non-canonical words + // (x >= r) instead of letting mulmod reduce them silently, so + // accept/reject semantics do not depend on batch length. let gp_mptr := scratch_mptr let gp := mload(mptr_start) + if iszero(lt(gp, r)) { + ret := 0 + leave + } let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) {} { - gp := mulmod(gp, mload(mptr), r) + let x := mload(mptr) + if iszero(lt(x, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x, r) mstore(gp_mptr, gp) mptr := add(mptr, 0x20) gp_mptr := add(gp_mptr, 0x20) } - gp := mulmod(gp, mload(mptr), r) + let x_last := mload(mptr) + if iszero(lt(x_last, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x_last, r) // A zero total product means at least one denominator was // zero, so no batch inverse exists. if iszero(gp) { @@ -504,6 +568,12 @@ contract Halo2Verifier { mstore(add(gp_mptr, 0xa0), r) ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -528,21 +598,30 @@ contract Halo2Verifier { // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0300 + let scratch := 0x1220 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } @@ -574,7 +653,13 @@ contract Halo2Verifier { // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -1362,15 +1447,15 @@ contract Halo2Verifier { // q_const_mptr points to Fr constants used by the VM. // q_program_mptr points to the bytecode stream. // Constants are stored as consecutive 32-byte Fr words. - let q_const_mptr := 0x1d60 + let q_const_mptr := 0x2ce0 // Program bytes are also stored in the VK payload, packed into // 32-byte words by PackedProgramCodec. - let q_program_mptr := 0x1d80 + let q_program_mptr := 0x2d00 // Running Horner accumulator for fully evaluated identities. // After all identities, this is nu_y(x) for the `None` // identity group. // Initialize A = 0 before scanning the identity stream. - mstore(0x56e0, 0) + mstore(0x6660, 0) // Simple selectors are grouped into separate linearization // buckets. They start at zero for every proof. // q_sel_zero_off walks selector bucket byte offsets. @@ -1385,12 +1470,19 @@ contract Halo2Verifier { { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0x66a0, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 22) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) // Store y^i at selector_power_mptr + 32*i. - mstore(add(0x5720, shl(5, q_y_power_i)), q_y_power) + mstore(add(0x66a0, shl(5, q_y_power_i)), q_y_power) } } @@ -1399,102 +1491,102 @@ contract Halo2Verifier { // VM/native identities, so they occupy the same y-batch order. { let var0 := 0x1 - let f_3 := mload(0x4980) - let f_4 := mload(0x4880) - let a_0 := mload(0x4700) + let f_3 := mload(0x5900) + let f_4 := mload(0x5800) + let a_0 := mload(0x5680) let var1 := mulmod(f_4, a_0, r) let var2 := addmod(f_3, var1, r) - let f_5 := mload(0x48a0) - let a_1 := mload(0x4720) + let f_5 := mload(0x5820) + let a_1 := mload(0x56a0) let var3 := mulmod(f_5, a_1, r) let var4 := addmod(var2, var3, r) - let f_6 := mload(0x48c0) - let a_2 := mload(0x4740) + let f_6 := mload(0x5840) + let a_2 := mload(0x56c0) let var5 := mulmod(f_6, a_2, r) let var6 := addmod(var4, var5, r) - let f_7 := mload(0x48e0) - let a_3 := mload(0x4760) + let f_7 := mload(0x5860) + let a_3 := mload(0x56e0) let var7 := mulmod(f_7, a_3, r) let var8 := addmod(var6, var7, r) - let f_8 := mload(0x4900) - let a_4 := mload(0x4780) + let f_8 := mload(0x5880) + let a_4 := mload(0x5700) let var9 := mulmod(f_8, a_4, r) let var10 := addmod(var8, var9, r) - let f_0 := mload(0x4920) - let a_0_next_1 := mload(0x47a0) + let f_0 := mload(0x58a0) + let a_0_next_1 := mload(0x5720) let var11 := mulmod(f_0, a_0_next_1, r) let var12 := addmod(var10, var11, r) - let f_1 := mload(0x4940) + let f_1 := mload(0x58c0) let var13 := mulmod(f_1, a_0, r) let var14 := mulmod(var13, a_1, r) let var15 := addmod(var12, var14, r) - let f_2 := mload(0x4960) + let f_2 := mload(0x58e0) let var16 := mulmod(f_2, a_0, r) let var17 := mulmod(var16, a_2, r) let var18 := addmod(var15, var17, r) let var19 := mulmod(var0, var18, r) - mstore(0x59e0, var19) + mstore(0x6960, var19) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } { let var0 := 0x1 - let a_1 := mload(0x4720) - let a_2 := mload(0x4740) + let a_1 := mload(0x56a0) + let a_2 := mload(0x56c0) let var1 := addmod(a_1, a_2, r) - let a_3 := mload(0x4760) + let a_3 := mload(0x56e0) let var2 := addmod(0, sub(r, a_3), r) let var3 := addmod(var1, var2, r) - let a_4 := mload(0x4780) + let a_4 := mload(0x5700) let var4 := addmod(0, sub(r, a_4), r) let var5 := addmod(var3, var4, r) let var6 := mulmod(var0, var5, r) - mstore(0x59e0, var6) + mstore(0x6960, var6) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } { let var0 := 0x1 - let a_0 := mload(0x4700) - let f_4 := mload(0x4880) + let a_0 := mload(0x5680) + let f_4 := mload(0x5800) let var1 := addmod(a_0, f_4, r) - let a_0_next_1 := mload(0x47a0) + let a_0_next_1 := mload(0x5720) let var2 := addmod(0, sub(r, a_0_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x59e0, var4) + mstore(0x6960, var4) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } { let var0 := 0x1 - let a_1 := mload(0x4720) - let f_5 := mload(0x48a0) + let a_1 := mload(0x56a0) + let f_5 := mload(0x5820) let var1 := addmod(a_1, f_5, r) - let a_1_next_1 := mload(0x47c0) + let a_1_next_1 := mload(0x5740) let var2 := addmod(0, sub(r, a_1_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x59e0, var4) + mstore(0x6960, var4) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5720, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x66a0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } // VM registers: @@ -1514,7 +1606,7 @@ contract Halo2Verifier { // q_end is an exclusive byte pointer for the VM loop. let q_end := add(q_program_mptr, 0x58) // q_sp starts at the first free stack word. - let q_sp := 0x59e0 + let q_sp := 0x6960 // q_top is meaningless until q_has_top is set. let q_top := 0 // q_has_top = 0 means the VM stack is empty. @@ -1614,69 +1706,69 @@ contract Halo2Verifier { // stack. The Rust memory planner must reserve enough // words for structured_permutation_scratch_words(meta) // whenever this opcode can appear. - q_sp := 0x59e0 + q_sp := 0x6960 // The generated lines below call the same fold snippets // used by interpreted expressions, so trace IDs and // y-batch positions remain contiguous. { let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 - let q_perm_vals := 0x59e0 - let q_perm_sigmas := 0x5ae0 - let q_perm_z_cur := 0x5be0 - let q_perm_z_next := 0x5c40 - let q_perm_z_last := 0x5ca0 - let q_perm_delta_base_ptr := 0x5ce0 + let q_perm_vals := 0x6960 + let q_perm_sigmas := 0x6a60 + let q_perm_z_cur := 0x6b60 + let q_perm_z_next := 0x6bc0 + let q_perm_z_last := 0x6c20 + let q_perm_delta_base_ptr := 0x6c60 let q_perm_num_cols := 8 let q_perm_num_sets := 3 let q_perm_chunk_len := 3 let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 - mstore(add(q_perm_vals, 0x0), mload(0x4860)) + mstore(add(q_perm_vals, 0x0), mload(0x57e0)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x4700, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x5680, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0xc0), mload(0x46e0)) + mstore(add(q_perm_vals, 0xc0), mload(0x5660)) mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) { for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 8) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off - mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x4a40, q_perm_sigma_load_src_off))) + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x59c0, q_perm_sigma_load_src_off))) } } { for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 3) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) - mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x4b40, q_perm_z_cur_load_src_off))) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x5ac0, q_perm_z_cur_load_src_off))) } } { for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 3) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) - mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x4b60, q_perm_z_next_load_src_off))) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x5ae0, q_perm_z_next_load_src_off))) } } - mstore(add(q_perm_z_last, 0x0), mload(0x4b80)) - mstore(add(q_perm_z_last, 0x20), mload(0x4be0)) + mstore(add(q_perm_z_last, 0x0), mload(0x5b00)) + mstore(add(q_perm_z_last, 0x20), mload(0x5b60)) let q_perm_eval := 0 q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_perm_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_perm_eval, r)) let q_perm_zn := mload(add(q_perm_z_cur, 0x40)) q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_perm_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_perm_eval, r)) for { let q_perm_i := 1 } lt(q_perm_i, 3) { q_perm_i := add(q_perm_i, 1) } { let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_perm_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_perm_eval, r)) } mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) for { let q_perm_set := 0 } lt(q_perm_set, 3) { q_perm_set := add(q_perm_set, 1) } { @@ -1695,8 +1787,8 @@ contract Halo2Verifier { q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) } q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_perm_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_perm_eval, r)) mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) } } @@ -1717,13 +1809,13 @@ contract Halo2Verifier { // f+beta/prefix/suffix scratch rather than as a // conventional VM stack. The Rust memory planner must // reserve structured_lookup_scratch_words(meta). - q_sp := 0x59e0 + q_sp := 0x6960 // Generated LogUp code follows the same y-batch order // as the Rust identity stream. { - let q_lookup_f := 0x59e0 - let q_lookup_prefix := 0x5a00 - let q_lookup_suffix := 0x5a20 + let q_lookup_f := 0x6960 + let q_lookup_prefix := 0x6980 + let q_lookup_suffix := 0x69a0 let q_lookup_l0 := mload(L_0_MPTR) let q_lookup_llast := mload(L_LAST_MPTR) let q_lookup_lblind := mload(L_BLIND_MPTR) @@ -1733,33 +1825,33 @@ contract Halo2Verifier { let q_lookup_theta := mload(THETA_MPTR) { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x4c80), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x5c00), r) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_lookup_eval, r)) } { - let f_10 := mload(0x49a0) + let f_10 := mload(0x5920) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) - let a_1 := mload(0x4720) + let a_1 := mload(0x56a0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_1, r) - let q_lookup_eval := addmod(mulmod(mload(0x4c60), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x5be0), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x4c60) - let f_16 := mload(0x4a00) - let f_11 := mload(0x49c0) + let q_lookup_sum_h := mload(0x5be0) + let f_16 := mload(0x5980) + let f_11 := mload(0x5940) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) - let f_12 := mload(0x49e0) + let f_12 := mload(0x5960) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) let q_lookup_s_sum_h := mulmod(f_16, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x4ca0), sub(r, addmod(mload(0x4c80), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x5c20), sub(r, addmod(mload(0x5c00), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x4c40), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x5bc0), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_lookup_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_lookup_eval, r)) } } } @@ -1780,124 +1872,124 @@ contract Halo2Verifier { // interpreter stack before dispatching. q_top := 0 q_has_top := 0 - q_sp := 0x59e0 + q_sp := 0x6960 // Native identity sub-cases are generated from selected heavy gate identities. switch q_native_idx case 0 { { let var0 := 0x1 - let f_0 := mload(0x4920) - let a_0_next_1 := mload(0x47a0) + let f_0 := mload(0x58a0) + let a_0_next_1 := mload(0x5720) let var1 := addmod(0, sub(r, a_0_next_1), r) let var2 := addmod(f_0, var1, r) let var3 := 0x1b8114c381b922fd5d6d241210e2d8a68ad5744053ba9e776118de4107b51ace - let a_0 := mload(0x4700) + let a_0 := mload(0x5680) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x4760) + let a_3 := mload(0x56e0) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0x3df32e4cc4cb2ed20e5d21899cf5331775990ccaec4c09b4e3717213fcc0d763 - let a_1 := mload(0x4720) + let a_1 := mload(0x56a0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x4780) + let a_4 := mload(0x5700) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_2 := mload(0x4740) + let a_2 := mload(0x56c0) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x4800) + let a_5 := mload(0x5780) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x59e0, var18) + mstore(0x6960, var18) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5720, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x66a0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } } case 1 { { let var0 := 0x1 - let f_1 := mload(0x4940) - let a_1_next_1 := mload(0x47c0) + let f_1 := mload(0x58c0) + let a_1_next_1 := mload(0x5740) let var1 := addmod(0, sub(r, a_1_next_1), r) let var2 := addmod(f_1, var1, r) let var3 := 0x404d21073985d14e432a4ad76d3fae06ca74314b950fe7b1d7f501cd31a8b374 - let a_0 := mload(0x4700) + let a_0 := mload(0x5680) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x4760) + let a_3 := mload(0x56e0) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0xb2cc8704264c6bd81bc620e9e524d4b73e9b2317679422ff7fa1603955649f1 - let a_1 := mload(0x4720) + let a_1 := mload(0x56a0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x4780) + let a_4 := mload(0x5700) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0xfdf664da55059fa5a9388c641035d496d0bb519834348b4e2a8fc8c637f1a1f - let a_2 := mload(0x4740) + let a_2 := mload(0x56c0) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x4800) + let a_5 := mload(0x5780) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x59e0, var18) + mstore(0x6960, var18) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5720, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x66a0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } } case 2 { { let var0 := 0x1 - let f_2 := mload(0x4960) - let a_2_next_1 := mload(0x47e0) + let f_2 := mload(0x58e0) + let a_2_next_1 := mload(0x5760) let var1 := addmod(0, sub(r, a_2_next_1), r) let var2 := addmod(f_2, var1, r) let var3 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 - let a_0 := mload(0x4700) + let a_0 := mload(0x5680) let var4 := mulmod(a_0, a_0, r) - let a_3 := mload(0x4760) + let a_3 := mload(0x56e0) let var5 := mulmod(var4, a_3, r) let var6 := mulmod(var3, var5, r) let var7 := addmod(var2, var6, r) let var8 := 0x6bd72f9cfc53af9d931896e77ea5c61244cb6d5fae8954f37dc7b9002f5aa78a - let a_1 := mload(0x4720) + let a_1 := mload(0x56a0) let var9 := mulmod(a_1, a_1, r) - let a_4 := mload(0x4780) + let a_4 := mload(0x5700) let var10 := mulmod(var9, a_4, r) let var11 := mulmod(var8, var10, r) let var12 := addmod(var7, var11, r) let var13 := 0x4997c5aa3a5fa07bcaf880a9054bef831effbd9cd58e46d9bb4fb88ef99de0db - let a_2 := mload(0x4740) + let a_2 := mload(0x56c0) let var14 := mulmod(a_2, a_2, r) - let a_5 := mload(0x4800) + let a_5 := mload(0x5780) let var15 := mulmod(var14, a_5, r) let var16 := mulmod(var13, var15, r) let var17 := addmod(var12, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x59e0, var18) + mstore(0x6960, var18) } - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5720, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x59e0), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x66a0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x6960), r)) } } default { revert(0, 0) } @@ -1921,14 +2013,14 @@ contract Halo2Verifier { // The global fully-evaluated accumulator is still // multiplied by y so later main identities land at the // same y powers as Rust's reverse fold. - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) let q_sel_acc := mload(q_target_ptr) if q_sel_gap { // Selector buckets are sparse in the global // identity stream. Precomputed y^gap advances only // this selector's local accumulator. - q_sel_acc := mulmod(q_sel_acc, mload(add(0x5720, shl(5, q_sel_gap))), r) + q_sel_acc := mulmod(q_sel_acc, mload(add(0x66a0, shl(5, q_sel_gap))), r) } mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) } @@ -1943,6 +2035,12 @@ contract Halo2Verifier { // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0x6960)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -1954,52 +2052,52 @@ contract Halo2Verifier { { let q_trash_tau := mload(TRASH_CHALLENGE_MPTR) { - let f_0 := mload(0x4920) - let a_0_next_1 := mload(0x47a0) + let f_0 := mload(0x58a0) + let a_0_next_1 := mload(0x5720) let var0 := 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000 let var1 := mulmod(a_0_next_1, var0, r) let var2 := addmod(f_0, var1, r) let var3 := 0x590ba402032e82eb1f660ef09796c5686345a5054ed96dae8e2d233633788771 - let a_0 := mload(0x4700) + let a_0 := mload(0x5680) let var4 := mulmod(var3, a_0, r) let var5 := addmod(var2, var4, r) let var6 := 0x52f789e4afc3801f7411102ee2f47cc5954a744e71cac98e75ea962a55a0a76f - let a_1 := mload(0x4720) + let a_1 := mload(0x56a0) let var7 := mulmod(var6, a_1, r) let var8 := addmod(var5, var7, r) let var9 := 0x3509dd2fe3aac0080783557fec090fb1cb4b2b0901253c55282024331d1fe1a8 - let a_2 := mload(0x4740) + let a_2 := mload(0x56c0) let var10 := q_pow5(a_2) let var11 := mulmod(var9, var10, r) let var12 := addmod(var8, var11, r) let var13 := 0x333f8046ece5579cbd6872449c57f2703dfc8864cfadc06d587ff104a0d0c1f2 - let a_3 := mload(0x4760) + let a_3 := mload(0x56e0) let var14 := q_pow5(a_3) let var15 := mulmod(var13, var14, r) let var16 := addmod(var12, var15, r) let var17 := 0x412c98232b6ab8a47aa76ee814ef7ec6261987c9802f2cfc490e007951a60ca5 - let a_4 := mload(0x4780) + let a_4 := mload(0x5700) let var18 := q_pow5(a_4) let var19 := mulmod(var17, var18, r) let var20 := addmod(var16, var19, r) let var21 := 0x53fded36d490ba6b05a5d10fd99ffe5456baec6a6a8753199d5ebdc33c99790e - let a_5 := mload(0x4800) + let a_5 := mload(0x5780) let var22 := q_pow5(a_5) let var23 := mulmod(var21, var22, r) let var24 := addmod(var20, var23, r) let var25 := 0x6ccb1c7d87f3c12a2bde4e68ac7f1e8b03481ba15d7f88f9a7f9b8310dd6d34 - let a_6 := mload(0x4820) + let a_6 := mload(0x57a0) let var26 := q_pow5(a_6) let var27 := mulmod(var25, var26, r) let var28 := addmod(var24, var27, r) let var29 := 0x3f05c4df7a6664dabe258779bf548eb4007f33601591080b3ecd34aea0e1edc1 - let a_7 := mload(0x4840) + let a_7 := mload(0x57c0) let var30 := q_pow5(a_7) let var31 := mulmod(var29, var30, r) let var32 := addmod(var28, var31, r) let var33 := addmod(mulmod(0, q_trash_tau, r), var32, r) - let f_1 := mload(0x4940) - let a_1_next_1 := mload(0x47c0) + let f_1 := mload(0x58c0) + let a_1_next_1 := mload(0x5740) let var34 := mulmod(a_1_next_1, var0, r) let var35 := addmod(f_1, var34, r) let var36 := 0x5b1fc262a28cbb8bf75d9b1a6edaa74591ec24cd9a209512213cec3a3c0f1a5d @@ -2027,7 +2125,7 @@ contract Halo2Verifier { let var58 := mulmod(var57, var30, r) let var59 := addmod(var56, var58, r) let var60 := addmod(mulmod(var33, q_trash_tau, r), var59, r) - let f_2 := mload(0x4960) + let f_2 := mload(0x58e0) let var61 := mulmod(a_3, var0, r) let var62 := addmod(f_2, var61, r) let var63 := 0x5e1d3dbecda6214343e24a47f45c5d033197ad01b65a730af95dc57e90c49140 @@ -2040,7 +2138,7 @@ contract Halo2Verifier { let var70 := mulmod(var69, var10, r) let var71 := addmod(var68, var70, r) let var72 := addmod(mulmod(var60, q_trash_tau, r), var71, r) - let f_3 := mload(0x4980) + let f_3 := mload(0x5900) let var73 := mulmod(a_4, var0, r) let var74 := addmod(f_3, var73, r) let var75 := 0x222e83e70453dfee19b402e9fa8dfe2c4987b034d0be3ceb478b3022e97934c1 @@ -2055,7 +2153,7 @@ contract Halo2Verifier { let var84 := mulmod(var69, var14, r) let var85 := addmod(var83, var84, r) let var86 := addmod(mulmod(var72, q_trash_tau, r), var85, r) - let f_4 := mload(0x4880) + let f_4 := mload(0x5800) let var87 := mulmod(a_5, var0, r) let var88 := addmod(f_4, var87, r) let var89 := 0x726df1506749848155630b86ae25a82b281ecd050fe3a52d85a181fa87202e4b @@ -2072,7 +2170,7 @@ contract Halo2Verifier { let var100 := mulmod(var69, var18, r) let var101 := addmod(var99, var100, r) let var102 := addmod(mulmod(var86, q_trash_tau, r), var101, r) - let f_5 := mload(0x48a0) + let f_5 := mload(0x5820) let var103 := mulmod(a_6, var0, r) let var104 := addmod(f_5, var103, r) let var105 := 0x2f5908b169c6cf1bd26dcf0f9e5105481f5164f3ece0582bf3098312167751a7 @@ -2091,7 +2189,7 @@ contract Halo2Verifier { let var118 := mulmod(var69, var22, r) let var119 := addmod(var117, var118, r) let var120 := addmod(mulmod(var102, q_trash_tau, r), var119, r) - let f_6 := mload(0x48c0) + let f_6 := mload(0x5840) let var121 := mulmod(a_7, var0, r) let var122 := addmod(f_6, var121, r) let var123 := 0x6d05a41959f539a7fc9ec0972ea1e3dbb6fc67dd51daf3414f7fbbb091c7274a @@ -2112,8 +2210,8 @@ contract Halo2Verifier { let var138 := mulmod(var69, var26, r) let var139 := addmod(var137, var138, r) let var140 := addmod(mulmod(var120, q_trash_tau, r), var139, r) - let f_7 := mload(0x48e0) - let a_2_next_1 := mload(0x47e0) + let f_7 := mload(0x5860) + let a_2_next_1 := mload(0x5760) let var141 := mulmod(a_2_next_1, var0, r) let var142 := addmod(f_7, var141, r) let var143 := 0x70d8f2a733a64d650faccc9b1c2a766a9544bb3ff1a11ee73cb43947ef386633 @@ -2136,12 +2234,12 @@ contract Halo2Verifier { let var160 := mulmod(var69, var30, r) let var161 := addmod(var159, var160, r) let var162 := addmod(mulmod(var140, q_trash_tau, r), var161, r) - let f_18 := mload(0x4a20) + let f_18 := mload(0x59a0) let q_trash_one_minus_selector := addmod(1, sub(r, f_18), r) - let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0x4cc0), r) + let q_trash_scaled := mulmod(q_trash_one_minus_selector, mload(0x5c40), r) let q_trash_eval := addmod(var162, sub(r, q_trash_scaled), r) - mstore(0x56e0, mulmod(mload(0x56e0), y, r)) - mstore(0x56e0, addmod(mload(0x56e0), q_trash_eval, r)) + mstore(0x6660, mulmod(mload(0x6660), y, r)) + mstore(0x6660, addmod(mload(0x6660), q_trash_eval, r)) } } // Finish selector buckets by applying the codegen-known tail @@ -2153,25 +2251,25 @@ contract Halo2Verifier { // selector commitment in the linearized MSM. { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5720, 0x02a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x66a0, 0x02a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5720, 0x0280)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x66a0, 0x0280)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5720, 0x0220)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x66a0, 0x0220)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5720, 0x0160)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x66a0, 0x0160)), r)) } // Fully evaluated identities are the constant-polynomial side // of the linearization query. Rust subtracts that grouped // scalar into expected_eval, so Solidity stores -nu_y(x). - let linearization_expected_eval := addmod(0, sub(r, mload(0x56e0)), r) + let linearization_expected_eval := addmod(0, sub(r, mload(0x6660)), r) mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) pop(y) } @@ -2273,42 +2371,42 @@ contract Halo2Verifier { { // q_eval_set[0]: 33 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x56e0, 0x4760) - mstore(0x5700, 0x4780) - mstore(0x5720, 0x4800) - mstore(0x5740, 0x4820) - mstore(0x5760, 0x4840) - mstore(0x5780, 0x46e0) - mstore(0x57a0, 0x4c40) - mstore(0x57c0, 0x4c60) - mstore(0x57e0, 0x4cc0) - mstore(0x5800, 0x4860) - mstore(0x5820, 0x4880) - mstore(0x5840, 0x48a0) - mstore(0x5860, 0x48c0) - mstore(0x5880, 0x48e0) - mstore(0x58a0, 0x4900) - mstore(0x58c0, 0x4920) - mstore(0x58e0, 0x4940) - mstore(0x5900, 0x4960) - mstore(0x5920, 0x4980) - mstore(0x5940, 0x49a0) - mstore(0x5960, 0x49c0) - mstore(0x5980, 0x49e0) - mstore(0x59a0, 0x4a00) - mstore(0x59c0, 0x4a20) - mstore(0x59e0, 0x4a40) - mstore(0x5a00, 0x4a60) - mstore(0x5a20, 0x4a80) - mstore(0x5a40, 0x4aa0) - mstore(0x5a60, 0x4ac0) - mstore(0x5a80, 0x4ae0) - mstore(0x5aa0, 0x4b00) - mstore(0x5ac0, 0x4b20) - mstore(0x5ae0, QUOTIENT_EVAL_MPTR) - let q_eval_set_0 := mload(0x4760) + mstore(0x6660, 0x56e0) + mstore(0x6680, 0x5700) + mstore(0x66a0, 0x5780) + mstore(0x66c0, 0x57a0) + mstore(0x66e0, 0x57c0) + mstore(0x6700, 0x5660) + mstore(0x6720, 0x5bc0) + mstore(0x6740, 0x5be0) + mstore(0x6760, 0x5c40) + mstore(0x6780, 0x57e0) + mstore(0x67a0, 0x5800) + mstore(0x67c0, 0x5820) + mstore(0x67e0, 0x5840) + mstore(0x6800, 0x5860) + mstore(0x6820, 0x5880) + mstore(0x6840, 0x58a0) + mstore(0x6860, 0x58c0) + mstore(0x6880, 0x58e0) + mstore(0x68a0, 0x5900) + mstore(0x68c0, 0x5920) + mstore(0x68e0, 0x5940) + mstore(0x6900, 0x5960) + mstore(0x6920, 0x5980) + mstore(0x6940, 0x59a0) + mstore(0x6960, 0x59c0) + mstore(0x6980, 0x59e0) + mstore(0x69a0, 0x5a00) + mstore(0x69c0, 0x5a20) + mstore(0x69e0, 0x5a40) + mstore(0x6a00, 0x5a60) + mstore(0x6a20, 0x5a80) + mstore(0x6a40, 0x5aa0) + mstore(0x6a60, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x56e0) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x56e0, 0x20) + let eval_p := add(0x6660, 0x20) for { let i := 1 } lt(i, 0x21) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2323,20 +2421,20 @@ contract Halo2Verifier { { // q_eval_set[1]: 5 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x56e0, 0x4700) - mstore(0x5700, 0x47a0) - mstore(0x5720, 0x4720) - mstore(0x5740, 0x47c0) - mstore(0x5760, 0x4740) - mstore(0x5780, 0x47e0) - mstore(0x57a0, 0x4c00) - mstore(0x57c0, 0x4c20) - mstore(0x57e0, 0x4c80) - mstore(0x5800, 0x4ca0) - let q_eval_set_0 := mload(0x4700) - let q_eval_set_1 := mload(0x47a0) + mstore(0x6660, 0x5680) + mstore(0x6680, 0x5720) + mstore(0x66a0, 0x56a0) + mstore(0x66c0, 0x5740) + mstore(0x66e0, 0x56c0) + mstore(0x6700, 0x5760) + mstore(0x6720, 0x5b80) + mstore(0x6740, 0x5ba0) + mstore(0x6760, 0x5c00) + mstore(0x6780, 0x5c20) + let q_eval_set_0 := mload(0x5680) + let q_eval_set_1 := mload(0x5720) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x56e0, 0x40) + let eval_p := add(0x6660, 0x40) for { let i := 1 } lt(i, 0x5) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2352,12 +2450,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[2]: 2 commitment(s) - let q_eval_set_0 := mload(0x4b40) - let q_eval_set_1 := mload(0x4b60) - let q_eval_set_2 := mload(0x4b80) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x4ba0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x4bc0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x4be0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + let q_eval_set_0 := mload(0x5ac0) + let q_eval_set_1 := mload(0x5ae0) + let q_eval_set_2 := mload(0x5b00) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x5b20), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x5b40), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x5b60), mload(add(X1_POWERS_MPTR, 0x20)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x60), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x80), q_eval_set_1) mstore(add(Q_EVAL_SET_MPTR, 0xa0), q_eval_set_2) @@ -2477,107 +2575,107 @@ contract Halo2Verifier { v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), x4_pow_1, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_3, r), r) - mcopy(0x56e0, 0x4e60, 0x80) - mstore(0x5760, 1) - mcopy(0x5780, 0x4ee0, 0x80) - mstore(0x5800, mload(add(X1_POWERS_MPTR, 0x20))) - mcopy(0x5820, 0x4f60, 0x80) - mstore(0x58a0, mload(add(X1_POWERS_MPTR, 0x40))) - mcopy(0x58c0, 0x4fe0, 0x80) - mstore(0x5940, mload(add(X1_POWERS_MPTR, 0x60))) - mcopy(0x5960, 0x5060, 0x80) - mstore(0x59e0, mload(add(X1_POWERS_MPTR, 0x80))) - mcopy(0x5a00, 0x50e0, 0x80) - mstore(0x5a80, mload(add(X1_POWERS_MPTR, 0xc0))) - mcopy(0x5aa0, 0x52e0, 0x80) - mstore(0x5b20, mload(add(X1_POWERS_MPTR, 0xe0))) - mcopy(0x5b40, 0x53e0, 0x80) - mstore(0x5bc0, mload(add(X1_POWERS_MPTR, 0x100))) - mcopy(0x5be0, 0x2260, 0x80) - mstore(0x5c60, mload(add(X1_POWERS_MPTR, 0x120))) - mcopy(0x5c80, 0x1fe0, 0x80) - mstore(0x5d00, mload(add(X1_POWERS_MPTR, 0x140))) - mcopy(0x5d20, 0x2060, 0x80) - mstore(0x5da0, mload(add(X1_POWERS_MPTR, 0x160))) - mcopy(0x5dc0, 0x20e0, 0x80) - mstore(0x5e40, mload(add(X1_POWERS_MPTR, 0x180))) - mcopy(0x5e60, 0x2160, 0x80) - mstore(0x5ee0, mload(add(X1_POWERS_MPTR, 0x1a0))) - mcopy(0x5f00, 0x21e0, 0x80) - mstore(0x5f80, mload(add(X1_POWERS_MPTR, 0x1c0))) - mcopy(0x5fa0, 0x1de0, 0x80) - mstore(0x6020, mload(add(X1_POWERS_MPTR, 0x1e0))) - mcopy(0x6040, 0x1e60, 0x80) - mstore(0x60c0, mload(add(X1_POWERS_MPTR, 0x200))) - mcopy(0x60e0, 0x1ee0, 0x80) - mstore(0x6160, mload(add(X1_POWERS_MPTR, 0x220))) - mcopy(0x6180, 0x1f60, 0x80) - mstore(0x6200, mload(add(X1_POWERS_MPTR, 0x240))) - mcopy(0x6220, 0x22e0, 0x80) - mstore(0x62a0, mload(add(X1_POWERS_MPTR, 0x260))) - mcopy(0x62c0, 0x2360, 0x80) - mstore(0x6340, mload(add(X1_POWERS_MPTR, 0x280))) - mcopy(0x6360, 0x23e0, 0x80) - mstore(0x63e0, mload(add(X1_POWERS_MPTR, 0x2a0))) - mcopy(0x6400, 0x25e0, 0x80) - mstore(0x6480, mload(add(X1_POWERS_MPTR, 0x2c0))) - mcopy(0x64a0, 0x26e0, 0x80) - mstore(0x6520, mload(add(X1_POWERS_MPTR, 0x2e0))) - mcopy(0x6540, 0x2760, 0x80) - mstore(0x65c0, mload(add(X1_POWERS_MPTR, 0x300))) - mcopy(0x65e0, 0x27e0, 0x80) - mstore(0x6660, mload(add(X1_POWERS_MPTR, 0x320))) - mcopy(0x6680, 0x2860, 0x80) - mstore(0x6700, mload(add(X1_POWERS_MPTR, 0x340))) - mcopy(0x6720, 0x28e0, 0x80) - mstore(0x67a0, mload(add(X1_POWERS_MPTR, 0x360))) - mcopy(0x67c0, 0x2960, 0x80) - mstore(0x6840, mload(add(X1_POWERS_MPTR, 0x380))) - mcopy(0x6860, 0x29e0, 0x80) - mstore(0x68e0, mload(add(X1_POWERS_MPTR, 0x3a0))) - mcopy(0x6900, 0x2a60, 0x80) - mstore(0x6980, mload(add(X1_POWERS_MPTR, 0x3c0))) - mcopy(0x69a0, 0x2ae0, 0x80) - mstore(0x6a20, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0x6660, 0x5de0, 0x80) + mstore(0x66e0, 1) + mcopy(0x6700, 0x5e60, 0x80) + mstore(0x6780, mload(add(X1_POWERS_MPTR, 0x20))) + mcopy(0x67a0, 0x5ee0, 0x80) + mstore(0x6820, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0x6840, 0x5f60, 0x80) + mstore(0x68c0, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0x68e0, 0x5fe0, 0x80) + mstore(0x6960, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0x6980, 0x6060, 0x80) + mstore(0x6a00, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0x6a20, 0x6260, 0x80) + mstore(0x6aa0, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0x6ac0, 0x6360, 0x80) + mstore(0x6b40, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0x6b60, 0x31e0, 0x80) + mstore(0x6be0, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0x6c00, 0x2f60, 0x80) + mstore(0x6c80, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0x6ca0, 0x2fe0, 0x80) + mstore(0x6d20, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0x6d40, 0x3060, 0x80) + mstore(0x6dc0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0x6de0, 0x30e0, 0x80) + mstore(0x6e60, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0x6e80, 0x3160, 0x80) + mstore(0x6f00, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0x6f20, 0x2d60, 0x80) + mstore(0x6fa0, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0x6fc0, 0x2de0, 0x80) + mstore(0x7040, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0x7060, 0x2e60, 0x80) + mstore(0x70e0, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0x7100, 0x2ee0, 0x80) + mstore(0x7180, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0x71a0, 0x3260, 0x80) + mstore(0x7220, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0x7240, 0x32e0, 0x80) + mstore(0x72c0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0x72e0, 0x3360, 0x80) + mstore(0x7360, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0x7380, 0x3560, 0x80) + mstore(0x7400, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0x7420, 0x3660, 0x80) + mstore(0x74a0, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0x74c0, 0x36e0, 0x80) + mstore(0x7540, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0x7560, 0x3760, 0x80) + mstore(0x75e0, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0x7600, 0x37e0, 0x80) + mstore(0x7680, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0x76a0, 0x3860, 0x80) + mstore(0x7720, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0x7740, 0x38e0, 0x80) + mstore(0x77c0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0x77e0, 0x3960, 0x80) + mstore(0x7860, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0x7880, 0x39e0, 0x80) + mstore(0x7900, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0x7920, 0x3a60, 0x80) + mstore(0x79a0, mload(add(X1_POWERS_MPTR, 0x3e0))) let lin_query_scalar_31 := mload(add(X1_POWERS_MPTR, 0x400)) let lin_cur_scalar_31 := mulmod(lin_query_scalar_31, lin_one_minus_x_n, r) - mcopy(0x6a40, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) - mstore(0x6ac0, lin_cur_scalar_31) + mcopy(0x79c0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0x7a40, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x6ae0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) - mstore(0x6b60, lin_cur_scalar_31) + mcopy(0x7a60, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0x7ae0, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x6b80, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) - mstore(0x6c00, lin_cur_scalar_31) + mcopy(0x7b00, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0x7b80, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x6c20, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) - mstore(0x6ca0, lin_cur_scalar_31) - mcopy(0x6cc0, 0x2460, 0x80) - mstore(0x6d40, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) - mcopy(0x6d60, 0x24e0, 0x80) - mstore(0x6de0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) - mcopy(0x6e00, 0x2560, 0x80) - mstore(0x6e80, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) - mcopy(0x6ea0, 0x2660, 0x80) - mstore(0x6f20, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) - mcopy(0x6f40, 0x4ce0, 0x80) - mstore(0x6fc0, x4_pow_1) - mcopy(0x6fe0, 0x4d60, 0x80) - mstore(0x7060, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) - mcopy(0x7080, 0x4de0, 0x80) - mstore(0x7100, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) - mcopy(0x7120, 0x5260, 0x80) - mstore(0x71a0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_1, r)) - mcopy(0x71c0, 0x5360, 0x80) - mstore(0x7240, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_1, r)) - mcopy(0x7260, 0x5160, 0x80) - mstore(0x72e0, x4_pow_2) - mcopy(0x7300, 0x51e0, 0x80) - mstore(0x7380, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) - mcopy(0x73a0, F_COM_MPTR, 0x80) - mstore(0x7420, x4_pow_3) + mcopy(0x7ba0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0x7c20, lin_cur_scalar_31) + mcopy(0x7c40, 0x33e0, 0x80) + mstore(0x7cc0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0x7ce0, 0x3460, 0x80) + mstore(0x7d60, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0x7d80, 0x34e0, 0x80) + mstore(0x7e00, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0x7e20, 0x35e0, 0x80) + mstore(0x7ea0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0x7ec0, 0x5c60, 0x80) + mstore(0x7f40, x4_pow_1) + mcopy(0x7f60, 0x5ce0, 0x80) + mstore(0x7fe0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0x8000, 0x5d60, 0x80) + mstore(0x8080, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0x80a0, 0x61e0, 0x80) + mstore(0x8120, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_1, r)) + mcopy(0x8140, 0x62e0, 0x80) + mstore(0x81c0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_1, r)) + mcopy(0x81e0, 0x60e0, 0x80) + mstore(0x8260, x4_pow_2) + mcopy(0x8280, 0x6160, 0x80) + mstore(0x8300, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0x8320, F_COM_MPTR, 0x80) + mstore(0x83a0, x4_pow_3) if success { - success := staticcall(gas(), 0x0c, 0x56e0, 0x1d60, FINAL_COM_MPTR, 0x80) + success := staticcall(gas(), 0x0c, 0x6660, 0x1d60, FINAL_COM_MPTR, 0x80) success := and(success, eq(returndatasize(), 0x80)) } mstore(V_MPTR, v) @@ -2589,28 +2687,28 @@ contract Halo2Verifier { // Scale z*pi - vG before the final pairing check // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) - mcopy(0x80, G1_BASE_MPTR, 0x80) - mstore(0x100, addmod(0, sub(r, mload(V_MPTR)), r)) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) if success { - success := staticcall(gas(), 0x0c, 0x80, 0xa0, 0x80, 0x80) + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, FINAL_COM_MPTR, 0x80) + mcopy(0x1080, FINAL_COM_MPTR, 0x80) if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, PI_MPTR, 0x80) - mstore(0x180, mload(X3_MPTR)) + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) if success { - success := staticcall(gas(), 0x0c, 0x100, 0xa0, 0x100, 0x80) + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) success := and(success, eq(returndatasize(), 0x80)) } if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(PAIRING_RHS_MPTR, 0x80, 0x80) + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) } } @@ -2648,6 +2746,12 @@ contract Halo2Verifier { // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) } diff --git a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2VerifyingKey.sol b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2VerifyingKey.sol index 895f2a4d2..1ee722abf 100644 --- a/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2VerifyingKey.sol +++ b/proofs/solidity-verifier/target/poseidon-fixture-dump/Halo2VerifyingKey.sol @@ -94,9 +94,9 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x03a0), 0x00000000000000000000000000000000006d57f79a18220d1e5ef04bd519e995) // neg_s_g2_y_c1_hi mstore(add(payload, 0x03c0), 0x9a9cc71553bb761b5422a6b6971b75c8d3695bfa07b861c4b1c958da426efc45) // neg_s_g2_y_c1_lo mstore(add(payload, 0x03e0), 0x0000000000000000000000000000000000000000000000000000000000000001) // quotient_const - mstore(add(payload, 0x0400), 0x0547401048c00547e008060d000b02000105470011470011470005476008060d) // quotient_program - mstore(add(payload, 0x0420), 0x000b03000005472011472011472005478008060d000b03000105474011474011) // quotient_program - mstore(add(payload, 0x0440), 0x474005480008060d000b0300011b00001b00011b0002191f0000000000000000) // quotient_program + mstore(add(payload, 0x0400), 0x0556c010584005576008060d000b0200010556801156801156800556e008060d) // quotient_program + mstore(add(payload, 0x0420), 0x000b0300000556a01156a01156a005570008060d000b0300010556c01156c011) // quotient_program + mstore(add(payload, 0x0440), 0x56c005578008060d000b0300011b00001b00011b0002191f0000000000000000) // quotient_program // Fixed-column commitment 0, stored as one // EIP-2537 padded uncompressed G1 slot. mstore(add(payload, 0x0460), 0x0000000000000000000000000000000016742a8c4f331d1be5bc8622ba92b271) // fixed_comms[0].x_hi diff --git a/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol index 41513989e..7679a069d 100644 --- a/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/rsa-signature-fixture-dump/Halo2Verifier.sol @@ -55,8 +55,8 @@ contract Halo2Verifier { uint256 internal constant INSTANCE_CPTR = 0xec4; // First general-purpose memory words reserved by the generated verifier. // RETURN_MPTR is a single word set to 1 on success. - uint256 internal constant TRANSCRIPT_MPTR = 0x80; - uint256 internal constant RETURN_MPTR = 0x80; + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; // ---------------------------------------------------------------------- // Verifying-key memory map. The VK header lives at VK_MPTR, followed @@ -64,84 +64,84 @@ contract Halo2Verifier { // runtime comes the challenge slots (challenge_mptr..) and the // per-stage scratch (theta_mptr..). // ---------------------------------------------------------------------- - uint256 internal constant VK_MPTR = 0x16e0; - uint256 internal constant VK_DIGEST_MPTR = 0x16e0; - uint256 internal constant NUM_INSTANCES_MPTR = 0x1700; - uint256 internal constant K_MPTR = 0x1720; - uint256 internal constant N_INV_MPTR = 0x1740; - uint256 internal constant OMEGA_MPTR = 0x1760; - uint256 internal constant OMEGA_INV_MPTR = 0x1780; - uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x17a0; - uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x17c0; - uint256 internal constant ACC_OFFSET_MPTR = 0x17e0; - uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x1800; - uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x1820; - uint256 internal constant G1_BASE_MPTR = 0x1840; - uint256 internal constant G2_BASE_MPTR = 0x18c0; - uint256 internal constant NEG_S_G2_BASE_MPTR = 0x19c0; - - uint256 internal constant CHALLENGE_MPTR = 0x2760; + uint256 internal constant VK_MPTR = 0x2660; + uint256 internal constant VK_DIGEST_MPTR = 0x2660; + uint256 internal constant NUM_INSTANCES_MPTR = 0x2680; + uint256 internal constant K_MPTR = 0x26a0; + uint256 internal constant N_INV_MPTR = 0x26c0; + uint256 internal constant OMEGA_MPTR = 0x26e0; + uint256 internal constant OMEGA_INV_MPTR = 0x2700; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x2720; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x2740; + uint256 internal constant ACC_OFFSET_MPTR = 0x2760; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x2780; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x27a0; + uint256 internal constant G1_BASE_MPTR = 0x27c0; + uint256 internal constant G2_BASE_MPTR = 0x2840; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x2940; + + uint256 internal constant CHALLENGE_MPTR = 0x36e0; // Challenge layout. Squeeze order in midnight-proofs: // user_phase challenges (variable count) // theta -> beta, gamma -> trash_challenge -> y -> x -> // x1, x2 -> x3 -> x4 - uint256 internal constant THETA_MPTR = 0x2760; - uint256 internal constant BETA_MPTR = 0x2780; - uint256 internal constant GAMMA_MPTR = 0x27a0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x27c0; - uint256 internal constant Y_MPTR = 0x27e0; - uint256 internal constant X_MPTR = 0x2800; - uint256 internal constant X1_MPTR = 0x2820; - uint256 internal constant X2_MPTR = 0x2840; - uint256 internal constant X3_MPTR = 0x2860; - uint256 internal constant X4_MPTR = 0x2880; + uint256 internal constant THETA_MPTR = 0x36e0; + uint256 internal constant BETA_MPTR = 0x3700; + uint256 internal constant GAMMA_MPTR = 0x3720; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x3740; + uint256 internal constant Y_MPTR = 0x3760; + uint256 internal constant X_MPTR = 0x3780; + uint256 internal constant X1_MPTR = 0x37a0; + uint256 internal constant X2_MPTR = 0x37c0; + uint256 internal constant X3_MPTR = 0x37e0; + uint256 internal constant X4_MPTR = 0x3800; // Batch-open commitments live in 4-word EIP-2537 padded slots. - uint256 internal constant F_COM_MPTR = 0x28a0; - uint256 internal constant PI_MPTR = 0x2920; + uint256 internal constant F_COM_MPTR = 0x3820; + uint256 internal constant PI_MPTR = 0x38a0; // Accumulator (KZG IVC). - uint256 internal constant ACC_LHS_MPTR = 0x29a0; - uint256 internal constant ACC_RHS_MPTR = 0x2a20; + uint256 internal constant ACC_LHS_MPTR = 0x3920; + uint256 internal constant ACC_RHS_MPTR = 0x39a0; // Lagrange / linearization scratch. - uint256 internal constant X_N_MPTR = 0x2aa0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x2ac0; - uint256 internal constant L_LAST_MPTR = 0x2ae0; - uint256 internal constant L_BLIND_MPTR = 0x2b00; - uint256 internal constant L_0_MPTR = 0x2b20; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x2b40; + uint256 internal constant X_N_MPTR = 0x3a20; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x3a40; + uint256 internal constant L_LAST_MPTR = 0x3a60; + uint256 internal constant L_BLIND_MPTR = 0x3a80; + uint256 internal constant L_0_MPTR = 0x3aa0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x3ac0; // Legacy name: this is not h(x). It stores the expected opening // scalar for the linearized commitment, i.e. the negated y-batched // identity numerator reconstructed from the alleged evals at x. - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x2b60; - uint256 internal constant QUOTIENT_MPTR = 0x2b80; // 4 words - uint256 internal constant F_EVAL_MPTR = 0x2c20; - uint256 internal constant V_MPTR = 0x2c40; - uint256 internal constant FINAL_COM_MPTR = 0x2c60; // 4 words - uint256 internal constant PAIRING_LHS_MPTR = 0x2ce0; // 4 words - uint256 internal constant PAIRING_RHS_MPTR = 0x2d60; // 4 words + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x3ae0; + uint256 internal constant QUOTIENT_MPTR = 0x3b00; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x3ba0; + uint256 internal constant V_MPTR = 0x3bc0; + uint256 internal constant FINAL_COM_MPTR = 0x3be0; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x3c60; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x3ce0; // 4 words // Multi-prepare scratch (sized at codegen time). - uint256 internal constant ROT_POINTS_MPTR = 0x2de0; - uint256 internal constant X1_POWERS_MPTR = 0x3160; + uint256 internal constant ROT_POINTS_MPTR = 0x3d60; + uint256 internal constant X1_POWERS_MPTR = 0x40e0; // Q_COM materialization is currently fused into the final MSM scratch, // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero // reserved capacity until a future emitter starts writing Q_COM_MPTR. - uint256 internal constant Q_COM_MPTR = 0x3980; - uint256 internal constant Q_EVAL_SET_MPTR = 0x3980; + uint256 internal constant Q_COM_MPTR = 0x4900; + uint256 internal constant Q_EVAL_SET_MPTR = 0x4900; // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals // block of the proof; we keep it as a memory slot for symmetry. - uint256 internal constant Q_EVAL_CPTR_MPTR = 0x4080; + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x5000; // Reserved 4-word slot for the G1 identity (point at infinity) in // EIP-2537 padded form. EVM memory is zero-initialised, and we // never write to this region, so the four `mload`s below produce // 0,0,0,0 which is exactly the identity encoding the EIP-2537 // ec_add / ec_mul precompiles accept. - uint256 internal constant G1_IDENTITY_MPTR = 0x4180; + uint256 internal constant G1_IDENTITY_MPTR = 0x5100; // Decoded polynomial-eval buffer (Optimisation H3). The off-chain // Solidity proof shim rewrites proof scalars into canonical BE words, @@ -149,11 +149,11 @@ contract Halo2Verifier { // side `evaluations` loop range-checks and spills that value here so // downstream eval references (gate evaluator + PCS q_eval Horner) // become 3-gas `mload(...)` instead of calldata reads. - uint256 internal constant REVERSED_EVALS_MPTR = 0x42e0; - uint256 internal constant SELECTOR_ACC_MPTR = 0x4fc0; - uint256 internal constant QUOTIENT_RETURN_MPTR = 0x80; - uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x4fc0; - uint256 internal constant TRACE_U256_MPTR = 0x7000; + uint256 internal constant REVERSED_EVALS_MPTR = 0x5260; + uint256 internal constant SELECTOR_ACC_MPTR = 0x5f40; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x5f40; + uint256 internal constant TRACE_U256_MPTR = 0x7940; // ---------------------------------------------------------------------- // Per-category bases for EIP-2537 padded G1 commitments. The proof @@ -170,13 +170,13 @@ contract Halo2Verifier { // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans // ---------------------------------------------------------------------- - uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x4840; - uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x4ac0; - uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x4b40; - uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x4cc0; - uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x4d40; - uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x4dc0; - uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x4dc0; + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x57c0; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x5a40; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x5ac0; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x5c40; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x5cc0; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x5d40; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x5d40; // BLS12-381 scalar-field modulus, used for transcript challenges and all // Halo2 verifier arithmetic. @@ -198,7 +198,7 @@ contract Halo2Verifier { function require_eip2537_precompiles() private view { assembly ("memory-safe") { // Scratch is reused for every runtime-prerequisite probe. - let scratch := 0x80 + let scratch := 0x1000 // MCOPY must be available because the verifier uses it for // proof-time point/scratch staging. Execute the opcode here so a @@ -222,11 +222,44 @@ contract Halo2Verifier { revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair // smoke call. - let msm_scratch := 0x4fc0 + let msm_scratch := 0x5f40 for { let off := 0 } lt(off, 0x19a0) { off := add(off, 0x20) } { mstore(add(msm_scratch, off), 0) } @@ -321,9 +354,15 @@ contract Halo2Verifier { // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } - let p := 0x15e0 + let p := 0x2560 // EIP-198 modexp frame: // [base_len, exp_len, mod_len, base, exponent, modulus] mstore(add(p, 0x00), 0x20) // base len @@ -458,6 +497,13 @@ contract Halo2Verifier { // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -478,16 +524,34 @@ contract Halo2Verifier { // Forward pass: scratch stores prefix products up to, but not // including, the final element. `gp` becomes the total product. + // + // Match the single-element path: reject non-canonical words + // (x >= r) instead of letting mulmod reduce them silently, so + // accept/reject semantics do not depend on batch length. let gp_mptr := scratch_mptr let gp := mload(mptr_start) + if iszero(lt(gp, r)) { + ret := 0 + leave + } let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) {} { - gp := mulmod(gp, mload(mptr), r) + let x := mload(mptr) + if iszero(lt(x, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x, r) mstore(gp_mptr, gp) mptr := add(mptr, 0x20) gp_mptr := add(gp_mptr, 0x20) } - gp := mulmod(gp, mload(mptr), r) + let x_last := mload(mptr) + if iszero(lt(x_last, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x_last, r) // A zero total product means at least one denominator was // zero, so no batch inverse exists. if iszero(gp) { @@ -504,6 +568,12 @@ contract Halo2Verifier { mstore(add(gp_mptr, 0xa0), r) ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -528,21 +598,30 @@ contract Halo2Verifier { // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0300 + let scratch := 0x1220 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } @@ -574,7 +653,13 @@ contract Halo2Verifier { // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -1334,15 +1419,15 @@ contract Halo2Verifier { // q_const_mptr points to Fr constants used by the VM. // q_program_mptr points to the bytecode stream. // Constants are stored as consecutive 32-byte Fr words. - let q_const_mptr := 0x1ac0 + let q_const_mptr := 0x2a40 // Program bytes are also stored in the VK payload, packed into // 32-byte words by PackedProgramCodec. - let q_program_mptr := 0x1ac0 + let q_program_mptr := 0x2a40 // Running Horner accumulator for fully evaluated identities. // After all identities, this is nu_y(x) for the `None` // identity group. // Initialize A = 0 before scanning the identity stream. - mstore(0x5020, 0) + mstore(0x5fa0, 0) // Simple selectors are grouped into separate linearization // buckets. They start at zero for every proof. // q_sel_zero_off walks selector bucket byte offsets. @@ -1357,12 +1442,19 @@ contract Halo2Verifier { { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0x5fe0, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 15) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) // Store y^i at selector_power_mptr + 32*i. - mstore(add(0x5060, shl(5, q_y_power_i)), q_y_power) + mstore(add(0x5fe0, shl(5, q_y_power_i)), q_y_power) } } @@ -1371,102 +1463,102 @@ contract Halo2Verifier { // VM/native identities, so they occupy the same y-batch order. { let var0 := 0x1 - let f_3 := mload(0x4520) - let f_4 := mload(0x4420) - let a_0 := mload(0x4300) + let f_3 := mload(0x54a0) + let f_4 := mload(0x53a0) + let a_0 := mload(0x5280) let var1 := mulmod(f_4, a_0, r) let var2 := addmod(f_3, var1, r) - let f_5 := mload(0x4440) - let a_1 := mload(0x4320) + let f_5 := mload(0x53c0) + let a_1 := mload(0x52a0) let var3 := mulmod(f_5, a_1, r) let var4 := addmod(var2, var3, r) - let f_6 := mload(0x4460) - let a_2 := mload(0x4340) + let f_6 := mload(0x53e0) + let a_2 := mload(0x52c0) let var5 := mulmod(f_6, a_2, r) let var6 := addmod(var4, var5, r) - let f_7 := mload(0x4480) - let a_3 := mload(0x4360) + let f_7 := mload(0x5400) + let a_3 := mload(0x52e0) let var7 := mulmod(f_7, a_3, r) let var8 := addmod(var6, var7, r) - let f_8 := mload(0x44a0) - let a_4 := mload(0x4380) + let f_8 := mload(0x5420) + let a_4 := mload(0x5300) let var9 := mulmod(f_8, a_4, r) let var10 := addmod(var8, var9, r) - let f_0 := mload(0x44c0) - let a_0_next_1 := mload(0x43a0) + let f_0 := mload(0x5440) + let a_0_next_1 := mload(0x5320) let var11 := mulmod(f_0, a_0_next_1, r) let var12 := addmod(var10, var11, r) - let f_1 := mload(0x44e0) + let f_1 := mload(0x5460) let var13 := mulmod(f_1, a_0, r) let var14 := mulmod(var13, a_1, r) let var15 := addmod(var12, var14, r) - let f_2 := mload(0x4500) + let f_2 := mload(0x5480) let var16 := mulmod(f_2, a_0, r) let var17 := mulmod(var16, a_2, r) let var18 := addmod(var15, var17, r) let var19 := mulmod(var0, var18, r) - mstore(0x5240, var19) + mstore(0x61c0, var19) } - mstore(0x5020, mulmod(mload(0x5020), y, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x5240), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x61c0), r)) } { let var0 := 0x1 - let a_1 := mload(0x4320) - let a_2 := mload(0x4340) + let a_1 := mload(0x52a0) + let a_2 := mload(0x52c0) let var1 := addmod(a_1, a_2, r) - let a_3 := mload(0x4360) + let a_3 := mload(0x52e0) let var2 := addmod(0, sub(r, a_3), r) let var3 := addmod(var1, var2, r) - let a_4 := mload(0x4380) + let a_4 := mload(0x5300) let var4 := addmod(0, sub(r, a_4), r) let var5 := addmod(var3, var4, r) let var6 := mulmod(var0, var5, r) - mstore(0x5240, var6) + mstore(0x61c0, var6) } - mstore(0x5020, mulmod(mload(0x5020), y, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x5240), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x61c0), r)) } { let var0 := 0x1 - let a_0 := mload(0x4300) - let f_4 := mload(0x4420) + let a_0 := mload(0x5280) + let f_4 := mload(0x53a0) let var1 := addmod(a_0, f_4, r) - let a_0_next_1 := mload(0x43a0) + let a_0_next_1 := mload(0x5320) let var2 := addmod(0, sub(r, a_0_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x5240, var4) + mstore(0x61c0, var4) } - mstore(0x5020, mulmod(mload(0x5020), y, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x5240), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x61c0), r)) } { let var0 := 0x1 - let a_1 := mload(0x4320) - let f_5 := mload(0x4440) + let a_1 := mload(0x52a0) + let f_5 := mload(0x53c0) let var1 := addmod(a_1, f_5, r) - let a_1_next_1 := mload(0x43c0) + let a_1_next_1 := mload(0x5340) let var2 := addmod(0, sub(r, a_1_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x5240, var4) + mstore(0x61c0, var4) } - mstore(0x5020, mulmod(mload(0x5020), y, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5060, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x5240), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x5fe0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x61c0), r)) } // VM registers: @@ -1486,7 +1578,7 @@ contract Halo2Verifier { // q_end is an exclusive byte pointer for the VM loop. let q_end := add(q_program_mptr, 0x05) // q_sp starts at the first free stack word. - let q_sp := 0x5240 + let q_sp := 0x61c0 // q_top is meaningless until q_has_top is set. let q_top := 0 // q_has_top = 0 means the VM stack is empty. @@ -1536,69 +1628,69 @@ contract Halo2Verifier { // stack. The Rust memory planner must reserve enough // words for structured_permutation_scratch_words(meta) // whenever this opcode can appear. - q_sp := 0x5240 + q_sp := 0x61c0 // The generated lines below call the same fold snippets // used by interpreted expressions, so trace IDs and // y-batch positions remain contiguous. { let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 - let q_perm_vals := 0x5240 - let q_perm_sigmas := 0x5340 - let q_perm_z_cur := 0x5440 - let q_perm_z_next := 0x54a0 - let q_perm_z_last := 0x5500 - let q_perm_delta_base_ptr := 0x5540 + let q_perm_vals := 0x61c0 + let q_perm_sigmas := 0x62c0 + let q_perm_z_cur := 0x63c0 + let q_perm_z_next := 0x6420 + let q_perm_z_last := 0x6480 + let q_perm_delta_base_ptr := 0x64c0 let q_perm_num_cols := 8 let q_perm_num_sets := 3 let q_perm_chunk_len := 3 let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 - mstore(add(q_perm_vals, 0x0), mload(0x4400)) + mstore(add(q_perm_vals, 0x0), mload(0x5380)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x4300, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x5280, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0xc0), mload(0x42e0)) + mstore(add(q_perm_vals, 0xc0), mload(0x5260)) mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) { for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 8) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off - mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x45c0, q_perm_sigma_load_src_off))) + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x5540, q_perm_sigma_load_src_off))) } } { for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 3) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) - mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x46c0, q_perm_z_cur_load_src_off))) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x5640, q_perm_z_cur_load_src_off))) } } { for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 3) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) - mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x46e0, q_perm_z_next_load_src_off))) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x5660, q_perm_z_next_load_src_off))) } } - mstore(add(q_perm_z_last, 0x0), mload(0x4700)) - mstore(add(q_perm_z_last, 0x20), mload(0x4760)) + mstore(add(q_perm_z_last, 0x0), mload(0x5680)) + mstore(add(q_perm_z_last, 0x20), mload(0x56e0)) let q_perm_eval := 0 q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_perm_eval, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_perm_eval, r)) let q_perm_zn := mload(add(q_perm_z_cur, 0x40)) q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_perm_eval, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_perm_eval, r)) for { let q_perm_i := 1 } lt(q_perm_i, 3) { q_perm_i := add(q_perm_i, 1) } { let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_perm_eval, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_perm_eval, r)) } mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) for { let q_perm_set := 0 } lt(q_perm_set, 3) { q_perm_set := add(q_perm_set, 1) } { @@ -1617,8 +1709,8 @@ contract Halo2Verifier { q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) } q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_perm_eval, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_perm_eval, r)) mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) } } @@ -1639,13 +1731,13 @@ contract Halo2Verifier { // f+beta/prefix/suffix scratch rather than as a // conventional VM stack. The Rust memory planner must // reserve structured_lookup_scratch_words(meta). - q_sp := 0x5240 + q_sp := 0x61c0 // Generated LogUp code follows the same y-batch order // as the Rust identity stream. { - let q_lookup_f := 0x5240 - let q_lookup_prefix := 0x52c0 - let q_lookup_suffix := 0x5340 + let q_lookup_f := 0x61c0 + let q_lookup_prefix := 0x6240 + let q_lookup_suffix := 0x62c0 let q_lookup_l0 := mload(L_0_MPTR) let q_lookup_llast := mload(L_LAST_MPTR) let q_lookup_lblind := mload(L_BLIND_MPTR) @@ -1655,17 +1747,17 @@ contract Halo2Verifier { let q_lookup_theta := mload(THETA_MPTR) { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x4800), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x5780), r) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_lookup_eval, r)) } { - let f_10 := mload(0x4540) + let f_10 := mload(0x54c0) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) let var1 := mulmod(var0, q_lookup_theta, r) for { let q_lookup_shared_i := 0 } lt(q_lookup_shared_i, 4) { q_lookup_shared_i := add(q_lookup_shared_i, 1) } { let q_lookup_shared_off := shl(5, q_lookup_shared_i) - let q_lookup_shared_tail := mload(add(0x4320, q_lookup_shared_off)) + let q_lookup_shared_tail := mload(add(0x52a0, q_lookup_shared_off)) let q_lookup_shared_compressed := addmod(var1, q_lookup_shared_tail, r) mstore(add(q_lookup_f, q_lookup_shared_off), addmod(q_lookup_shared_compressed, q_lookup_beta, r)) } @@ -1687,24 +1779,24 @@ contract Halo2Verifier { for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 4) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) } - let q_lookup_eval := addmod(mulmod(mload(0x47e0), q_lookup_product, r), sub(r, q_lookup_sum), r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x5760), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x47e0) - let f_16 := mload(0x45a0) - let f_11 := mload(0x4560) + let q_lookup_sum_h := mload(0x5760) + let f_16 := mload(0x5520) + let f_11 := mload(0x54e0) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) - let f_12 := mload(0x4580) + let f_12 := mload(0x5500) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) let q_lookup_s_sum_h := mulmod(f_16, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x4820), sub(r, addmod(mload(0x4800), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x57a0), sub(r, addmod(mload(0x5780), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x47c0), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x5740), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x5020, mulmod(mload(0x5020), y, r)) - mstore(0x5020, addmod(mload(0x5020), q_lookup_eval, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) + mstore(0x5fa0, addmod(mload(0x5fa0), q_lookup_eval, r)) } } } @@ -1725,27 +1817,27 @@ contract Halo2Verifier { // interpreter stack before dispatching. q_top := 0 q_has_top := 0 - q_sp := 0x5240 + q_sp := 0x61c0 // Native identity sub-cases are generated from selected heavy gate identities. switch q_native_idx case 0 { { let var0 := 0x1 - let a_2 := mload(0x4340) - let f_6 := mload(0x4460) + let a_2 := mload(0x52c0) + let f_6 := mload(0x53e0) let var1 := addmod(a_2, f_6, r) - let a_2_next_1 := mload(0x43e0) + let a_2_next_1 := mload(0x5360) let var2 := addmod(0, sub(r, a_2_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x5240, var4) + mstore(0x61c0, var4) } - mstore(0x5020, mulmod(mload(0x5020), y, r)) + mstore(0x5fa0, mulmod(mload(0x5fa0), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x5060, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x5240), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x5fe0, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x61c0), r)) } } default { revert(0, 0) } @@ -1761,6 +1853,12 @@ contract Halo2Verifier { // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0x61c0)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -1778,21 +1876,21 @@ contract Halo2Verifier { // selector commitment in the linearized MSM. { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5060, 0x01c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5fe0, 0x01c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5060, 0x01a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5fe0, 0x01a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5060, 0x0140)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x5fe0, 0x0140)), r)) } // Fully evaluated identities are the constant-polynomial side // of the linearization query. Rust subtracts that grouped // scalar into expected_eval, so Solidity stores -nu_y(x). - let linearization_expected_eval := addmod(0, sub(r, mload(0x5020)), r) + let linearization_expected_eval := addmod(0, sub(r, mload(0x5fa0)), r) mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) pop(y) } @@ -1893,37 +1991,37 @@ contract Halo2Verifier { { // q_eval_set[0]: 28 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x5020, 0x4360) - mstore(0x5040, 0x4380) - mstore(0x5060, 0x42e0) - mstore(0x5080, 0x47c0) - mstore(0x50a0, 0x47e0) - mstore(0x50c0, 0x4400) - mstore(0x50e0, 0x4420) - mstore(0x5100, 0x4440) - mstore(0x5120, 0x4460) - mstore(0x5140, 0x4480) - mstore(0x5160, 0x44a0) - mstore(0x5180, 0x44c0) - mstore(0x51a0, 0x44e0) - mstore(0x51c0, 0x4500) - mstore(0x51e0, 0x4520) - mstore(0x5200, 0x4540) - mstore(0x5220, 0x4560) - mstore(0x5240, 0x4580) - mstore(0x5260, 0x45a0) - mstore(0x5280, 0x45c0) - mstore(0x52a0, 0x45e0) - mstore(0x52c0, 0x4600) - mstore(0x52e0, 0x4620) - mstore(0x5300, 0x4640) - mstore(0x5320, 0x4660) - mstore(0x5340, 0x4680) - mstore(0x5360, 0x46a0) - mstore(0x5380, QUOTIENT_EVAL_MPTR) - let q_eval_set_0 := mload(0x4360) + mstore(0x5fa0, 0x52e0) + mstore(0x5fc0, 0x5300) + mstore(0x5fe0, 0x5260) + mstore(0x6000, 0x5740) + mstore(0x6020, 0x5760) + mstore(0x6040, 0x5380) + mstore(0x6060, 0x53a0) + mstore(0x6080, 0x53c0) + mstore(0x60a0, 0x53e0) + mstore(0x60c0, 0x5400) + mstore(0x60e0, 0x5420) + mstore(0x6100, 0x5440) + mstore(0x6120, 0x5460) + mstore(0x6140, 0x5480) + mstore(0x6160, 0x54a0) + mstore(0x6180, 0x54c0) + mstore(0x61a0, 0x54e0) + mstore(0x61c0, 0x5500) + mstore(0x61e0, 0x5520) + mstore(0x6200, 0x5540) + mstore(0x6220, 0x5560) + mstore(0x6240, 0x5580) + mstore(0x6260, 0x55a0) + mstore(0x6280, 0x55c0) + mstore(0x62a0, 0x55e0) + mstore(0x62c0, 0x5600) + mstore(0x62e0, 0x5620) + mstore(0x6300, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x52e0) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x5020, 0x20) + let eval_p := add(0x5fa0, 0x20) for { let i := 1 } lt(i, 0x1c) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -1938,20 +2036,20 @@ contract Halo2Verifier { { // q_eval_set[1]: 5 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x5020, 0x4300) - mstore(0x5040, 0x43a0) - mstore(0x5060, 0x4320) - mstore(0x5080, 0x43c0) - mstore(0x50a0, 0x4340) - mstore(0x50c0, 0x43e0) - mstore(0x50e0, 0x4780) - mstore(0x5100, 0x47a0) - mstore(0x5120, 0x4800) - mstore(0x5140, 0x4820) - let q_eval_set_0 := mload(0x4300) - let q_eval_set_1 := mload(0x43a0) + mstore(0x5fa0, 0x5280) + mstore(0x5fc0, 0x5320) + mstore(0x5fe0, 0x52a0) + mstore(0x6000, 0x5340) + mstore(0x6020, 0x52c0) + mstore(0x6040, 0x5360) + mstore(0x6060, 0x5700) + mstore(0x6080, 0x5720) + mstore(0x60a0, 0x5780) + mstore(0x60c0, 0x57a0) + let q_eval_set_0 := mload(0x5280) + let q_eval_set_1 := mload(0x5320) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x5020, 0x40) + let eval_p := add(0x5fa0, 0x40) for { let i := 1 } lt(i, 0x5) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -1967,12 +2065,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[2]: 2 commitment(s) - let q_eval_set_0 := mload(0x46c0) - let q_eval_set_1 := mload(0x46e0) - let q_eval_set_2 := mload(0x4700) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x4720), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x4740), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x4760), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + let q_eval_set_0 := mload(0x5640) + let q_eval_set_1 := mload(0x5660) + let q_eval_set_2 := mload(0x5680) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x56a0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x56c0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x56e0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x60), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x80), q_eval_set_1) mstore(add(Q_EVAL_SET_MPTR, 0xa0), q_eval_set_2) @@ -2092,95 +2190,95 @@ contract Halo2Verifier { v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x20)), x4_pow_1, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_3, r), r) - mcopy(0x5020, 0x49c0, 0x80) - mstore(0x50a0, 1) - mcopy(0x50c0, 0x4a40, 0x80) - mstore(0x5140, mload(add(X1_POWERS_MPTR, 0x20))) - mcopy(0x5160, 0x4ac0, 0x80) - mstore(0x51e0, mload(add(X1_POWERS_MPTR, 0x60))) - mcopy(0x5200, 0x4cc0, 0x80) - mstore(0x5280, mload(add(X1_POWERS_MPTR, 0x80))) - mcopy(0x52a0, 0x1f60, 0x80) - mstore(0x5320, mload(add(X1_POWERS_MPTR, 0xa0))) - mcopy(0x5340, 0x1ce0, 0x80) - mstore(0x53c0, mload(add(X1_POWERS_MPTR, 0xc0))) - mcopy(0x53e0, 0x1d60, 0x80) - mstore(0x5460, mload(add(X1_POWERS_MPTR, 0xe0))) - mcopy(0x5480, 0x1de0, 0x80) - mstore(0x5500, mload(add(X1_POWERS_MPTR, 0x100))) - mcopy(0x5520, 0x1e60, 0x80) - mstore(0x55a0, mload(add(X1_POWERS_MPTR, 0x120))) - mcopy(0x55c0, 0x1ee0, 0x80) - mstore(0x5640, mload(add(X1_POWERS_MPTR, 0x140))) - mcopy(0x5660, 0x1ae0, 0x80) - mstore(0x56e0, mload(add(X1_POWERS_MPTR, 0x160))) - mcopy(0x5700, 0x1b60, 0x80) - mstore(0x5780, mload(add(X1_POWERS_MPTR, 0x180))) - mcopy(0x57a0, 0x1be0, 0x80) - mstore(0x5820, mload(add(X1_POWERS_MPTR, 0x1a0))) - mcopy(0x5840, 0x1c60, 0x80) - mstore(0x58c0, mload(add(X1_POWERS_MPTR, 0x1c0))) - mcopy(0x58e0, 0x1fe0, 0x80) - mstore(0x5960, mload(add(X1_POWERS_MPTR, 0x1e0))) - mcopy(0x5980, 0x2060, 0x80) - mstore(0x5a00, mload(add(X1_POWERS_MPTR, 0x200))) - mcopy(0x5a20, 0x20e0, 0x80) - mstore(0x5aa0, mload(add(X1_POWERS_MPTR, 0x220))) - mcopy(0x5ac0, 0x22e0, 0x80) - mstore(0x5b40, mload(add(X1_POWERS_MPTR, 0x240))) - mcopy(0x5b60, 0x2360, 0x80) - mstore(0x5be0, mload(add(X1_POWERS_MPTR, 0x260))) - mcopy(0x5c00, 0x23e0, 0x80) - mstore(0x5c80, mload(add(X1_POWERS_MPTR, 0x280))) - mcopy(0x5ca0, 0x2460, 0x80) - mstore(0x5d20, mload(add(X1_POWERS_MPTR, 0x2a0))) - mcopy(0x5d40, 0x24e0, 0x80) - mstore(0x5dc0, mload(add(X1_POWERS_MPTR, 0x2c0))) - mcopy(0x5de0, 0x2560, 0x80) - mstore(0x5e60, mload(add(X1_POWERS_MPTR, 0x2e0))) - mcopy(0x5e80, 0x25e0, 0x80) - mstore(0x5f00, mload(add(X1_POWERS_MPTR, 0x300))) - mcopy(0x5f20, 0x2660, 0x80) - mstore(0x5fa0, mload(add(X1_POWERS_MPTR, 0x320))) - mcopy(0x5fc0, 0x26e0, 0x80) - mstore(0x6040, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0x5fa0, 0x5940, 0x80) + mstore(0x6020, 1) + mcopy(0x6040, 0x59c0, 0x80) + mstore(0x60c0, mload(add(X1_POWERS_MPTR, 0x20))) + mcopy(0x60e0, 0x5a40, 0x80) + mstore(0x6160, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0x6180, 0x5c40, 0x80) + mstore(0x6200, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0x6220, 0x2ee0, 0x80) + mstore(0x62a0, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0x62c0, 0x2c60, 0x80) + mstore(0x6340, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0x6360, 0x2ce0, 0x80) + mstore(0x63e0, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0x6400, 0x2d60, 0x80) + mstore(0x6480, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0x64a0, 0x2de0, 0x80) + mstore(0x6520, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0x6540, 0x2e60, 0x80) + mstore(0x65c0, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0x65e0, 0x2a60, 0x80) + mstore(0x6660, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0x6680, 0x2ae0, 0x80) + mstore(0x6700, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0x6720, 0x2b60, 0x80) + mstore(0x67a0, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0x67c0, 0x2be0, 0x80) + mstore(0x6840, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0x6860, 0x2f60, 0x80) + mstore(0x68e0, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0x6900, 0x2fe0, 0x80) + mstore(0x6980, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0x69a0, 0x3060, 0x80) + mstore(0x6a20, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0x6a40, 0x3260, 0x80) + mstore(0x6ac0, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0x6ae0, 0x32e0, 0x80) + mstore(0x6b60, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0x6b80, 0x3360, 0x80) + mstore(0x6c00, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0x6c20, 0x33e0, 0x80) + mstore(0x6ca0, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0x6cc0, 0x3460, 0x80) + mstore(0x6d40, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0x6d60, 0x34e0, 0x80) + mstore(0x6de0, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0x6e00, 0x3560, 0x80) + mstore(0x6e80, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0x6ea0, 0x35e0, 0x80) + mstore(0x6f20, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0x6f40, 0x3660, 0x80) + mstore(0x6fc0, mload(add(X1_POWERS_MPTR, 0x340))) let lin_query_scalar_26 := mload(add(X1_POWERS_MPTR, 0x360)) let lin_cur_scalar_26 := mulmod(lin_query_scalar_26, lin_one_minus_x_n, r) - mcopy(0x6060, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) - mstore(0x60e0, lin_cur_scalar_26) + mcopy(0x6fe0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0x7060, lin_cur_scalar_26) lin_cur_scalar_26 := mulmod(lin_cur_scalar_26, lin_x_split, r) - mcopy(0x6100, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) - mstore(0x6180, lin_cur_scalar_26) + mcopy(0x7080, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0x7100, lin_cur_scalar_26) lin_cur_scalar_26 := mulmod(lin_cur_scalar_26, lin_x_split, r) - mcopy(0x61a0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) - mstore(0x6220, lin_cur_scalar_26) + mcopy(0x7120, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0x71a0, lin_cur_scalar_26) lin_cur_scalar_26 := mulmod(lin_cur_scalar_26, lin_x_split, r) - mcopy(0x6240, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) - mstore(0x62c0, lin_cur_scalar_26) - mcopy(0x62e0, 0x2160, 0x80) - mstore(0x6360, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) - mcopy(0x6380, 0x21e0, 0x80) - mstore(0x6400, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) - mcopy(0x6420, 0x2260, 0x80) - mstore(0x64a0, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) - mcopy(0x64c0, 0x4840, 0x80) - mstore(0x6540, x4_pow_1) - mcopy(0x6560, 0x48c0, 0x80) - mstore(0x65e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) - mcopy(0x6600, 0x4940, 0x80) - mstore(0x6680, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) - mcopy(0x66a0, 0x4c40, 0x80) - mstore(0x6720, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_1, r)) - mcopy(0x6740, 0x4d40, 0x80) - mstore(0x67c0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_1, r)) - mcopy(0x67e0, 0x4b40, 0x80) - mstore(0x6860, x4_pow_2) - mcopy(0x6880, 0x4bc0, 0x80) - mstore(0x6900, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) - mcopy(0x6920, F_COM_MPTR, 0x80) - mstore(0x69a0, x4_pow_3) + mcopy(0x71c0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0x7240, lin_cur_scalar_26) + mcopy(0x7260, 0x30e0, 0x80) + mstore(0x72e0, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0x7300, 0x3160, 0x80) + mstore(0x7380, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0x73a0, 0x31e0, 0x80) + mstore(0x7420, mulmod(lin_query_scalar_26, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0x7440, 0x57c0, 0x80) + mstore(0x74c0, x4_pow_1) + mcopy(0x74e0, 0x5840, 0x80) + mstore(0x7560, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0x7580, 0x58c0, 0x80) + mstore(0x7600, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0x7620, 0x5bc0, 0x80) + mstore(0x76a0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_1, r)) + mcopy(0x76c0, 0x5cc0, 0x80) + mstore(0x7740, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_1, r)) + mcopy(0x7760, 0x5ac0, 0x80) + mstore(0x77e0, x4_pow_2) + mcopy(0x7800, 0x5b40, 0x80) + mstore(0x7880, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0x78a0, F_COM_MPTR, 0x80) + mstore(0x7920, x4_pow_3) if success { - success := staticcall(gas(), 0x0c, 0x5020, 0x19a0, FINAL_COM_MPTR, 0x80) + success := staticcall(gas(), 0x0c, 0x5fa0, 0x19a0, FINAL_COM_MPTR, 0x80) success := and(success, eq(returndatasize(), 0x80)) } mstore(V_MPTR, v) @@ -2192,28 +2290,28 @@ contract Halo2Verifier { // Scale z*pi - vG before the final pairing check // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) - mcopy(0x80, G1_BASE_MPTR, 0x80) - mstore(0x100, addmod(0, sub(r, mload(V_MPTR)), r)) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) if success { - success := staticcall(gas(), 0x0c, 0x80, 0xa0, 0x80, 0x80) + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, FINAL_COM_MPTR, 0x80) + mcopy(0x1080, FINAL_COM_MPTR, 0x80) if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, PI_MPTR, 0x80) - mstore(0x180, mload(X3_MPTR)) + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) if success { - success := staticcall(gas(), 0x0c, 0x100, 0xa0, 0x100, 0x80) + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) success := and(success, eq(returndatasize(), 0x80)) } if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(PAIRING_RHS_MPTR, 0x80, 0x80) + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) } } @@ -2251,6 +2349,12 @@ contract Halo2Verifier { // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) } diff --git a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol index 2e9d4419e..36d30c3e4 100644 --- a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol +++ b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2Verifier.sol @@ -43,7 +43,7 @@ contract Halo2Verifier { // EXPECTED_VK_PAYLOAD_LENGTH. uint256 internal constant EXPECTED_VK_PAYLOAD_LENGTH = 8032; uint256 internal constant EXPECTED_VK_LENGTH = 8033; - uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x6200af6d8b2f00ac28582e3c159c74e3e43daf38a03210efc438aa723d3e98bc; + uint256 internal constant EXPECTED_VK_CODEHASH_WORD = 0x677b7b26592c167d44fd428a1d743abb1eb2fd8040804bd032440791caeb7471; bytes32 internal constant EXPECTED_VK_CODEHASH = bytes32(EXPECTED_VK_CODEHASH_WORD); // Solidity ABI calldata cursors. The generated verifier accepts exactly @@ -55,8 +55,8 @@ contract Halo2Verifier { uint256 internal constant INSTANCE_CPTR = 0x1504; // First general-purpose memory words reserved by the generated verifier. // RETURN_MPTR is a single word set to 1 on success. - uint256 internal constant TRANSCRIPT_MPTR = 0x80; - uint256 internal constant RETURN_MPTR = 0x80; + uint256 internal constant TRANSCRIPT_MPTR = 0x1000; + uint256 internal constant RETURN_MPTR = 0x1000; // ---------------------------------------------------------------------- // Verifying-key memory map. The VK header lives at VK_MPTR, followed @@ -64,84 +64,84 @@ contract Halo2Verifier { // runtime comes the challenge slots (challenge_mptr..) and the // per-stage scratch (theta_mptr..). // ---------------------------------------------------------------------- - uint256 internal constant VK_MPTR = 0x1d20; - uint256 internal constant VK_DIGEST_MPTR = 0x1d20; - uint256 internal constant NUM_INSTANCES_MPTR = 0x1d40; - uint256 internal constant K_MPTR = 0x1d60; - uint256 internal constant N_INV_MPTR = 0x1d80; - uint256 internal constant OMEGA_MPTR = 0x1da0; - uint256 internal constant OMEGA_INV_MPTR = 0x1dc0; - uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x1de0; - uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x1e00; - uint256 internal constant ACC_OFFSET_MPTR = 0x1e20; - uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x1e40; - uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x1e60; - uint256 internal constant G1_BASE_MPTR = 0x1e80; - uint256 internal constant G2_BASE_MPTR = 0x1f00; - uint256 internal constant NEG_S_G2_BASE_MPTR = 0x2000; - - uint256 internal constant CHALLENGE_MPTR = 0x3c80; + uint256 internal constant VK_MPTR = 0x2ca0; + uint256 internal constant VK_DIGEST_MPTR = 0x2ca0; + uint256 internal constant NUM_INSTANCES_MPTR = 0x2cc0; + uint256 internal constant K_MPTR = 0x2ce0; + uint256 internal constant N_INV_MPTR = 0x2d00; + uint256 internal constant OMEGA_MPTR = 0x2d20; + uint256 internal constant OMEGA_INV_MPTR = 0x2d40; + uint256 internal constant OMEGA_INV_TO_L_MPTR = 0x2d60; + uint256 internal constant HAS_ACCUMULATOR_MPTR = 0x2d80; + uint256 internal constant ACC_OFFSET_MPTR = 0x2da0; + uint256 internal constant NUM_ACC_LIMBS_MPTR = 0x2dc0; + uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x2de0; + uint256 internal constant G1_BASE_MPTR = 0x2e00; + uint256 internal constant G2_BASE_MPTR = 0x2e80; + uint256 internal constant NEG_S_G2_BASE_MPTR = 0x2f80; + + uint256 internal constant CHALLENGE_MPTR = 0x4c00; // Challenge layout. Squeeze order in midnight-proofs: // user_phase challenges (variable count) // theta -> beta, gamma -> trash_challenge -> y -> x -> // x1, x2 -> x3 -> x4 - uint256 internal constant THETA_MPTR = 0x3c80; - uint256 internal constant BETA_MPTR = 0x3ca0; - uint256 internal constant GAMMA_MPTR = 0x3cc0; - uint256 internal constant TRASH_CHALLENGE_MPTR = 0x3ce0; - uint256 internal constant Y_MPTR = 0x3d00; - uint256 internal constant X_MPTR = 0x3d20; - uint256 internal constant X1_MPTR = 0x3d40; - uint256 internal constant X2_MPTR = 0x3d60; - uint256 internal constant X3_MPTR = 0x3d80; - uint256 internal constant X4_MPTR = 0x3da0; + uint256 internal constant THETA_MPTR = 0x4c00; + uint256 internal constant BETA_MPTR = 0x4c20; + uint256 internal constant GAMMA_MPTR = 0x4c40; + uint256 internal constant TRASH_CHALLENGE_MPTR = 0x4c60; + uint256 internal constant Y_MPTR = 0x4c80; + uint256 internal constant X_MPTR = 0x4ca0; + uint256 internal constant X1_MPTR = 0x4cc0; + uint256 internal constant X2_MPTR = 0x4ce0; + uint256 internal constant X3_MPTR = 0x4d00; + uint256 internal constant X4_MPTR = 0x4d20; // Batch-open commitments live in 4-word EIP-2537 padded slots. - uint256 internal constant F_COM_MPTR = 0x3dc0; - uint256 internal constant PI_MPTR = 0x3e40; + uint256 internal constant F_COM_MPTR = 0x4d40; + uint256 internal constant PI_MPTR = 0x4dc0; // Accumulator (KZG IVC). - uint256 internal constant ACC_LHS_MPTR = 0x3ec0; - uint256 internal constant ACC_RHS_MPTR = 0x3f40; + uint256 internal constant ACC_LHS_MPTR = 0x4e40; + uint256 internal constant ACC_RHS_MPTR = 0x4ec0; // Lagrange / linearization scratch. - uint256 internal constant X_N_MPTR = 0x3fc0; - uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x3fe0; - uint256 internal constant L_LAST_MPTR = 0x4000; - uint256 internal constant L_BLIND_MPTR = 0x4020; - uint256 internal constant L_0_MPTR = 0x4040; - uint256 internal constant INSTANCE_EVAL_MPTR = 0x4060; + uint256 internal constant X_N_MPTR = 0x4f40; + uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x4f60; + uint256 internal constant L_LAST_MPTR = 0x4f80; + uint256 internal constant L_BLIND_MPTR = 0x4fa0; + uint256 internal constant L_0_MPTR = 0x4fc0; + uint256 internal constant INSTANCE_EVAL_MPTR = 0x4fe0; // Legacy name: this is not h(x). It stores the expected opening // scalar for the linearized commitment, i.e. the negated y-batched // identity numerator reconstructed from the alleged evals at x. - uint256 internal constant QUOTIENT_EVAL_MPTR = 0x4080; - uint256 internal constant QUOTIENT_MPTR = 0x40a0; // 4 words - uint256 internal constant F_EVAL_MPTR = 0x4140; - uint256 internal constant V_MPTR = 0x4160; - uint256 internal constant FINAL_COM_MPTR = 0x4180; // 4 words - uint256 internal constant PAIRING_LHS_MPTR = 0x4200; // 4 words - uint256 internal constant PAIRING_RHS_MPTR = 0x4280; // 4 words + uint256 internal constant QUOTIENT_EVAL_MPTR = 0x5000; + uint256 internal constant QUOTIENT_MPTR = 0x5020; // 4 words + uint256 internal constant F_EVAL_MPTR = 0x50c0; + uint256 internal constant V_MPTR = 0x50e0; + uint256 internal constant FINAL_COM_MPTR = 0x5100; // 4 words + uint256 internal constant PAIRING_LHS_MPTR = 0x5180; // 4 words + uint256 internal constant PAIRING_RHS_MPTR = 0x5200; // 4 words // Multi-prepare scratch (sized at codegen time). - uint256 internal constant ROT_POINTS_MPTR = 0x4300; - uint256 internal constant X1_POWERS_MPTR = 0x4680; + uint256 internal constant ROT_POINTS_MPTR = 0x5280; + uint256 internal constant X1_POWERS_MPTR = 0x5600; // Q_COM materialization is currently fused into the final MSM scratch, // so this marker intentionally aliases Q_EVAL_SET_MPTR and has zero // reserved capacity until a future emitter starts writing Q_COM_MPTR. - uint256 internal constant Q_COM_MPTR = 0x4ea0; - uint256 internal constant Q_EVAL_SET_MPTR = 0x4ea0; + uint256 internal constant Q_COM_MPTR = 0x5e20; + uint256 internal constant Q_EVAL_SET_MPTR = 0x5e20; // Q_EVAL_CPTR is set at runtime once the verifier reaches the q_evals // block of the proof; we keep it as a memory slot for symmetry. - uint256 internal constant Q_EVAL_CPTR_MPTR = 0x55a0; + uint256 internal constant Q_EVAL_CPTR_MPTR = 0x6520; // Reserved 4-word slot for the G1 identity (point at infinity) in // EIP-2537 padded form. EVM memory is zero-initialised, and we // never write to this region, so the four `mload`s below produce // 0,0,0,0 which is exactly the identity encoding the EIP-2537 // ec_add / ec_mul precompiles accept. - uint256 internal constant G1_IDENTITY_MPTR = 0x56a0; + uint256 internal constant G1_IDENTITY_MPTR = 0x6620; // Decoded polynomial-eval buffer (Optimisation H3). The off-chain // Solidity proof shim rewrites proof scalars into canonical BE words, @@ -149,11 +149,11 @@ contract Halo2Verifier { // side `evaluations` loop range-checks and spills that value here so // downstream eval references (gate evaluator + PCS q_eval Horner) // become 3-gas `mload(...)` instead of calldata reads. - uint256 internal constant REVERSED_EVALS_MPTR = 0x5800; - uint256 internal constant SELECTOR_ACC_MPTR = 0x6b00; - uint256 internal constant QUOTIENT_RETURN_MPTR = 0x80; - uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x6b00; - uint256 internal constant TRACE_U256_MPTR = 0x9420; + uint256 internal constant REVERSED_EVALS_MPTR = 0x6780; + uint256 internal constant SELECTOR_ACC_MPTR = 0x7a80; + uint256 internal constant QUOTIENT_RETURN_MPTR = 0x1000; + uint256 internal constant BATCH_INV_SCRATCH_MPTR = 0x7a80; + uint256 internal constant TRACE_U256_MPTR = 0xa3a0; // ---------------------------------------------------------------------- // Per-category bases for EIP-2537 padded G1 commitments. The proof @@ -170,13 +170,13 @@ contract Halo2Verifier { // TRASHCAN_COMMS_MPTR_BASE + ... + 4*num_lookups // QUOTIENT_LIMB_COMMS_MPTR_BASE + ... + 4*num_trashcans // ---------------------------------------------------------------------- - uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x6080; - uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x6480; - uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x6580; - uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x6700; - uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x6800; - uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x6900; - uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x6900; + uint256 internal constant ADVICE_COMMS_MPTR_BASE = 0x7000; + uint256 internal constant LOOKUP_M_COMMS_MPTR_BASE = 0x7400; + uint256 internal constant PERM_Z_COMMS_MPTR_BASE = 0x7500; + uint256 internal constant LOOKUP_HELPER_COMMS_MPTR_BASE = 0x7680; + uint256 internal constant LOOKUP_Z_COMMS_MPTR_BASE = 0x7780; + uint256 internal constant TRASHCAN_COMMS_MPTR_BASE = 0x7880; + uint256 internal constant QUOTIENT_LIMB_COMMS_MPTR_BASE = 0x7880; // BLS12-381 scalar-field modulus, used for transcript challenges and all // Halo2 verifier arithmetic. @@ -198,7 +198,7 @@ contract Halo2Verifier { function require_eip2537_precompiles() private view { assembly ("memory-safe") { // Scratch is reused for every runtime-prerequisite probe. - let scratch := 0x80 + let scratch := 0x1000 // MCOPY must be available because the verifier uses it for // proof-time point/scratch staging. Execute the opcode here so a @@ -222,11 +222,44 @@ contract Halo2Verifier { revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), 0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f) + mstore(add(scratch, 0x20), 0xc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb) + mstore(add(scratch, 0x40), 0x0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4) + mstore(add(scratch, 0x60), 0xfcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1) + mcopy(add(scratch, 0x80), scratch, 0x80) + if iszero(staticcall(gas(), 0x0b, scratch, 0x0100, scratch, 0x80)) { revert(0, 0) } + if iszero(eq(returndatasize(), 0x80)) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), 0x000000000000000000000000000000000572cbea904d67468808c8eb50a9450c), + eq(mload(add(scratch, 0x20)), 0x9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e) + ), + and( + eq(mload(add(scratch, 0x40)), 0x00000000000000000000000000000000166a9d8cabc673a322fda673779d8e38), + eq(mload(add(scratch, 0x60)), 0x22ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, 0x0300) { off := add(off, 0x20) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair // smoke call. - let msm_scratch := 0x6b00 + let msm_scratch := 0x7a80 for { let off := 0 } lt(off, 0x2760) { off := add(off, 0x20) } { mstore(add(msm_scratch, off), 0) } @@ -321,9 +354,15 @@ contract Halo2Verifier { // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } - let p := 0x1c20 + let p := 0x2ba0 // EIP-198 modexp frame: // [base_len, exp_len, mod_len, base, exponent, modulus] mstore(add(p, 0x00), 0x20) // base len @@ -458,6 +497,13 @@ contract Halo2Verifier { // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -478,16 +524,34 @@ contract Halo2Verifier { // Forward pass: scratch stores prefix products up to, but not // including, the final element. `gp` becomes the total product. + // + // Match the single-element path: reject non-canonical words + // (x >= r) instead of letting mulmod reduce them silently, so + // accept/reject semantics do not depend on batch length. let gp_mptr := scratch_mptr let gp := mload(mptr_start) + if iszero(lt(gp, r)) { + ret := 0 + leave + } let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) {} { - gp := mulmod(gp, mload(mptr), r) + let x := mload(mptr) + if iszero(lt(x, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x, r) mstore(gp_mptr, gp) mptr := add(mptr, 0x20) gp_mptr := add(gp_mptr, 0x20) } - gp := mulmod(gp, mload(mptr), r) + let x_last := mload(mptr) + if iszero(lt(x_last, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x_last, r) // A zero total product means at least one denominator was // zero, so no batch inverse exists. if iszero(gp) { @@ -504,6 +568,12 @@ contract Halo2Verifier { mstore(add(gp_mptr, 0xa0), r) ret := staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -528,21 +598,30 @@ contract Halo2Verifier { // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to // be a 4-step mstore chain for each G1 (~60 gas) and an // 8-iter mstore loop for each G2 (~240 gas). Net saving // here is ~500 gas per ec_pairing call. - let scratch := 0x0300 + let scratch := 0x1220 mcopy(scratch, lhs_mptr, 0x80) mcopy(add(scratch, 0x80), G2_BASE_MPTR, 0x100) mcopy(add(scratch, 0x180), rhs_mptr, 0x80) mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), 0x0f, scratch, 0x0300, scratch, 0x20) ret := and(ret, eq(returndatasize(), 0x20)) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } @@ -574,7 +653,13 @@ contract Halo2Verifier { // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -1350,15 +1435,15 @@ contract Halo2Verifier { // q_const_mptr points to Fr constants used by the VM. // q_program_mptr points to the bytecode stream. // Constants are stored as consecutive 32-byte Fr words. - let q_const_mptr := 0x2100 + let q_const_mptr := 0x3080 // Program bytes are also stored in the VK payload, packed into // 32-byte words by PackedProgramCodec. - let q_program_mptr := 0x2660 + let q_program_mptr := 0x35e0 // Running Horner accumulator for fully evaluated identities. // After all identities, this is nu_y(x) for the `None` // identity group. // Initialize A = 0 before scanning the identity stream. - mstore(0x6cc0, 0) + mstore(0x7c40, 0) // Simple selectors are grouped into separate linearization // buckets. They start at zero for every proof. // q_sel_zero_off walks selector bucket byte offsets. @@ -1373,12 +1458,19 @@ contract Halo2Verifier { { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore(0x7c80, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, 35) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) // Store y^i at selector_power_mptr + 32*i. - mstore(add(0x6d00, shl(5, q_y_power_i)), q_y_power) + mstore(add(0x7c80, shl(5, q_y_power_i)), q_y_power) } } @@ -1387,102 +1479,102 @@ contract Halo2Verifier { // VM/native identities, so they occupy the same y-batch order. { let var0 := 0x1 - let f_3 := mload(0x5c40) - let f_4 := mload(0x5b40) - let a_0 := mload(0x5820) + let f_3 := mload(0x6bc0) + let f_4 := mload(0x6ac0) + let a_0 := mload(0x67a0) let var1 := mulmod(f_4, a_0, r) let var2 := addmod(f_3, var1, r) - let f_5 := mload(0x5b60) - let a_1 := mload(0x5840) + let f_5 := mload(0x6ae0) + let a_1 := mload(0x67c0) let var3 := mulmod(f_5, a_1, r) let var4 := addmod(var2, var3, r) - let f_6 := mload(0x5b80) - let a_2 := mload(0x5860) + let f_6 := mload(0x6b00) + let a_2 := mload(0x67e0) let var5 := mulmod(f_6, a_2, r) let var6 := addmod(var4, var5, r) - let f_7 := mload(0x5ba0) - let a_3 := mload(0x5880) + let f_7 := mload(0x6b20) + let a_3 := mload(0x6800) let var7 := mulmod(f_7, a_3, r) let var8 := addmod(var6, var7, r) - let f_8 := mload(0x5bc0) - let a_4 := mload(0x58a0) + let f_8 := mload(0x6b40) + let a_4 := mload(0x6820) let var9 := mulmod(f_8, a_4, r) let var10 := addmod(var8, var9, r) - let f_0 := mload(0x5be0) - let a_0_next_1 := mload(0x58c0) + let f_0 := mload(0x6b60) + let a_0_next_1 := mload(0x6840) let var11 := mulmod(f_0, a_0_next_1, r) let var12 := addmod(var10, var11, r) - let f_1 := mload(0x5c00) + let f_1 := mload(0x6b80) let var13 := mulmod(f_1, a_0, r) let var14 := mulmod(var13, a_1, r) let var15 := addmod(var12, var14, r) - let f_2 := mload(0x5c20) + let f_2 := mload(0x6ba0) let var16 := mulmod(f_2, a_0, r) let var17 := mulmod(var16, a_2, r) let var18 := addmod(var15, var17, r) let var19 := mulmod(var0, var18, r) - mstore(0x7160, var19) + mstore(0x80e0, var19) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } { let var0 := 0x1 - let a_1 := mload(0x5840) - let a_2 := mload(0x5860) + let a_1 := mload(0x67c0) + let a_2 := mload(0x67e0) let var1 := addmod(a_1, a_2, r) - let a_3 := mload(0x5880) + let a_3 := mload(0x6800) let var2 := addmod(0, sub(r, a_3), r) let var3 := addmod(var1, var2, r) - let a_4 := mload(0x58a0) + let a_4 := mload(0x6820) let var4 := addmod(0, sub(r, a_4), r) let var5 := addmod(var3, var4, r) let var6 := mulmod(var0, var5, r) - mstore(0x7160, var6) + mstore(0x80e0, var6) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x20) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } { let var0 := 0x1 - let a_0 := mload(0x5820) - let f_4 := mload(0x5b40) + let a_0 := mload(0x67a0) + let f_4 := mload(0x6ac0) let var1 := addmod(a_0, f_4, r) - let a_0_next_1 := mload(0x58c0) + let a_0_next_1 := mload(0x6840) let var2 := addmod(0, sub(r, a_0_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x7160, var4) + mstore(0x80e0, var4) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } { let var0 := 0x1 - let a_1 := mload(0x5840) - let f_5 := mload(0x5b60) + let a_1 := mload(0x67c0) + let f_5 := mload(0x6ae0) let var1 := addmod(a_1, f_5, r) - let a_1_next_1 := mload(0x58e0) + let a_1_next_1 := mload(0x6860) let var2 := addmod(0, sub(r, a_1_next_1), r) let var3 := addmod(var1, var2, r) let var4 := mulmod(var0, var3, r) - mstore(0x7160, var4) + mstore(0x80e0, var4) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x40) let q_selector_acc := mload(q_selector_ptr) - q_selector_acc := mulmod(q_selector_acc, mload(add(0x6d00, 0x20)), r) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + q_selector_acc := mulmod(q_selector_acc, mload(add(0x7c80, 0x20)), r) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } // VM registers: @@ -1502,7 +1594,7 @@ contract Halo2Verifier { // q_end is an exclusive byte pointer for the VM loop. let q_end := add(q_program_mptr, 0x019b) // q_sp starts at the first free stack word. - let q_sp := 0x7160 + let q_sp := 0x80e0 // q_top is meaningless until q_has_top is set. let q_top := 0 // q_has_top = 0 means the VM stack is empty. @@ -1833,70 +1925,70 @@ contract Halo2Verifier { // stack. The Rust memory planner must reserve enough // words for structured_permutation_scratch_words(meta) // whenever this opcode can appear. - q_sp := 0x7160 + q_sp := 0x80e0 // The generated lines below call the same fold snippets // used by interpreted expressions, so trace IDs and // y-batch positions remain contiguous. { let delta := 0x8634d0aa021aaf843cab354fabb0062f6502437c6a09c006c083479590189d7 - let q_perm_vals := 0x7160 - let q_perm_sigmas := 0x7280 - let q_perm_z_cur := 0x73a0 - let q_perm_z_next := 0x7400 - let q_perm_z_last := 0x7460 - let q_perm_delta_base_ptr := 0x74a0 + let q_perm_vals := 0x80e0 + let q_perm_sigmas := 0x8200 + let q_perm_z_cur := 0x8320 + let q_perm_z_next := 0x8380 + let q_perm_z_last := 0x83e0 + let q_perm_delta_base_ptr := 0x8420 let q_perm_num_cols := 9 let q_perm_num_sets := 3 let q_perm_chunk_len := 3 let q_perm_delta_chunk := 0x4285088329c399ea457a8ca1d30f8957e74c7f529842a1579b4fee55b3982923 - mstore(add(q_perm_vals, 0x0), mload(0x5b20)) + mstore(add(q_perm_vals, 0x0), mload(0x6aa0)) { for { let q_perm_val_load_i := 0 } lt(q_perm_val_load_i, 5) { q_perm_val_load_i := add(q_perm_val_load_i, 1) } { let q_perm_val_load_dst_off := shl(5, q_perm_val_load_i) let q_perm_val_load_src_off := q_perm_val_load_dst_off - mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x5820, q_perm_val_load_src_off))) + mstore(add(add(q_perm_vals, 0x20), q_perm_val_load_dst_off), mload(add(0x67a0, q_perm_val_load_src_off))) } } - mstore(add(q_perm_vals, 0xc0), mload(0x5800)) + mstore(add(q_perm_vals, 0xc0), mload(0x6780)) mstore(add(q_perm_vals, 0xe0), mload(INSTANCE_EVAL_MPTR)) - mstore(add(q_perm_vals, 0x100), mload(0x5920)) + mstore(add(q_perm_vals, 0x100), mload(0x68a0)) { for { let q_perm_sigma_load_i := 0 } lt(q_perm_sigma_load_i, 9) { q_perm_sigma_load_i := add(q_perm_sigma_load_i, 1) } { let q_perm_sigma_load_dst_off := shl(5, q_perm_sigma_load_i) let q_perm_sigma_load_src_off := q_perm_sigma_load_dst_off - mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x5d60, q_perm_sigma_load_src_off))) + mstore(add(add(q_perm_sigmas, 0x0), q_perm_sigma_load_dst_off), mload(add(0x6ce0, q_perm_sigma_load_src_off))) } } { for { let q_perm_z_cur_load_i := 0 } lt(q_perm_z_cur_load_i, 3) { q_perm_z_cur_load_i := add(q_perm_z_cur_load_i, 1) } { let q_perm_z_cur_load_dst_off := shl(5, q_perm_z_cur_load_i) let q_perm_z_cur_load_src_off := mul(q_perm_z_cur_load_i, 0x60) - mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x5e80, q_perm_z_cur_load_src_off))) + mstore(add(add(q_perm_z_cur, 0x0), q_perm_z_cur_load_dst_off), mload(add(0x6e00, q_perm_z_cur_load_src_off))) } } { for { let q_perm_z_next_load_i := 0 } lt(q_perm_z_next_load_i, 3) { q_perm_z_next_load_i := add(q_perm_z_next_load_i, 1) } { let q_perm_z_next_load_dst_off := shl(5, q_perm_z_next_load_i) let q_perm_z_next_load_src_off := mul(q_perm_z_next_load_i, 0x60) - mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x5ea0, q_perm_z_next_load_src_off))) + mstore(add(add(q_perm_z_next, 0x0), q_perm_z_next_load_dst_off), mload(add(0x6e20, q_perm_z_next_load_src_off))) } } - mstore(add(q_perm_z_last, 0x0), mload(0x5ec0)) - mstore(add(q_perm_z_last, 0x20), mload(0x5f20)) + mstore(add(q_perm_z_last, 0x0), mload(0x6e40)) + mstore(add(q_perm_z_last, 0x20), mload(0x6ea0)) let q_perm_eval := 0 q_perm_eval := mulmod(mload(L_0_MPTR), addmod(1, sub(r, mload(q_perm_z_cur)), r), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_perm_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_perm_eval, r)) let q_perm_zn := mload(add(q_perm_z_cur, 0x40)) q_perm_eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(q_perm_zn, q_perm_zn, r), sub(r, q_perm_zn), r), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_perm_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_perm_eval, r)) for { let q_perm_i := 1 } lt(q_perm_i, 3) { q_perm_i := add(q_perm_i, 1) } { let q_perm_cur := mload(add(q_perm_z_cur, shl(5, q_perm_i))) let q_perm_prev := mload(add(q_perm_z_last, shl(5, sub(q_perm_i, 1)))) q_perm_eval := mulmod(mload(L_0_MPTR), addmod(q_perm_cur, sub(r, q_perm_prev), r), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_perm_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_perm_eval, r)) } mstore(q_perm_delta_base_ptr, mulmod(mload(BETA_MPTR), mload(X_MPTR), r)) for { let q_perm_set := 0 } lt(q_perm_set, 3) { q_perm_set := add(q_perm_set, 1) } { @@ -1915,8 +2007,8 @@ contract Halo2Verifier { q_perm_delta_pow := mulmod(q_perm_delta_pow, delta, r) } q_perm_eval := mulmod(addmod(1, sub(r, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)), r), addmod(q_perm_left, sub(r, q_perm_right), r), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_perm_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_perm_eval, r)) mstore(q_perm_delta_base_ptr, mulmod(mload(q_perm_delta_base_ptr), q_perm_delta_chunk, r)) } } @@ -1937,13 +2029,13 @@ contract Halo2Verifier { // f+beta/prefix/suffix scratch rather than as a // conventional VM stack. The Rust memory planner must // reserve structured_lookup_scratch_words(meta). - q_sp := 0x7160 + q_sp := 0x80e0 // Generated LogUp code follows the same y-batch order // as the Rust identity stream. { - let q_lookup_f := 0x7160 - let q_lookup_prefix := 0x71a0 - let q_lookup_suffix := 0x71e0 + let q_lookup_f := 0x80e0 + let q_lookup_prefix := 0x8120 + let q_lookup_suffix := 0x8160 let q_lookup_l0 := mload(L_0_MPTR) let q_lookup_llast := mload(L_LAST_MPTR) let q_lookup_lblind := mload(L_BLIND_MPTR) @@ -1953,54 +2045,54 @@ contract Halo2Verifier { let q_lookup_theta := mload(THETA_MPTR) { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x5fc0), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x6f40), r) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } { - let f_10 := mload(0x5c60) + let f_10 := mload(0x6be0) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_10, r) - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_1, r) - let q_lookup_eval := addmod(mulmod(mload(0x5fa0), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x6f20), addmod(var1, q_lookup_beta, r), r), sub(r, 1), r) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x5fa0) - let f_19 := mload(0x5d20) - let f_11 := mload(0x5c80) + let q_lookup_sum_h := mload(0x6f20) + let f_19 := mload(0x6ca0) + let f_11 := mload(0x6c00) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_11, r) - let f_12 := mload(0x5ca0) + let f_12 := mload(0x6c20) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_12, r) let q_lookup_s_sum_h := mulmod(f_19, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x5fe0), sub(r, addmod(mload(0x5fc0), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x6f60), sub(r, addmod(mload(0x6f40), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var1, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x5f80), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x6f00), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } } { { - let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x6040), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + let q_lookup_eval := mulmod(q_lookup_lsum, mload(0x6fc0), r) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } { - let f_0 := mload(0x5be0) + let f_0 := mload(0x6b60) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_0, r) - let a_6 := mload(0x5940) + let a_6 := mload(0x68c0) let var1 := addmod(mulmod(var0, q_lookup_theta, r), a_6, r) - let a_0 := mload(0x5820) + let a_0 := mload(0x67a0) let var2 := addmod(mulmod(var1, q_lookup_theta, r), a_0, r) mstore(add(q_lookup_f, 0x0), addmod(var2, q_lookup_beta, r)) - let f_1 := mload(0x5c00) + let f_1 := mload(0x6b80) let var3 := addmod(mulmod(0, q_lookup_theta, r), f_1, r) - let a_7 := mload(0x5960) + let a_7 := mload(0x68e0) let var4 := addmod(mulmod(var3, q_lookup_theta, r), a_7, r) - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var5 := addmod(mulmod(var4, q_lookup_theta, r), a_1, r) mstore(add(q_lookup_f, 0x20), addmod(var5, q_lookup_beta, r)) let q_lookup_product := 1 @@ -2021,26 +2113,26 @@ contract Halo2Verifier { for { let q_lookup_sum_i := 0 } lt(q_lookup_sum_i, 2) { q_lookup_sum_i := add(q_lookup_sum_i, 1) } { q_lookup_sum := addmod(q_lookup_sum, mulmod(mload(add(q_lookup_prefix, shl(5, q_lookup_sum_i))), mload(add(q_lookup_suffix, shl(5, q_lookup_sum_i))), r), r) } - let q_lookup_eval := addmod(mulmod(mload(0x6020), q_lookup_product, r), sub(r, q_lookup_sum), r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + let q_lookup_eval := addmod(mulmod(mload(0x6fa0), q_lookup_product, r), sub(r, q_lookup_sum), r) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } { - let q_lookup_sum_h := mload(0x6020) - let f_20 := mload(0x5d40) - let f_13 := mload(0x5cc0) + let q_lookup_sum_h := mload(0x6fa0) + let f_20 := mload(0x6cc0) + let f_13 := mload(0x6c40) let var0 := addmod(mulmod(0, q_lookup_theta, r), f_13, r) - let f_14 := mload(0x5ce0) + let f_14 := mload(0x6c60) let var1 := addmod(mulmod(var0, q_lookup_theta, r), f_14, r) - let f_15 := mload(0x5d00) + let f_15 := mload(0x6c80) let var2 := addmod(mulmod(var1, q_lookup_theta, r), f_15, r) let q_lookup_s_sum_h := mulmod(f_20, q_lookup_sum_h, r) - let q_lookup_diff := addmod(mload(0x6060), sub(r, addmod(mload(0x6040), q_lookup_s_sum_h, r)), r) + let q_lookup_diff := addmod(mload(0x6fe0), sub(r, addmod(mload(0x6fc0), q_lookup_s_sum_h, r)), r) let q_lookup_t_beta := addmod(var2, q_lookup_beta, r) - let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x6000), r) + let q_lookup_core := addmod(mulmod(q_lookup_diff, q_lookup_t_beta, r), mload(0x6f80), r) let q_lookup_eval := mulmod(q_lookup_active, q_lookup_core, r) - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) - mstore(0x6cc0, addmod(mload(0x6cc0), q_lookup_eval, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) + mstore(0x7c40, addmod(mload(0x7c40), q_lookup_eval, r)) } } } @@ -2061,100 +2153,100 @@ contract Halo2Verifier { // interpreter stack before dispatching. q_top := 0 q_has_top := 0 - q_sp := 0x7160 + q_sp := 0x80e0 // Native identity sub-cases are generated from selected heavy gate identities. switch q_native_idx case 0 { { let var0 := 0x1 - let a_3_prev_1 := mload(0x5980) - let a_4_prev_1 := mload(0x59a0) + let a_3_prev_1 := mload(0x6900) + let a_4_prev_1 := mload(0x6920) let var1 := addmod(a_3_prev_1, a_4_prev_1, r) - let a_3 := mload(0x5880) + let a_3 := mload(0x6800) let var2 := addmod(var1, a_3, r) let var3 := 0x40000000000 - let a_1_prev_1 := mload(0x59e0) + let a_1_prev_1 := mload(0x6960) let var4 := mulmod(var3, a_1_prev_1, r) let var5 := 0x100000 - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var6 := mulmod(var5, a_1, r) let var7 := addmod(var4, var6, r) - let a_1_next_1 := mload(0x58e0) + let a_1_next_1 := mload(0x6860) let var8 := addmod(var7, a_1_next_1, r) let var9 := 0x2 - let a_0_prev_1 := mload(0x59c0) + let a_0_prev_1 := mload(0x6940) let var10 := mulmod(var3, a_0_prev_1, r) - let a_0 := mload(0x5820) + let a_0 := mload(0x67a0) let var11 := mulmod(var5, a_0, r) let var12 := addmod(var10, var11, r) - let a_0_next_1 := mload(0x58c0) + let a_0_next_1 := mload(0x6840) let var13 := addmod(var12, a_0_next_1, r) let var14 := mulmod(var9, var13, r) let var15 := addmod(var8, var14, r) let var16 := addmod(0, sub(r, var15), r) let var17 := addmod(var2, var16, r) let var18 := mulmod(var0, var17, r) - mstore(0x7160, var18) + mstore(0x80e0, var18) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x60) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } } case 1 { { let var0 := 0x1 - let a_3_prev_1 := mload(0x5980) - let a_4_prev_1 := mload(0x59a0) + let a_3_prev_1 := mload(0x6900) + let a_4_prev_1 := mload(0x6920) let var1 := addmod(a_3_prev_1, a_4_prev_1, r) let var2 := 0x40000000000 - let a_1_prev_1 := mload(0x59e0) + let a_1_prev_1 := mload(0x6960) let var3 := mulmod(var2, a_1_prev_1, r) let var4 := 0x100000 - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var5 := mulmod(var4, a_1, r) let var6 := addmod(var3, var5, r) - let a_1_next_1 := mload(0x58e0) + let a_1_next_1 := mload(0x6860) let var7 := addmod(var6, a_1_next_1, r) let var8 := 0x2 - let a_0_prev_1 := mload(0x59c0) + let a_0_prev_1 := mload(0x6940) let var9 := mulmod(var2, a_0_prev_1, r) - let a_0 := mload(0x5820) + let a_0 := mload(0x67a0) let var10 := mulmod(var4, a_0, r) let var11 := addmod(var9, var10, r) - let a_0_next_1 := mload(0x58c0) + let a_0_next_1 := mload(0x6840) let var12 := addmod(var11, a_0_next_1, r) let var13 := mulmod(var8, var12, r) let var14 := addmod(var7, var13, r) let var15 := addmod(0, sub(r, var14), r) let var16 := addmod(var1, var15, r) let var17 := mulmod(var0, var16, r) - mstore(0x7160, var17) + mstore(0x80e0, var17) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0x80) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } } case 2 { { let var0 := 0x1 let var1 := 0x1000000000000000 - let a_4 := mload(0x58a0) + let a_4 := mload(0x6820) let var2 := mulmod(var1, a_4, r) let var3 := 0x10000000000 - let a_3_prev_1 := mload(0x5980) + let a_3_prev_1 := mload(0x6900) let var4 := mulmod(var3, a_3_prev_1, r) let var5 := addmod(var2, var4, r) let var6 := 0x400000 - let a_4_prev_1 := mload(0x59a0) + let a_4_prev_1 := mload(0x6920) let var7 := mulmod(var6, a_4_prev_1, r) let var8 := addmod(var5, var7, r) - let a_3 := mload(0x5880) + let a_3 := mload(0x6800) let var9 := addmod(var8, a_3, r) let var10 := 0x40000000000 let var11 := mulmod(var10, a_3, r) @@ -2176,54 +2268,54 @@ contract Halo2Verifier { let var27 := addmod(var24, var26, r) let var28 := addmod(var27, a_3_prev_1, r) let var29 := addmod(var19, var28, r) - let a_0_prev_1 := mload(0x59c0) + let a_0_prev_1 := mload(0x6940) let var30 := mulmod(var10, a_0_prev_1, r) - let a_0 := mload(0x5820) + let a_0 := mload(0x67a0) let var31 := mulmod(var25, a_0, r) let var32 := addmod(var30, var31, r) - let a_0_next_1 := mload(0x58c0) + let a_0_next_1 := mload(0x6840) let var33 := addmod(var32, a_0_next_1, r) let var34 := 0x2 - let a_1_prev_1 := mload(0x59e0) + let a_1_prev_1 := mload(0x6960) let var35 := mulmod(var10, a_1_prev_1, r) - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var36 := mulmod(var25, a_1, r) let var37 := addmod(var35, var36, r) - let a_1_next_1 := mload(0x58e0) + let a_1_next_1 := mload(0x6860) let var38 := addmod(var37, a_1_next_1, r) let var39 := mulmod(var34, var38, r) let var40 := addmod(var33, var39, r) let var41 := addmod(0, sub(r, var40), r) let var42 := addmod(var29, var41, r) let var43 := mulmod(var0, var42, r) - mstore(0x7160, var43) + mstore(0x80e0, var43) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xa0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } } case 3 { { let var0 := 0x1 let var1 := 0x10000000000000 - let a_3_next_1 := mload(0x5a00) + let a_3_next_1 := mload(0x6980) let var2 := mulmod(var1, a_3_next_1, r) let var3 := 0x4000000000 - let a_3_prev_1 := mload(0x5980) + let a_3_prev_1 := mload(0x6900) let var4 := mulmod(var3, a_3_prev_1, r) let var5 := addmod(var2, var4, r) let var6 := 0x4000 - let a_4_prev_1 := mload(0x59a0) + let a_4_prev_1 := mload(0x6920) let var7 := mulmod(var6, a_4_prev_1, r) let var8 := addmod(var5, var7, r) let var9 := 0x400 - let a_3 := mload(0x5880) + let a_3 := mload(0x6800) let var10 := mulmod(var9, a_3, r) let var11 := addmod(var8, var10, r) - let a_4 := mload(0x58a0) + let a_4 := mload(0x6820) let var12 := addmod(var11, a_4, r) let var13 := 0x40000000000000 let var14 := mulmod(var13, a_4, r) @@ -2250,34 +2342,34 @@ contract Halo2Verifier { let var35 := addmod(var33, var34, r) let var36 := addmod(var35, a_3_prev_1, r) let var37 := addmod(var25, var36, r) - let a_0_prev_1 := mload(0x59c0) + let a_0_prev_1 := mload(0x6940) let var38 := mulmod(var15, a_0_prev_1, r) let var39 := 0x100000 - let a_0 := mload(0x5820) + let a_0 := mload(0x67a0) let var40 := mulmod(var39, a_0, r) let var41 := addmod(var38, var40, r) - let a_0_next_1 := mload(0x58c0) + let a_0_next_1 := mload(0x6840) let var42 := addmod(var41, a_0_next_1, r) let var43 := 0x2 - let a_1_prev_1 := mload(0x59e0) + let a_1_prev_1 := mload(0x6960) let var44 := mulmod(var15, a_1_prev_1, r) - let a_1 := mload(0x5840) + let a_1 := mload(0x67c0) let var45 := mulmod(var39, a_1, r) let var46 := addmod(var44, var45, r) - let a_1_next_1 := mload(0x58e0) + let a_1_next_1 := mload(0x6860) let var47 := addmod(var46, a_1_next_1, r) let var48 := mulmod(var43, var47, r) let var49 := addmod(var42, var48, r) let var50 := addmod(0, sub(r, var49), r) let var51 := addmod(var37, var50, r) let var52 := mulmod(var0, var51, r) - mstore(0x7160, var52) + mstore(0x80e0, var52) } - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) { let q_selector_ptr := add(SELECTOR_ACC_MPTR, 0xc0) let q_selector_acc := mload(q_selector_ptr) - mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x7160), r)) + mstore(q_selector_ptr, addmod(q_selector_acc, mload(0x80e0), r)) } } default { revert(0, 0) } @@ -2301,14 +2393,14 @@ contract Halo2Verifier { // The global fully-evaluated accumulator is still // multiplied by y so later main identities land at the // same y powers as Rust's reverse fold. - mstore(0x6cc0, mulmod(mload(0x6cc0), y, r)) + mstore(0x7c40, mulmod(mload(0x7c40), y, r)) let q_target_ptr := add(SELECTOR_ACC_MPTR, shl(5, q_sel_idx)) let q_sel_acc := mload(q_target_ptr) if q_sel_gap { // Selector buckets are sparse in the global // identity stream. Precomputed y^gap advances only // this selector's local accumulator. - q_sel_acc := mulmod(q_sel_acc, mload(add(0x6d00, shl(5, q_sel_gap))), r) + q_sel_acc := mulmod(q_sel_acc, mload(add(0x7c80, shl(5, q_sel_gap))), r) } mstore(q_target_ptr, addmod(q_sel_acc, q_eval, r)) } @@ -2323,6 +2415,12 @@ contract Halo2Verifier { // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, 0x80e0)) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled @@ -2340,65 +2438,65 @@ contract Halo2Verifier { // selector commitment in the linearized MSM. { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x00) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0440)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0440)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x20) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0420)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0420)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x40) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x03c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x03c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x60) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x03a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x03a0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x80) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0360)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0360)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xa0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0340)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0340)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xc0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0320)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0320)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0xe0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0300)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0300)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0100) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x02e0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x02e0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0120) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x02c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x02c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0140) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0280)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0280)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0160) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x0240)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x0240)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x0180) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x01c0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x01c0)), r)) } { let q_sel_ptr := add(SELECTOR_ACC_MPTR, 0x01a0) - mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x6d00, 0x01a0)), r)) + mstore(q_sel_ptr, mulmod(mload(q_sel_ptr), mload(add(0x7c80, 0x01a0)), r)) } // Fully evaluated identities are the constant-polynomial side // of the linearization query. Rust subtracts that grouped // scalar into expected_eval, so Solidity stores -nu_y(x). - let linearization_expected_eval := addmod(0, sub(r, mload(0x6cc0)), r) + let linearization_expected_eval := addmod(0, sub(r, mload(0x7c40)), r) mstore(QUOTIENT_EVAL_MPTR, linearization_expected_eval) pop(y) } @@ -2501,42 +2599,42 @@ contract Halo2Verifier { { // q_eval_set[0]: 33 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x6cc0, 0x5800) - mstore(0x6ce0, 0x5f80) - mstore(0x6d00, 0x5fa0) - mstore(0x6d20, 0x6000) - mstore(0x6d40, 0x6020) - mstore(0x6d60, 0x5b20) - mstore(0x6d80, 0x5b40) - mstore(0x6da0, 0x5b60) - mstore(0x6dc0, 0x5b80) - mstore(0x6de0, 0x5ba0) - mstore(0x6e00, 0x5bc0) - mstore(0x6e20, 0x5be0) - mstore(0x6e40, 0x5c00) - mstore(0x6e60, 0x5c20) - mstore(0x6e80, 0x5c40) - mstore(0x6ea0, 0x5c60) - mstore(0x6ec0, 0x5c80) - mstore(0x6ee0, 0x5ca0) - mstore(0x6f00, 0x5cc0) - mstore(0x6f20, 0x5ce0) - mstore(0x6f40, 0x5d00) - mstore(0x6f60, 0x5d20) - mstore(0x6f80, 0x5d40) - mstore(0x6fa0, 0x5d60) - mstore(0x6fc0, 0x5d80) - mstore(0x6fe0, 0x5da0) - mstore(0x7000, 0x5dc0) - mstore(0x7020, 0x5de0) - mstore(0x7040, 0x5e00) - mstore(0x7060, 0x5e20) - mstore(0x7080, 0x5e40) - mstore(0x70a0, 0x5e60) - mstore(0x70c0, QUOTIENT_EVAL_MPTR) - let q_eval_set_0 := mload(0x5800) + mstore(0x7c40, 0x6780) + mstore(0x7c60, 0x6f00) + mstore(0x7c80, 0x6f20) + mstore(0x7ca0, 0x6f80) + mstore(0x7cc0, 0x6fa0) + mstore(0x7ce0, 0x6aa0) + mstore(0x7d00, 0x6ac0) + mstore(0x7d20, 0x6ae0) + mstore(0x7d40, 0x6b00) + mstore(0x7d60, 0x6b20) + mstore(0x7d80, 0x6b40) + mstore(0x7da0, 0x6b60) + mstore(0x7dc0, 0x6b80) + mstore(0x7de0, 0x6ba0) + mstore(0x7e00, 0x6bc0) + mstore(0x7e20, 0x6be0) + mstore(0x7e40, 0x6c00) + mstore(0x7e60, 0x6c20) + mstore(0x7e80, 0x6c40) + mstore(0x7ea0, 0x6c60) + mstore(0x7ec0, 0x6c80) + mstore(0x7ee0, 0x6ca0) + mstore(0x7f00, 0x6cc0) + mstore(0x7f20, 0x6ce0) + mstore(0x7f40, 0x6d00) + mstore(0x7f60, 0x6d20) + mstore(0x7f80, 0x6d40) + mstore(0x7fa0, 0x6d60) + mstore(0x7fc0, 0x6d80) + mstore(0x7fe0, 0x6da0) + mstore(0x8000, 0x6dc0) + mstore(0x8020, 0x6de0) + mstore(0x8040, QUOTIENT_EVAL_MPTR) + let q_eval_set_0 := mload(0x6780) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x6cc0, 0x20) + let eval_p := add(0x7c40, 0x20) for { let i := 1 } lt(i, 0x21) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2550,12 +2648,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[1]: 3 commitment(s) - let q_eval_set_0 := mload(0x5f40) - let q_eval_set_1 := mload(0x5f60) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x5fc0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x5fe0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6040), mload(add(X1_POWERS_MPTR, 0x40)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6060), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + let q_eval_set_0 := mload(0x6ec0) + let q_eval_set_1 := mload(0x6ee0) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6f40), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6f60), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6fc0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6fe0), mload(add(X1_POWERS_MPTR, 0x40)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0x20), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0x40), q_eval_set_1) } @@ -2565,35 +2663,35 @@ contract Halo2Verifier { { // q_eval_set[2]: 8 commitment(s) (rolled, m>=4) // stage per-(commit, rotation) eval source addresses - mstore(0x6cc0, 0x5820) - mstore(0x6ce0, 0x58c0) - mstore(0x6d00, 0x59c0) - mstore(0x6d20, 0x5840) - mstore(0x6d40, 0x58e0) - mstore(0x6d60, 0x59e0) - mstore(0x6d80, 0x5860) - mstore(0x6da0, 0x5900) - mstore(0x6dc0, 0x5a80) - mstore(0x6de0, 0x5880) - mstore(0x6e00, 0x5a00) - mstore(0x6e20, 0x5980) - mstore(0x6e40, 0x58a0) - mstore(0x6e60, 0x5a20) - mstore(0x6e80, 0x59a0) - mstore(0x6ea0, 0x5920) - mstore(0x6ec0, 0x5ae0) - mstore(0x6ee0, 0x5ac0) - mstore(0x6f00, 0x5940) - mstore(0x6f20, 0x5a60) - mstore(0x6f40, 0x5a40) - mstore(0x6f60, 0x5960) - mstore(0x6f80, 0x5b00) - mstore(0x6fa0, 0x5aa0) - let q_eval_set_0 := mload(0x5820) - let q_eval_set_1 := mload(0x58c0) - let q_eval_set_2 := mload(0x59c0) + mstore(0x7c40, 0x67a0) + mstore(0x7c60, 0x6840) + mstore(0x7c80, 0x6940) + mstore(0x7ca0, 0x67c0) + mstore(0x7cc0, 0x6860) + mstore(0x7ce0, 0x6960) + mstore(0x7d00, 0x67e0) + mstore(0x7d20, 0x6880) + mstore(0x7d40, 0x6a00) + mstore(0x7d60, 0x6800) + mstore(0x7d80, 0x6980) + mstore(0x7da0, 0x6900) + mstore(0x7dc0, 0x6820) + mstore(0x7de0, 0x69a0) + mstore(0x7e00, 0x6920) + mstore(0x7e20, 0x68a0) + mstore(0x7e40, 0x6a60) + mstore(0x7e60, 0x6a40) + mstore(0x7e80, 0x68c0) + mstore(0x7ea0, 0x69e0) + mstore(0x7ec0, 0x69c0) + mstore(0x7ee0, 0x68e0) + mstore(0x7f00, 0x6a80) + mstore(0x7f20, 0x6a20) + let q_eval_set_0 := mload(0x67a0) + let q_eval_set_1 := mload(0x6840) + let q_eval_set_2 := mload(0x6940) let pow_p := add(X1_POWERS_MPTR, 0x20) - let eval_p := add(0x6cc0, 0x60) + let eval_p := add(0x7c40, 0x60) for { let i := 1 } lt(i, 0x8) { i := add(i, 1) } { let pow := mload(pow_p) q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(mload(eval_p)), pow, r), r) @@ -2611,12 +2709,12 @@ contract Halo2Verifier { // grouped so gas checkpoints can attribute their cost. { // q_eval_set[3]: 2 commitment(s) - let q_eval_set_0 := mload(0x5e80) - let q_eval_set_1 := mload(0x5ea0) - let q_eval_set_2 := mload(0x5ec0) - q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x5ee0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x5f00), mload(add(X1_POWERS_MPTR, 0x20)), r), r) - q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x5f20), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + let q_eval_set_0 := mload(0x6e00) + let q_eval_set_1 := mload(0x6e20) + let q_eval_set_2 := mload(0x6e40) + q_eval_set_0 := addmod(q_eval_set_0, mulmod(mload(0x6e60), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_1 := addmod(q_eval_set_1, mulmod(mload(0x6e80), mload(add(X1_POWERS_MPTR, 0x20)), r), r) + q_eval_set_2 := addmod(q_eval_set_2, mulmod(mload(0x6ea0), mload(add(X1_POWERS_MPTR, 0x20)), r), r) mstore(add(Q_EVAL_SET_MPTR, 0xc0), q_eval_set_0) mstore(add(Q_EVAL_SET_MPTR, 0xe0), q_eval_set_1) mstore(add(Q_EVAL_SET_MPTR, 0x100), q_eval_set_2) @@ -2784,139 +2882,139 @@ contract Halo2Verifier { v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x40)), x4_pow_2, r), r) v := addmod(v, mulmod(calldataload(add(Q_EVAL_CPTR, 0x60)), x4_pow_3, r), r) v := addmod(v, mulmod(mload(F_EVAL_MPTR), x4_pow_4, r), r) - mcopy(0x6cc0, 0x6480, 0x80) - mstore(0x6d40, mload(add(X1_POWERS_MPTR, 0x20))) - mcopy(0x6d60, 0x6700, 0x80) - mstore(0x6de0, mload(add(X1_POWERS_MPTR, 0x40))) - mcopy(0x6e00, 0x6500, 0x80) - mstore(0x6e80, mload(add(X1_POWERS_MPTR, 0x60))) - mcopy(0x6ea0, 0x6780, 0x80) - mstore(0x6f20, mload(add(X1_POWERS_MPTR, 0x80))) - mcopy(0x6f40, 0x2c80, 0x80) - mstore(0x6fc0, mload(add(X1_POWERS_MPTR, 0xa0))) - mcopy(0x6fe0, 0x2a00, 0x80) - mstore(0x7060, mload(add(X1_POWERS_MPTR, 0xc0))) - mcopy(0x7080, 0x2a80, 0x80) - mstore(0x7100, mload(add(X1_POWERS_MPTR, 0xe0))) - mcopy(0x7120, 0x2b00, 0x80) - mstore(0x71a0, mload(add(X1_POWERS_MPTR, 0x100))) - mcopy(0x71c0, 0x2b80, 0x80) - mstore(0x7240, mload(add(X1_POWERS_MPTR, 0x120))) - mcopy(0x7260, 0x2c00, 0x80) - mstore(0x72e0, mload(add(X1_POWERS_MPTR, 0x140))) - mcopy(0x7300, 0x2800, 0x80) - mstore(0x7380, mload(add(X1_POWERS_MPTR, 0x160))) - mcopy(0x73a0, 0x2880, 0x80) - mstore(0x7420, mload(add(X1_POWERS_MPTR, 0x180))) - mcopy(0x7440, 0x2900, 0x80) - mstore(0x74c0, mload(add(X1_POWERS_MPTR, 0x1a0))) - mcopy(0x74e0, 0x2980, 0x80) - mstore(0x7560, mload(add(X1_POWERS_MPTR, 0x1c0))) - mcopy(0x7580, 0x2d00, 0x80) - mstore(0x7600, mload(add(X1_POWERS_MPTR, 0x1e0))) - mcopy(0x7620, 0x2d80, 0x80) - mstore(0x76a0, mload(add(X1_POWERS_MPTR, 0x200))) - mcopy(0x76c0, 0x2e00, 0x80) - mstore(0x7740, mload(add(X1_POWERS_MPTR, 0x220))) - mcopy(0x7760, 0x2e80, 0x80) - mstore(0x77e0, mload(add(X1_POWERS_MPTR, 0x240))) - mcopy(0x7800, 0x2f00, 0x80) - mstore(0x7880, mload(add(X1_POWERS_MPTR, 0x260))) - mcopy(0x78a0, 0x2f80, 0x80) - mstore(0x7920, mload(add(X1_POWERS_MPTR, 0x280))) - mcopy(0x7940, 0x3180, 0x80) - mstore(0x79c0, mload(add(X1_POWERS_MPTR, 0x2a0))) - mcopy(0x79e0, 0x3200, 0x80) - mstore(0x7a60, mload(add(X1_POWERS_MPTR, 0x2c0))) - mcopy(0x7a80, 0x3800, 0x80) - mstore(0x7b00, mload(add(X1_POWERS_MPTR, 0x2e0))) - mcopy(0x7b20, 0x3880, 0x80) - mstore(0x7ba0, mload(add(X1_POWERS_MPTR, 0x300))) - mcopy(0x7bc0, 0x3900, 0x80) - mstore(0x7c40, mload(add(X1_POWERS_MPTR, 0x320))) - mcopy(0x7c60, 0x3980, 0x80) - mstore(0x7ce0, mload(add(X1_POWERS_MPTR, 0x340))) - mcopy(0x7d00, 0x3a00, 0x80) - mstore(0x7d80, mload(add(X1_POWERS_MPTR, 0x360))) - mcopy(0x7da0, 0x3a80, 0x80) - mstore(0x7e20, mload(add(X1_POWERS_MPTR, 0x380))) - mcopy(0x7e40, 0x3b00, 0x80) - mstore(0x7ec0, mload(add(X1_POWERS_MPTR, 0x3a0))) - mcopy(0x7ee0, 0x3b80, 0x80) - mstore(0x7f60, mload(add(X1_POWERS_MPTR, 0x3c0))) - mcopy(0x7f80, 0x3c00, 0x80) - mstore(0x8000, mload(add(X1_POWERS_MPTR, 0x3e0))) + mcopy(0x7c40, 0x7400, 0x80) + mstore(0x7cc0, mload(add(X1_POWERS_MPTR, 0x20))) + mcopy(0x7ce0, 0x7680, 0x80) + mstore(0x7d60, mload(add(X1_POWERS_MPTR, 0x40))) + mcopy(0x7d80, 0x7480, 0x80) + mstore(0x7e00, mload(add(X1_POWERS_MPTR, 0x60))) + mcopy(0x7e20, 0x7700, 0x80) + mstore(0x7ea0, mload(add(X1_POWERS_MPTR, 0x80))) + mcopy(0x7ec0, 0x3c00, 0x80) + mstore(0x7f40, mload(add(X1_POWERS_MPTR, 0xa0))) + mcopy(0x7f60, 0x3980, 0x80) + mstore(0x7fe0, mload(add(X1_POWERS_MPTR, 0xc0))) + mcopy(0x8000, 0x3a00, 0x80) + mstore(0x8080, mload(add(X1_POWERS_MPTR, 0xe0))) + mcopy(0x80a0, 0x3a80, 0x80) + mstore(0x8120, mload(add(X1_POWERS_MPTR, 0x100))) + mcopy(0x8140, 0x3b00, 0x80) + mstore(0x81c0, mload(add(X1_POWERS_MPTR, 0x120))) + mcopy(0x81e0, 0x3b80, 0x80) + mstore(0x8260, mload(add(X1_POWERS_MPTR, 0x140))) + mcopy(0x8280, 0x3780, 0x80) + mstore(0x8300, mload(add(X1_POWERS_MPTR, 0x160))) + mcopy(0x8320, 0x3800, 0x80) + mstore(0x83a0, mload(add(X1_POWERS_MPTR, 0x180))) + mcopy(0x83c0, 0x3880, 0x80) + mstore(0x8440, mload(add(X1_POWERS_MPTR, 0x1a0))) + mcopy(0x8460, 0x3900, 0x80) + mstore(0x84e0, mload(add(X1_POWERS_MPTR, 0x1c0))) + mcopy(0x8500, 0x3c80, 0x80) + mstore(0x8580, mload(add(X1_POWERS_MPTR, 0x1e0))) + mcopy(0x85a0, 0x3d00, 0x80) + mstore(0x8620, mload(add(X1_POWERS_MPTR, 0x200))) + mcopy(0x8640, 0x3d80, 0x80) + mstore(0x86c0, mload(add(X1_POWERS_MPTR, 0x220))) + mcopy(0x86e0, 0x3e00, 0x80) + mstore(0x8760, mload(add(X1_POWERS_MPTR, 0x240))) + mcopy(0x8780, 0x3e80, 0x80) + mstore(0x8800, mload(add(X1_POWERS_MPTR, 0x260))) + mcopy(0x8820, 0x3f00, 0x80) + mstore(0x88a0, mload(add(X1_POWERS_MPTR, 0x280))) + mcopy(0x88c0, 0x4100, 0x80) + mstore(0x8940, mload(add(X1_POWERS_MPTR, 0x2a0))) + mcopy(0x8960, 0x4180, 0x80) + mstore(0x89e0, mload(add(X1_POWERS_MPTR, 0x2c0))) + mcopy(0x8a00, 0x4780, 0x80) + mstore(0x8a80, mload(add(X1_POWERS_MPTR, 0x2e0))) + mcopy(0x8aa0, 0x4800, 0x80) + mstore(0x8b20, mload(add(X1_POWERS_MPTR, 0x300))) + mcopy(0x8b40, 0x4880, 0x80) + mstore(0x8bc0, mload(add(X1_POWERS_MPTR, 0x320))) + mcopy(0x8be0, 0x4900, 0x80) + mstore(0x8c60, mload(add(X1_POWERS_MPTR, 0x340))) + mcopy(0x8c80, 0x4980, 0x80) + mstore(0x8d00, mload(add(X1_POWERS_MPTR, 0x360))) + mcopy(0x8d20, 0x4a00, 0x80) + mstore(0x8da0, mload(add(X1_POWERS_MPTR, 0x380))) + mcopy(0x8dc0, 0x4a80, 0x80) + mstore(0x8e40, mload(add(X1_POWERS_MPTR, 0x3a0))) + mcopy(0x8e60, 0x4b00, 0x80) + mstore(0x8ee0, mload(add(X1_POWERS_MPTR, 0x3c0))) + mcopy(0x8f00, 0x4b80, 0x80) + mstore(0x8f80, mload(add(X1_POWERS_MPTR, 0x3e0))) let lin_query_scalar_31 := mload(add(X1_POWERS_MPTR, 0x400)) let lin_cur_scalar_31 := mulmod(lin_query_scalar_31, lin_one_minus_x_n, r) - mcopy(0x8020, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) - mstore(0x80a0, lin_cur_scalar_31) + mcopy(0x8fa0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x0), 0x80) + mstore(0x9020, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x80c0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) - mstore(0x8140, lin_cur_scalar_31) + mcopy(0x9040, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x80), 0x80) + mstore(0x90c0, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x8160, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) - mstore(0x81e0, lin_cur_scalar_31) + mcopy(0x90e0, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x100), 0x80) + mstore(0x9160, lin_cur_scalar_31) lin_cur_scalar_31 := mulmod(lin_cur_scalar_31, lin_x_split, r) - mcopy(0x8200, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) - mstore(0x8280, lin_cur_scalar_31) - mcopy(0x82a0, 0x3000, 0x80) - mstore(0x8320, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) - mcopy(0x8340, 0x3080, 0x80) - mstore(0x83c0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) - mcopy(0x83e0, 0x3100, 0x80) - mstore(0x8460, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) - mcopy(0x8480, 0x3280, 0x80) - mstore(0x8500, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) - mcopy(0x8520, 0x3300, 0x80) - mstore(0x85a0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) - mcopy(0x85c0, 0x3380, 0x80) - mstore(0x8640, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) - mcopy(0x8660, 0x3400, 0x80) - mstore(0x86e0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) - mcopy(0x8700, 0x3480, 0x80) - mstore(0x8780, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) - mcopy(0x87a0, 0x3500, 0x80) - mstore(0x8820, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) - mcopy(0x8840, 0x3580, 0x80) - mstore(0x88c0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) - mcopy(0x88e0, 0x3600, 0x80) - mstore(0x8960, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x140)), r)) - mcopy(0x8980, 0x3680, 0x80) - mstore(0x8a00, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x160)), r)) - mcopy(0x8a20, 0x3700, 0x80) - mstore(0x8aa0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x180)), r)) - mcopy(0x8ac0, 0x3780, 0x80) - mstore(0x8b40, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x1a0)), r)) - mcopy(0x8b60, 0x6680, 0x80) - mstore(0x8be0, x4_pow_1) - mcopy(0x8c00, 0x6800, 0x80) - mstore(0x8c80, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) - mcopy(0x8ca0, 0x6880, 0x80) - mstore(0x8d20, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) - mcopy(0x8d40, 0x6080, 0x80) - mstore(0x8dc0, x4_pow_2) - mcopy(0x8de0, 0x6100, 0x80) - mstore(0x8e60, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) - mcopy(0x8e80, 0x6180, 0x80) - mstore(0x8f00, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) - mcopy(0x8f20, 0x6200, 0x80) - mstore(0x8fa0, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_2, r)) - mcopy(0x8fc0, 0x6280, 0x80) - mstore(0x9040, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_2, r)) - mcopy(0x9060, 0x6300, 0x80) - mstore(0x90e0, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_2, r)) - mcopy(0x9100, 0x6380, 0x80) - mstore(0x9180, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_2, r)) - mcopy(0x91a0, 0x6400, 0x80) - mstore(0x9220, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_2, r)) - mcopy(0x9240, 0x6580, 0x80) - mstore(0x92c0, x4_pow_3) - mcopy(0x92e0, 0x6600, 0x80) - mstore(0x9360, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) - mcopy(0x9380, F_COM_MPTR, 0x80) - mstore(0x9400, x4_pow_4) + mcopy(0x9180, add(QUOTIENT_LIMB_COMMS_MPTR_BASE, 0x180), 0x80) + mstore(0x9200, lin_cur_scalar_31) + mcopy(0x9220, 0x3f80, 0x80) + mstore(0x92a0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x0)), r)) + mcopy(0x92c0, 0x4000, 0x80) + mstore(0x9340, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x20)), r)) + mcopy(0x9360, 0x4080, 0x80) + mstore(0x93e0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x40)), r)) + mcopy(0x9400, 0x4200, 0x80) + mstore(0x9480, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x60)), r)) + mcopy(0x94a0, 0x4280, 0x80) + mstore(0x9520, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x80)), r)) + mcopy(0x9540, 0x4300, 0x80) + mstore(0x95c0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xa0)), r)) + mcopy(0x95e0, 0x4380, 0x80) + mstore(0x9660, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xc0)), r)) + mcopy(0x9680, 0x4400, 0x80) + mstore(0x9700, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0xe0)), r)) + mcopy(0x9720, 0x4480, 0x80) + mstore(0x97a0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x100)), r)) + mcopy(0x97c0, 0x4500, 0x80) + mstore(0x9840, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x120)), r)) + mcopy(0x9860, 0x4580, 0x80) + mstore(0x98e0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x140)), r)) + mcopy(0x9900, 0x4600, 0x80) + mstore(0x9980, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x160)), r)) + mcopy(0x99a0, 0x4680, 0x80) + mstore(0x9a20, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x180)), r)) + mcopy(0x9a40, 0x4700, 0x80) + mstore(0x9ac0, mulmod(lin_query_scalar_31, mload(add(SELECTOR_ACC_MPTR, 0x1a0)), r)) + mcopy(0x9ae0, 0x7600, 0x80) + mstore(0x9b60, x4_pow_1) + mcopy(0x9b80, 0x7780, 0x80) + mstore(0x9c00, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_1, r)) + mcopy(0x9c20, 0x7800, 0x80) + mstore(0x9ca0, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_1, r)) + mcopy(0x9cc0, 0x7000, 0x80) + mstore(0x9d40, x4_pow_2) + mcopy(0x9d60, 0x7080, 0x80) + mstore(0x9de0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_2, r)) + mcopy(0x9e00, 0x7100, 0x80) + mstore(0x9e80, mulmod(mload(add(X1_POWERS_MPTR, 0x40)), x4_pow_2, r)) + mcopy(0x9ea0, 0x7180, 0x80) + mstore(0x9f20, mulmod(mload(add(X1_POWERS_MPTR, 0x60)), x4_pow_2, r)) + mcopy(0x9f40, 0x7200, 0x80) + mstore(0x9fc0, mulmod(mload(add(X1_POWERS_MPTR, 0x80)), x4_pow_2, r)) + mcopy(0x9fe0, 0x7280, 0x80) + mstore(0xa060, mulmod(mload(add(X1_POWERS_MPTR, 0xa0)), x4_pow_2, r)) + mcopy(0xa080, 0x7300, 0x80) + mstore(0xa100, mulmod(mload(add(X1_POWERS_MPTR, 0xc0)), x4_pow_2, r)) + mcopy(0xa120, 0x7380, 0x80) + mstore(0xa1a0, mulmod(mload(add(X1_POWERS_MPTR, 0xe0)), x4_pow_2, r)) + mcopy(0xa1c0, 0x7500, 0x80) + mstore(0xa240, x4_pow_3) + mcopy(0xa260, 0x7580, 0x80) + mstore(0xa2e0, mulmod(mload(add(X1_POWERS_MPTR, 0x20)), x4_pow_3, r)) + mcopy(0xa300, F_COM_MPTR, 0x80) + mstore(0xa380, x4_pow_4) if success { - success := staticcall(gas(), 0x0c, 0x6cc0, 0x2760, FINAL_COM_MPTR, 0x80) + success := staticcall(gas(), 0x0c, 0x7c40, 0x2760, FINAL_COM_MPTR, 0x80) success := and(success, eq(returndatasize(), 0x80)) } mstore(V_MPTR, v) @@ -2928,28 +3026,28 @@ contract Halo2Verifier { // Scale z*pi - vG before the final pairing check // pairing inputs (LHS = pi; RHS = final_com - v*G + x3*pi) mcopy(PAIRING_LHS_MPTR, PI_MPTR, 0x80) - mcopy(0x80, G1_BASE_MPTR, 0x80) - mstore(0x100, addmod(0, sub(r, mload(V_MPTR)), r)) + mcopy(0x1000, G1_BASE_MPTR, 0x80) + mstore(0x1080, addmod(0, sub(r, mload(V_MPTR)), r)) if success { - success := staticcall(gas(), 0x0c, 0x80, 0xa0, 0x80, 0x80) + success := staticcall(gas(), 0x0c, 0x1000, 0xa0, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, FINAL_COM_MPTR, 0x80) + mcopy(0x1080, FINAL_COM_MPTR, 0x80) if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(0x100, PI_MPTR, 0x80) - mstore(0x180, mload(X3_MPTR)) + mcopy(0x1080, PI_MPTR, 0x80) + mstore(0x1100, mload(X3_MPTR)) if success { - success := staticcall(gas(), 0x0c, 0x100, 0xa0, 0x100, 0x80) + success := staticcall(gas(), 0x0c, 0x1080, 0xa0, 0x1080, 0x80) success := and(success, eq(returndatasize(), 0x80)) } if success { - success := staticcall(gas(), 0x0b, 0x80, 0x100, 0x80, 0x80) + success := staticcall(gas(), 0x0b, 0x1000, 0x100, 0x1000, 0x80) success := and(success, eq(returndatasize(), 0x80)) } - mcopy(PAIRING_RHS_MPTR, 0x80, 0x80) + mcopy(PAIRING_RHS_MPTR, 0x1000, 0x80) } } @@ -2987,6 +3085,12 @@ contract Halo2Verifier { // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) } diff --git a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol index 0ee83d389..dc1527a1e 100644 --- a/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol +++ b/proofs/solidity-verifier/target/sha-preimage-fixture-dump/Halo2VerifyingKey.sol @@ -116,19 +116,19 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x0660), 0x0000000000000000000000000000000000000000000000000000000000040011) // quotient_const mstore(add(payload, 0x0680), 0x0000000000000000000000000000000000000000000000000000001100000000) // quotient_const mstore(add(payload, 0x06a0), 0x0000000000000000000000000000000000000000000000000000000044000000) // quotient_const - mstore(add(payload, 0x06c0), 0x0000000000000000000000000000000000000000000000000000000000200000) // quotient_const - mstore(add(payload, 0x06e0), 0x0000000000000000000000000000000000000000000000000000000000000400) // quotient_const - mstore(add(payload, 0x0700), 0x0000000000000000000000000000000000000000000000000000000000400000) // quotient_const + mstore(add(payload, 0x06c0), 0x0000000000000000000000000000000000000000000000000000000000000400) // quotient_const + mstore(add(payload, 0x06e0), 0x0000000000000000000000000000000000000000000000000000000000200000) // quotient_const + mstore(add(payload, 0x0700), 0x0000000000000000000000000000000000000000000000000000000000000004) // quotient_const mstore(add(payload, 0x0720), 0x0000000000000000000000000000000000000000000000000000000000002000) // quotient_const - mstore(add(payload, 0x0740), 0x0000000000000000000000000000000000000000000000000000000000000004) // quotient_const - mstore(add(payload, 0x0760), 0x0000000000000000000000000000000000000000000000000000100000000000) // quotient_const + mstore(add(payload, 0x0740), 0x0000000000000000000000000000000000000000000000000000000000400000) // quotient_const + mstore(add(payload, 0x0760), 0x0000000000000000000000000000000000000000000000000000000000000010) // quotient_const mstore(add(payload, 0x0780), 0x0000000000000000000000000000000000000000000000000000000004000000) // quotient_const - mstore(add(payload, 0x07a0), 0x0000000000000000000000000000000000000000000000000000000000000010) // quotient_const - mstore(add(payload, 0x07c0), 0x0000000000000000000000000000000000000000000000000000000002000000) // quotient_const + mstore(add(payload, 0x07a0), 0x0000000000000000000000000000000000000000000000000000100000000000) // quotient_const + mstore(add(payload, 0x07c0), 0x0000000000000000000000000000000000000000000000000000000000000040) // quotient_const mstore(add(payload, 0x07e0), 0x0000000000000000000000000000000000000000000000000000000000000800) // quotient_const - mstore(add(payload, 0x0800), 0x0000000000000000000000000000000000000000000000000000000000000040) // quotient_const - mstore(add(payload, 0x0820), 0x0000000000000000000000000000000000000000000000000004000000000000) // quotient_const - mstore(add(payload, 0x0840), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x0800), 0x0000000000000000000000000000000000000000000000000000000002000000) // quotient_const + mstore(add(payload, 0x0820), 0x0000000000000000000000000000000000000000000000000000000000001000) // quotient_const + mstore(add(payload, 0x0840), 0x0000000000000000000000000000000000000000000000000004000000000000) // quotient_const mstore(add(payload, 0x0860), 0x0000000000000000000000000000000000000000000000000000000000040000) // quotient_const mstore(add(payload, 0x0880), 0x0000000000000000000000000000000000000000000000000000000000000080) // quotient_const mstore(add(payload, 0x08a0), 0x0000000000000000000000000000000000000000000000000000000000000008) // quotient_const @@ -136,19 +136,19 @@ contract Halo2VerifyingKey { mstore(add(payload, 0x08e0), 0x0000000000000000000000000000000000000000000000000000000000080000) // quotient_const mstore(add(payload, 0x0900), 0x0000000000000000000000000000000000000000000000000000000000020000) // quotient_const mstore(add(payload, 0x0920), 0x0000000000000000000000000000000000000000000000000000000100000000) // quotient_const - mstore(add(payload, 0x0940), 0x055860105b8005590008060d000b0200011b00001b00010558601058800558a0) // quotient_program - mstore(add(payload, 0x0960), 0x08060d000b0400011b00021b0003210001000007000158200258400358600458) // quotient_program - mstore(add(payload, 0x0980), 0x800558a00658c00758e00859000959800a59a00b59c00c59e00d5a000e5a200b) // quotient_program - mstore(add(payload, 0x09a0), 0x070000210001000007000158200258400f58601058801158a00658c00758e012) // quotient_program - mstore(add(payload, 0x09c0), 0x59001359801459a00b59c00c59e0155a00165a200b0800000917115a40135940) // quotient_program - mstore(add(payload, 0x09e0), 0x18105a60055a8008060d000b0900000919115a40135aa01a1359401b10596005) // quotient_program - mstore(add(payload, 0x0a00), 0x5a8008060d000b0a0000091c1159c01359e01d1358201e10584005586008060d) // quotient_program - mstore(add(payload, 0x0a20), 0x000b0a0001091f115a40135aa01a1359402013596021105a60055a8008060d00) // quotient_program - mstore(add(payload, 0x0a40), 0x0b0b000009221159c01359e01d13582019135840231058c005586008060d000b) // quotient_program - mstore(add(payload, 0x0a60), 0x0b00011c245920255940265960275a40185aa0285ac0295ae0105a60055a8008) // quotient_program - mstore(add(payload, 0x0a80), 0x060d000b0c0000090008105ac0115ac00d000b0c00010900081059201159200d) // quotient_program - mstore(add(payload, 0x0aa0), 0x000b0c0001090008105ae0115ae00d000b0c00011c0058800058a00059000059) // quotient_program - mstore(add(payload, 0x0ac0), 0x800059a0005a00005a20055a80135b002a08060d000b0d0000191f0000000000) // quotient_program + mstore(add(payload, 0x0940), 0x0567e0106b0005688008060d000b0200011b00001b00010567e0106800056820) // quotient_program + mstore(add(payload, 0x0960), 0x08060d000b0400011b00021b0003210001000007000167a00267c00367e00468) // quotient_program + mstore(add(payload, 0x0980), 0x000568200668400768600868800969000a69200b69400c69600d69800e69a00b) // quotient_program + mstore(add(payload, 0x09a0), 0x070000210001000007000167a00267c00f67e010680011682006684007686012) // quotient_program + mstore(add(payload, 0x09c0), 0x68801369001469200b69400c69601569801669a00b08000009181169c01368c0) // quotient_program + mstore(add(payload, 0x09e0), 0x171069e0056a0008060d000b090000091b1169c0136a201a1368c0191068e005) // quotient_program + mstore(add(payload, 0x0a00), 0x6a0008060d000b0a0000091e1169401369601d1367a01c1067c00567e008060d) // quotient_program + mstore(add(payload, 0x0a20), 0x000b0a000109211169c0136a201a1368c0201368e01f1069e0056a0008060d00) // quotient_program + mstore(add(payload, 0x0a40), 0x0b0b000009231169401369601d1367a01b1367c0221068400567e008060d000b) // quotient_program + mstore(add(payload, 0x0a60), 0x0b00011c2468a02568c02668e02769c0176a20286a40296a601069e0056a0008) // quotient_program + mstore(add(payload, 0x0a80), 0x060d000b0c0000090008106a40116a400d000b0c00010900081068a01168a00d) // quotient_program + mstore(add(payload, 0x0aa0), 0x000b0c0001090008106a60116a600d000b0c00011c0068000068200068800069) // quotient_program + mstore(add(payload, 0x0ac0), 0x000069200069800069a0056a00136a802a08060d000b0d0000191f0000000000) // quotient_program // Fixed-column commitment 0, stored as one // EIP-2537 padded uncompressed G1 slot. mstore(add(payload, 0x0ae0), 0x000000000000000000000000000000001197f0fef4c3a1846341b3c9bbaf1bab) // fixed_comms[0].x_hi diff --git a/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul b/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul index e206aed4e..f4de01c7b 100644 --- a/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul +++ b/proofs/solidity-verifier/templates/partials/quotient_numerator/QuotientNumeratorBlock.yul @@ -126,7 +126,14 @@ { // q_y_power holds y^i at the current loop index. let q_y_power := 1 - // Start at i=1 because y^0 = 1 is implicit and never read. + // Slot 0 holds y^0 = 1. Codegen never emits a read of it + // (FOLD_SELECTOR guards on a nonzero gap, and + // selector_tail_updates drops zero tails), but the tail + // block multiplies by mload(selector_power_mptr + offset) + // unconditionally -- so initialize the slot rather than + // leaving correctness to two filters in another file. + mstore({{ program.selector_power_mptr|hex() }}, 1) + // Start at i=1 because y^0 = 1 is written above. for { let q_y_power_i := 1 } lt(q_y_power_i, {{ program.selector_max_power + 1 }}) { q_y_power_i := add(q_y_power_i, 1) } { // Advance from y^(i-1) to y^i modulo Fr. q_y_power := mulmod(q_y_power, y, r) @@ -1190,6 +1197,12 @@ // over-reads operands or leaves a partial expression live. if iszero(eq(q_pc, q_end)) { revert(0, 0) } if q_has_top { revert(0, 0) } + // The spilled stack must also be balanced. A FOLD executed + // with more than one operand live consumes only the cached + // top, leaving abandoned words below q_sp with q_has_top + // clear -- so both checks above pass while an operand of the + // identity has been silently dropped from nu_y(x). + if iszero(eq(q_sp, {{ program.stack_mptr|hex() }})) { revert(0, 0) } // Structured post-VM suffix. The current default uses this for // regular trash constraints: it is smaller than fully unrolled diff --git a/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul b/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul index 3a5d5eb77..448df6991 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/AccumulatorHelpers.yul @@ -25,7 +25,13 @@ // public input. `first_adjust` removes the identity flag // base from the first x word when present. let packed := calldataload(add(src, mul(div(i, limbs_per_word), 0x20))) - if and(iszero(div(i, limbs_per_word)), first_adjust) { + // `and` here is bitwise, so it must not be fed the raw + // `first_adjust` (a radix base, i.e. a high power of two): + // `iszero(...)` is 0 or 1 and shares no bit with it, which + // would make the guard false for every call. Subtracting is + // already a no-op when `first_adjust` is zero, so gate on + // the word index alone. + if iszero(div(i, limbs_per_word)) { packed := sub(packed, first_adjust) } // Select limb i from its packed field word. The mod/div @@ -259,6 +265,11 @@ // Carried-scalar layout: the circuit exposes the scalar // that multiplies the carried LHS point. let lhs_scalar := calldataload(lhs_scalar_ptr) + // Canonicality is enforced here rather than relying on the + // later instance-absorption loop: G1MSM reduces scalars + // mod r implicitly, so s and s+r would be indistinguishable + // inside this helper. + out := and(out, lt(lhs_scalar, r)) {%- else %} // Already-collapsed point-pair layout: carried scalars are // implicit one. @@ -319,6 +330,7 @@ {%- if self.expected_acc_has_carried_scalars %} // Explicit carried RHS scalar. let rhs_scalar := calldataload(rhs_scalar_ptr) + out := and(out, lt(rhs_scalar, r)) {%- else %} // Implicit unit scalar for already-collapsed point pairs. let rhs_scalar := 1 @@ -347,6 +359,9 @@ // corresponding base point is embedded in verifier memory at // {{ base_mptr|hex() }}. let fixed_scalar_{{ loop.index0 }} := calldataload(fixed_scalar_ptr) + // Reject non-canonical tail scalars before the negation below: + // for s >= r, `mod(sub(r, s), r)` is not -s mod r. + out := and(out, lt(fixed_scalar_{{ loop.index0 }}, r)) {%- if negate_scalar %} // Some accumulator bases are represented with a negated scalar // so the MSM can reuse the generated positive base point. diff --git a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul index f3ddbfc73..5ba918b36 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/AssemblyHelpers.yul @@ -5,7 +5,13 @@ // when the VK payload becomes smaller. function scalar_inv(x) -> inv { // Zero has no multiplicative inverse in Fr; callers rely on a - // revert here rather than a bogus modexp result. + // revert here rather than a bogus modexp result. Check the + // full canonical range, not just the literal word 0: for any + // x congruent to 0 mod r (x = r, say) modexp returns 0, which + // downstream mulmod chains would silently absorb. Every + // current call site feeds addmod/mulmod output, so this only + // guards against a future emitter passing a raw scalar. + if iszero(lt(x, FR_MODULUS)) { revert(0, 0) } if iszero(x) { revert(0, 0) } let p := {{ memory.scalar_inv_scratch_mptr|hex() }} // EIP-198 modexp frame: @@ -142,6 +148,13 @@ // just run one modexp inverse in place. if eq(count_bytes, 0x20) { let x := mload(mptr_start) + // Reject anything congruent to zero mod r, not just the + // literal word 0: modexp would return 0 for those too, and + // the caller would take it for a valid inverse. + if iszero(lt(x, r)) { + ret := 0 + leave + } if iszero(x) { ret := 0 leave @@ -162,16 +175,34 @@ // Forward pass: scratch stores prefix products up to, but not // including, the final element. `gp` becomes the total product. + // + // Match the single-element path: reject non-canonical words + // (x >= r) instead of letting mulmod reduce them silently, so + // accept/reject semantics do not depend on batch length. let gp_mptr := scratch_mptr let gp := mload(mptr_start) + if iszero(lt(gp, r)) { + ret := 0 + leave + } let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) {} { - gp := mulmod(gp, mload(mptr), r) + let x := mload(mptr) + if iszero(lt(x, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x, r) mstore(gp_mptr, gp) mptr := add(mptr, 0x20) gp_mptr := add(gp_mptr, 0x20) } - gp := mulmod(gp, mload(mptr), r) + let x_last := mload(mptr) + if iszero(lt(x_last, r)) { + ret := 0 + leave + } + gp := mulmod(gp, x_last, r) // A zero total product means at least one denominator was // zero, so no batch inverse exists. if iszero(gp) { @@ -188,6 +219,12 @@ mstore(add(gp_mptr, {{ template_constants.modexp.mod_offset|hex() }}), r) ret := staticcall(gas(), {{ template_constants.modexp.address|hex() }}, gp_mptr, {{ template_constants.modexp.frame_bytes|hex() }}, gp_mptr, {{ template_constants.modexp.output_bytes|hex() }}) ret := and(ret, eq(returndatasize(), {{ template_constants.modexp.output_bytes|hex() }})) + // Leave before the backward pass on a failed modexp. A failed + // staticcall writes no output, so `mload(gp_mptr)` would read + // back the stale frame header and the pass below would + // overwrite every denominator in [mptr_start, mptr_end) with + // garbage products before returning ret = 0. + if iszero(ret) { leave } let all_inv := mload(gp_mptr) // Backward pass: derive each inverse from the inverted total @@ -212,7 +249,12 @@ // 4-word G1 slots; G2 bases are loaded from the pinned VK payload. function ec_pairing(success, lhs_mptr, rhs_mptr) -> ret { ret := success - if iszero(ret) { leave } + // Every other exit from this function reverts, and the + // terminal `return(RETURN_MPTR, 0x20)` in TraceReturn.yul + // returns true without consulting `success`. Revert here too, + // so this helper has no path that hands control back to a + // caller that would report success for an unverified proof. + if iszero(ret) { revert(0, 0) } // Lay out two (G1, G2) pairs at scratch..scratch+0x300: // [lhs_g1 (0x80) | G2_BASE (0x100) | rhs_g1 (0x80) | NEG_S_G2_BASE (0x100)] // Cancun MCOPY (3 + 3·words gas) replaces what used to @@ -226,7 +268,11 @@ mcopy(add(scratch, 0x200), NEG_S_G2_BASE_MPTR, 0x100) ret := staticcall(gas(), {{ template_constants.eip2537.pairing_address|hex() }}, scratch, {{ template_constants.pairing_two_pair_bytes|hex() }}, scratch, {{ template_constants.word_bytes|hex() }}) ret := and(ret, eq(returndatasize(), {{ template_constants.word_bytes|hex() }})) - ret := and(ret, mload(scratch)) + // Compare against 1 rather than truncating to the low bit: + // `and(ret, word)` would accept any odd result word. EIP-2537 + // only ever returns 0 or 1, so this matches the strict form + // the constructor smoke test already uses. + ret := and(ret, eq(mload(scratch), 1)) if iszero(ret) { revert(0, 0) } ret := 1 } diff --git a/proofs/solidity-verifier/templates/partials/verifier/PrecompileSmoke.sol b/proofs/solidity-verifier/templates/partials/verifier/PrecompileSmoke.sol index baa6e6180..5fed51133 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/PrecompileSmoke.sol +++ b/proofs/solidity-verifier/templates/partials/verifier/PrecompileSmoke.sol @@ -27,6 +27,39 @@ revert(0, 0) } + // Known-answer probe: G1ADD(G, G) == 2G. + // + // Every probe above uses the point at infinity, which is exactly + // the input an implementation gets right without doing any curve + // arithmetic -- a precompile that returns its zero-filled input, or + // zeros for anything, satisfies them. The identity is also the one + // input on which an implementation that omits the EIP-2537 subgroup + // check still answers correctly, and the production verifier leans + // on G1MSM as its subgroup validator for absorbed commitments. So + // add one vector whose answer a stub cannot guess. + mstore(add(scratch, 0x00), {{ template_constants.eip2537.g1_generator.0|hex_padded(64) }}) + mstore(add(scratch, 0x20), {{ template_constants.eip2537.g1_generator.1|hex_padded(64) }}) + mstore(add(scratch, 0x40), {{ template_constants.eip2537.g1_generator.2|hex_padded(64) }}) + mstore(add(scratch, 0x60), {{ template_constants.eip2537.g1_generator.3|hex_padded(64) }}) + mcopy(add(scratch, {{ template_constants.g1_bytes|hex() }}), scratch, {{ template_constants.g1_bytes|hex() }}) + if iszero(staticcall(gas(), {{ template_constants.eip2537.g1add_address|hex() }}, scratch, {{ template_constants.g1add_input_bytes|hex() }}, scratch, {{ template_constants.g1_bytes|hex() }})) { revert(0, 0) } + if iszero(eq(returndatasize(), {{ template_constants.g1_bytes|hex() }})) { revert(0, 0) } + if iszero(and( + and( + eq(mload(add(scratch, 0x00)), {{ template_constants.eip2537.g1_double_generator.0|hex_padded(64) }}), + eq(mload(add(scratch, 0x20)), {{ template_constants.eip2537.g1_double_generator.1|hex_padded(64) }}) + ), + and( + eq(mload(add(scratch, 0x40)), {{ template_constants.eip2537.g1_double_generator.2|hex_padded(64) }}), + eq(mload(add(scratch, 0x60)), {{ template_constants.eip2537.g1_double_generator.3|hex_padded(64) }}) + ) + )) { revert(0, 0) } + + // Restore the identity encoding for the probes below. + for { let off := 0 } lt(off, {{ template_constants.eip2537.smoke_scratch_bytes|hex() }}) { off := add(off, {{ template_constants.word_bytes|hex() }}) } { + mstore(add(scratch, off), 0) + } + // Worst-case generated G1MSM with all identity/zero terms -> // identity, 128-byte return. This exercises the largest MSM input // length rendered by this verifier instead of only a one-pair diff --git a/proofs/solidity-verifier/templates/partials/verifier/TraceReturn.yul b/proofs/solidity-verifier/templates/partials/verifier/TraceReturn.yul index 5f040942c..7d3f27f40 100644 --- a/proofs/solidity-verifier/templates/partials/verifier/TraceReturn.yul +++ b/proofs/solidity-verifier/templates/partials/verifier/TraceReturn.yul @@ -54,5 +54,11 @@ // Success path is terminal. Invalid inputs have already reverted, // so the Solidity ABI observes `true`. + // + // The guard is redundant today -- every failure path above reverts + // rather than clearing `success` -- but it keeps acceptance a local + // property of this file instead of an invariant split across + // FinalPairing.yul and ec_pairing. + if iszero(success) { revert(0, 0) } mstore(RETURN_MPTR, 1) return(RETURN_MPTR, 0x20) diff --git a/proofs/solidity-verifier/tests/hybrid_mt_fixture.rs b/proofs/solidity-verifier/tests/hybrid_mt_fixture.rs index 4a94bbdd7..baaa8da6d 100644 --- a/proofs/solidity-verifier/tests/hybrid_mt_fixture.rs +++ b/proofs/solidity-verifier/tests/hybrid_mt_fixture.rs @@ -193,13 +193,17 @@ fn hybrid_mt_renders_compiles_and_verifies() { let srs_dir = srs_dir(); let srs_path = format!("{srs_dir}/bls_filecoin_2p{K}"); let fallback_srs_path = format!("{srs_dir}/bls_filecoin_2p19"); - if !Path::new(&srs_path).exists() && !Path::new(&fallback_srs_path).exists() { - eprintln!( - "skipping hybrid MT Solidity smoke: SRS not found at {srs_path} or \ - {fallback_srs_path}. Set SRS_DIR or fetch the asset under midfall/zk_stdlib." - ); - return; - } + // The gate was explicitly requested, so a missing asset fails rather than + // silently reporting a pass that rendered and compiled nothing. + assert!( + Path::new(&srs_path).exists() || Path::new(&fallback_srs_path).exists(), + "{RUN_EVM_TESTS_ENV}=1 requires the test SRS, but it was not found at {srs_path} or \ + {fallback_srs_path}. +Fetch it with: + curl -L -o {fallback_srs_path} \ + https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19 +or point SRS_DIR at an existing copy." + ); env::set_var("SRS_DIR", &srs_dir); let relation = HybridMtCircuit; @@ -263,10 +267,13 @@ fn hybrid_mt_renders_compiles_and_verifies() { verifier_solidity.len() ); - if !pinned_solc_available() { - eprintln!("skipping hybrid MT EVM smoke: pinned solc not available"); - return; - } + assert!( + pinned_solc_available(), + "{RUN_EVM_TESTS_ENV}=1 requires the pinned solc, which was not found or did not match. +\ + Install it, point SOLC at the binary, or set \ + HALO2_SOLIDITY_ALLOW_UNPINNED_SOLC=1 to accept another version." + ); let vk_creation_code = compile_solidity(&vk_solidity); let verifier_creation_code = compile_solidity(&verifier_solidity); diff --git a/proofs/solidity-verifier/tests/ivc_accumulator_replay.rs b/proofs/solidity-verifier/tests/ivc_accumulator_replay.rs new file mode 100644 index 000000000..a8f000c4f --- /dev/null +++ b/proofs/solidity-verifier/tests/ivc_accumulator_replay.rs @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: CC0-1.0 +//! CI-runnable adversarial replay of the public-accumulator decode path. +//! +//! The accumulator decoder in `templates/partials/verifier/AccumulatorHelpers.yul` +//! had no executing test coverage. The tests that looked like they covered it +//! -- `accumulator_decoder_rejects_noncanonical_infinity` and friends in +//! `src/lowering/tests.rs` -- are `verifier_template.contains("...")` string +//! greps over the raw template. They assert the guard *text* exists and never +//! render, compile, or run it. That is why the always-false `and` guard in +//! `load_acc_coord_shifted` survived: those greps passed for as long as the +//! identity branch was dead code. +//! +//! The only executing accumulator tests live in `tests/ivc_keccak_solidity.rs`, +//! which proves a k=20 decider from scratch and needs ~300 MB of SRS, so it is +//! gated behind `HALO2_SOLIDITY_RUN_IVC_BENCH=1` and never runs in CI. +//! +//! This test closes that gap by replaying *pre-rendered* artifacts. Because it +//! ships the generated Solidity and the matching calldata rather than a +//! verifying key, it needs neither SRS nor a proving run nor +//! `midnight-aggregation` -- only solc and revm. A verifier cannot be rendered +//! from a VK without the full SRS (`SolidityGenerator` consumes +//! `params.g_lagrange()`), which is what rules out a vk.bin-based replay. +//! +//! Two fixtures are replayed, covering both accumulator encodings: +//! +//! - `fixtures/ivc` -- the IVC Keccak decider, `AccumulatorEncoding::new`, +//! which carries explicit lhs/rhs scalars. +//! - `fixtures/moonlight-wrap` -- the Moonlight wrap decider, `point_pair`, +//! which does not. Its `expected_acc_has_carried_scalars = false` arms were +//! previously only ever compiled, never executed against a proof. +//! +//! Each fixture's README records its provenance and regeneration command. +//! +//! A fixture describes itself: the accumulator offset, limb count and +//! `has_accumulator` flag are parsed back out of the rendered verifying-key +//! payload, the encoding kind is recovered from the payload width, and the +//! infinity encoding comes from the verifier's own constants. So this file +//! carries no per-fixture constants that could drift from the artifacts. +//! +//! Staleness caveat: these are snapshots of the codegen that produced them. +//! A fixture is self-consistent, so the replay keeps passing after a codegen +//! change -- it just stops testing current output. The commit stamp in each +//! README makes drift auditable; detecting it automatically would require +//! re-rendering, which needs the SRS again. + +#![cfg(feature = "evm")] + +use std::path::PathBuf; + +use halo2_solidity_verifier::{compile_solidity_with_runs, CallOutcome, Evm}; + +/// The IVC verifier is large, so it is rendered and benched at `runs = 1`. +const SOLC_OPTIMIZE_RUNS: u32 = 1; +/// Generous cap so an unexpected loop reports OutOfGas rather than masquerading +/// as a revert. +const GAS_CAP: u64 = 5_000_000_000; +/// ABI prologue: selector, proof head, instances head, then the proof length +/// word. The proof payload starts immediately after. +const PROOF_PAYLOAD_START: usize = 4 + 0x40 + 0x20; + +fn fixture_dir(fixture: &str) -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures").join(fixture) +} + +fn read_fixture(fixture: &str, name: &str) -> Vec { + let path = fixture_dir(fixture).join(name); + std::fs::read(&path).unwrap_or_else(|err| panic!("missing fixture {}: {err}", path.display())) +} + +fn read_fixture_string(fixture: &str, name: &str) -> String { + String::from_utf8(read_fixture(fixture, name)).expect("fixture should be UTF-8") +} + +/// Read an optional fixture file. Split renders ship a quotient evaluator; +/// single-contract renders do not. +fn read_optional_fixture_string(fixture: &str, name: &str) -> Option { + let path = fixture_dir(fixture).join(name); + std::fs::read_to_string(path).ok() +} + +/// Read a labelled VK payload word out of the rendered verifying-key source. +/// +/// The generator emits each header word as +/// `mstore(add(payload, 0x...), 0x...) // name`, so the fixture describes its +/// own accumulator placement and this test does not have to carry a second +/// copy that could drift out of sync with the artifact. +fn vk_payload_word(vk_solidity: &str, name: &str) -> u64 { + let suffix = format!("// {name}"); + let line = vk_solidity + .lines() + .map(str::trim) + .find(|line| line.starts_with("mstore(") && line.ends_with(&suffix)) + .unwrap_or_else(|| panic!("verifying-key source has no `{name}` payload word")); + let value = line + .rsplit_once("0x") + .expect("payload word should be hex") + .1 + .split_whitespace() + .next() + .expect("payload word should have a value") + .trim_end_matches(')'); + u64::from_str_radix(value.trim_start_matches('0'), 16).unwrap_or(0) +} + +fn read_u256_word(calldata: &[u8], offset: usize) -> u64 { + let word = &calldata[offset..offset + 0x20]; + assert!( + word[..24].iter().all(|b| *b == 0), + "word at {offset:#x} does not fit in u64" + ); + u64::from_be_bytes(word[24..].try_into().unwrap()) +} + +fn assert_reverts(outcome: CallOutcome, case: &str) { + match outcome { + CallOutcome::Revert { .. } => {} + CallOutcome::Success { output, .. } => panic!( + "{case}: verifier accepted a proof it must reject (output = 0x{})", + hex::encode(output) + ), + CallOutcome::Halt { reason, .. } => { + panic!("{case}: expected a revert but the call halted ({reason})") + } + } +} + +/// The IVC decider carries explicit lhs/rhs scalars +/// (`AccumulatorEncoding::new`). +#[test] +fn ivc_accumulator_decoder_rejects_malformed_public_accumulator() { + replay_accumulator_fixture("ivc"); +} + +/// The Moonlight wrap decider uses the scalar-free `point_pair` encoding, whose +/// `expected_acc_has_carried_scalars = false` arms were previously only ever +/// compiled, never executed against a proof. +#[test] +fn wrap_point_pair_decoder_rejects_malformed_public_accumulator() { + replay_accumulator_fixture("moonlight-wrap"); +} + +/// Replay a rendered accumulator fixture, then mutate the proof and public +/// inputs and assert every mutation is rejected. +/// +/// The accept baseline is what gives the rejections meaning: without it a +/// "rejects" assertion could pass because the verifier rejects everything. +fn replay_accumulator_fixture(fixture: &str) { + let verifier_solidity = read_fixture_string(fixture, "Halo2Verifier.sol"); + let vk_solidity = read_fixture_string(fixture, "Halo2VerifyingKey.sol"); + let calldata = read_fixture(fixture, "calldata.bin"); + + assert_eq!( + vk_payload_word(&vk_solidity, "has_accumulator"), + 1, + "fixture must be a public-accumulator render, otherwise this test \ + exercises none of AccumulatorHelpers.yul" + ); + let final_acc_offset = vk_payload_word(&vk_solidity, "acc_offset") as usize; + let num_acc_limbs = vk_payload_word(&vk_solidity, "num_acc_limbs"); + + // Derive the accumulator's calldata position from the ABI layout; the + // fixture describes its own instance-space offset via the VK payload. + let proof_len = read_u256_word(&calldata, PROOF_PAYLOAD_START - 0x20) as usize; + let instances_len_word = PROOF_PAYLOAD_START + proof_len; + let instance_count = read_u256_word(&calldata, instances_len_word); + let first_acc_word = instances_len_word + 0x20 + final_acc_offset * 0x20; + // 7 limbs of 56 bits pack 4 to a field element, so each coordinate takes 2 + // words and each point 4. + assert_eq!( + num_acc_limbs, 7, + "limb count changed; the word arithmetic below no longer holds" + ); + + // Recover the encoding kind from the payload width rather than hardcoding + // it per fixture: the accumulator occupies the instance tail, so eight + // words is `point_pair` and ten is the scalar-carrying encoding. + let acc_words = instance_count as usize - final_acc_offset; + let has_carried_scalars = match acc_words { + 8 => false, + 10 => true, + other => panic!( + "unexpected accumulator payload of {other} words; expected 8 (point_pair) \ + or 10 (point-and-scalar)" + ), + }; + let scalar_stride = if has_carried_scalars { 0x20 } else { 0 }; + let lhs_scalar_word = first_acc_word + 4 * 0x20; + let rhs_first_word = lhs_scalar_word + scalar_stride; + let rhs_scalar_word = rhs_first_word + 4 * 0x20; + let acc_end_word = rhs_scalar_word + scalar_stride; + assert!( + acc_end_word <= calldata.len(), + "accumulator words run past the fixture calldata; the fixture is inconsistent" + ); + + // Guard the offset arithmetic above. Without this, a miscomputed + // `first_acc_word` would still make every mutation below revert -- for the + // wrong reason -- and the test would pass while exercising nothing. + // + // Four 56-bit limbs occupy the low 224 bits of a packed word, so the top + // four bytes of every accumulator coordinate word must be zero. Random + // proof or instance bytes would not satisfy this. + for (index, word_start) in [first_acc_word, rhs_first_word] + .into_iter() + .flat_map(|point| (0..4).map(move |w| point + w * 0x20)) + .enumerate() + { + assert_eq!( + &calldata[word_start..word_start + 4], + &[0u8; 4], + "accumulator coordinate word {index} at {word_start:#x} has non-zero high bytes; \ + the computed accumulator offset does not point at packed limbs" + ); + } + + let mut evm = Evm::default(); + let vk_address = evm.create(compile_solidity_with_runs(&vk_solidity, SOLC_OPTIMIZE_RUNS)); + let verifier_code = compile_solidity_with_runs(&verifier_solidity, SOLC_OPTIMIZE_RUNS); + // Split renders pin a separately deployed quotient evaluator; single + // contract renders take the verifying key alone. + let verifier_address = match read_optional_fixture_string(fixture, "Halo2QuotientEvaluator.sol") + { + Some(quotient_solidity) => { + let quotient_address = evm.create(compile_solidity_with_runs( + "ient_solidity, + SOLC_OPTIMIZE_RUNS, + )); + evm.create_with_two_address_args(verifier_code, vk_address, quotient_address) + } + None => evm.create_with_address_arg(verifier_code, vk_address), + }; + + match evm.try_call_with_gas(verifier_address, calldata.clone(), GAS_CAP) { + CallOutcome::Success { output, .. } => { + let expected: Vec = [vec![0u8; 31], vec![1]].concat(); + assert_eq!( + output, expected, + "fixture proof should verify; the fixture and calldata may be out of sync" + ); + } + CallOutcome::Revert { gas_used, output } => panic!( + "fixture proof was rejected (gas_used = {gas_used}, output = 0x{}); \ + regenerate fixtures/ivc -- see this file's header", + hex::encode(output) + ), + CallOutcome::Halt { gas_used, reason } => { + panic!("fixture proof halted (gas_used = {gas_used}, reason = {reason})") + } + } + + // Limbs are 56 bits packed 4 to a word, so the first word carries 224 + // significant bits. Byte 3 is the lowest unused high byte in the + // big-endian word: setting it keeps the value below the Fr modulus, so + // only `check_acc_coord_packing` can catch it. + let mut bad_packing = calldata.clone(); + bad_packing[first_acc_word + 3] ^= 0x01; + assert_reverts( + evm.try_call_with_gas(verifier_address, bad_packing, GAS_CAP), + "non-canonical accumulator limb packing", + ); + + // Perturb a coordinate and zero its scalar, so the term cannot be + // dismissed as a no-op multiply and has to fail the point decode. + for (case, point_word, scalar_word) in [ + ( + "malformed LHS accumulator point", + first_acc_word, + lhs_scalar_word, + ), + ( + "malformed RHS accumulator point", + rhs_first_word, + rhs_scalar_word, + ), + ] { + let mut malformed = calldata.clone(); + malformed[point_word + 31] ^= 0x01; + if has_carried_scalars { + malformed[scalar_word..scalar_word + 0x20].fill(0); + } + assert_reverts( + evm.try_call_with_gas(verifier_address, malformed, GAS_CAP), + case, + ); + } + + // Substituting the canonical point at infinity for a real accumulator term + // must not verify. This is the encoding `is_acc_encoded_identity` accepts, + // so it exercises the identity path rather than the range check. + for (case, point_word) in [ + ( + "LHS accumulator replaced with encoded infinity", + first_acc_word, + ), + ( + "RHS accumulator replaced with encoded infinity", + rhs_first_word, + ), + ] { + let mut identity = calldata.clone(); + write_encoded_identity(&mut identity, point_word, &verifier_solidity); + assert_reverts( + evm.try_call_with_gas(verifier_address, identity, GAS_CAP), + case, + ); + } + + // --------------------------------------------------------------------- + // Calldata framing. These attacks need no layout knowledge at all. + // --------------------------------------------------------------------- + let instance_count = read_u256_word(&calldata, instances_len_word); + let mut framing_cases: Vec<(String, Vec)> = Vec::new(); + + let mut trailing = calldata.clone(); + trailing.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]); + framing_cases.push(("extra trailing calldata".into(), trailing)); + + let mut truncated = calldata.clone(); + truncated.pop(); + framing_cases.push(("truncated calldata".into(), truncated)); + + let mut wrong_selector = calldata.clone(); + wrong_selector[3] ^= 0x01; + framing_cases.push(("wrong function selector".into(), wrong_selector)); + + for (case, offset, value) in [ + ("wrong proof ABI head", 0x04, 0x60), + ("wrong instances ABI head", 0x24, 0x20), + ( + "short proof length", + PROOF_PAYLOAD_START - 0x20, + proof_len as u64 - 0x20, + ), + ( + "long proof length", + PROOF_PAYLOAD_START - 0x20, + proof_len as u64 + 0x20, + ), + ( + "wrong instance array length", + instances_len_word, + instance_count + 1, + ), + ] { + let mut mutated = calldata.clone(); + write_u256_word(&mut mutated, offset, value); + framing_cases.push((case.into(), mutated)); + } + + for (case, mutated) in framing_cases { + assert_reverts( + evm.try_call_with_gas(verifier_address, mutated, GAS_CAP), + &case, + ); + } + + // --------------------------------------------------------------------- + // Curve-level attacks on every proof commitment. + // + // The repacked proof opens with a run of EIP-2537 padded G1 points, so the + // run length is discovered from the padding signature rather than + // hardcoded: a regenerated fixture with a different commitment count stays + // covered. + // --------------------------------------------------------------------- + let g1_count = padded_g1_block_count(&calldata, PROOF_PAYLOAD_START, proof_len); + assert!( + g1_count >= 8, + "expected a run of padded G1 commitments at the proof head, found {g1_count}; \ + the fixture proof layout changed" + ); + + let p_hi = solidity_constant(&verifier_solidity, "BLS_P_HI"); + let mut p_lo = solidity_constant(&verifier_solidity, "BLS_P_MINUS_ONE_LO"); + // p - 1 ends in ...aaaa, so incrementing cannot carry out of the low byte. + p_lo[31] += 1; + + for index in 0..g1_count { + let at = PROOF_PAYLOAD_START + index * G1_PADDED_BYTES; + + // (0, 1) is field-canonical but off the curve: y^2 = x^3 + 4 gives + // 1 != 4, so the G1 precompiles must reject it. + let mut off_curve = calldata.clone(); + off_curve[at..at + G1_PADDED_BYTES].fill(0); + off_curve[at + G1_PADDED_BYTES - 1] = 1; + + // x = p exactly: one past the largest canonical coordinate. + let mut base_modulus = calldata.clone(); + base_modulus[at..at + 0x20].copy_from_slice(&p_hi); + base_modulus[at + 0x20..at + 0x40].copy_from_slice(&p_lo); + + // EIP-2537 pads each 48-byte coordinate with 16 leading zero bytes. + // Setting one is a non-canonical encoding of an otherwise valid point. + let mut bad_padding = calldata.clone(); + bad_padding[at] ^= 0x01; + + for (label, mutated) in [ + ("off-curve", off_curve), + ("base-modulus", base_modulus), + ("non-canonical padding", bad_padding), + ] { + assert_reverts( + evm.try_call_with_gas(verifier_address, mutated, GAS_CAP), + &format!("{label} G1 at proof commitment {index}"), + ); + } + } + + // --------------------------------------------------------------------- + // Scalar canonicality across the evaluation block that follows the + // commitments, plus the non-accumulator public inputs. + // --------------------------------------------------------------------- + let fr_modulus = solidity_constant(&verifier_solidity, "FR_MODULUS"); + let evals_start = PROOF_PAYLOAD_START + g1_count * G1_PADDED_BYTES; + let evals_end = PROOF_PAYLOAD_START + proof_len; + assert!( + evals_start < evals_end && (evals_end - evals_start) % 0x20 == 0, + "evaluation block is not a whole number of words" + ); + + for (index, at) in (evals_start..evals_end).step_by(0x20).enumerate() { + let mut noncanonical = calldata.clone(); + noncanonical[at..at + 0x20].copy_from_slice(&fr_modulus); + assert_reverts( + evm.try_call_with_gas(verifier_address, noncanonical, GAS_CAP), + &format!("proof scalar {index} set to the Fr modulus"), + ); + } + + // The accumulator words are covered above; sweep the remaining public + // inputs for scalar canonicality. + let first_instance_word = instances_len_word + 0x20; + for index in 0..instance_count as usize { + let at = first_instance_word + index * 0x20; + if (first_acc_word..rhs_scalar_word + 0x20).contains(&at) { + continue; + } + let mut noncanonical = calldata.clone(); + noncanonical[at..at + 0x20].copy_from_slice(&fr_modulus); + assert_reverts( + evm.try_call_with_gas(verifier_address, noncanonical, GAS_CAP), + &format!("public input {index} set to the Fr modulus"), + ); + } +} + +/// EIP-2537 padded G1: `x_hi, x_lo, y_hi, y_lo`. +const G1_PADDED_BYTES: usize = 4 * 0x20; + +/// Count the leading run of EIP-2537 padded G1 points in the repacked proof. +/// +/// Each coordinate is a 48-byte field element left-padded to 64 bytes, so the +/// high word of `x` and of `y` both start with sixteen zero bytes. Evaluation +/// scalars do not share that signature, which is what ends the run. +fn padded_g1_block_count(calldata: &[u8], proof_start: usize, proof_len: usize) -> usize { + let mut count = 0; + while (count + 1) * G1_PADDED_BYTES <= proof_len { + let at = proof_start + count * G1_PADDED_BYTES; + let x_pad_zero = calldata[at..at + 16].iter().all(|b| *b == 0); + let y_pad_zero = calldata[at + 0x40..at + 0x50].iter().all(|b| *b == 0); + if !(x_pad_zero && y_pad_zero) { + break; + } + count += 1; + } + count +} + +/// Write `value` as a big-endian EVM word at `offset`. +fn write_u256_word(calldata: &mut [u8], offset: usize, value: u64) { + calldata[offset..offset + 0x20].fill(0); + calldata[offset + 0x18..offset + 0x20].copy_from_slice(&value.to_be_bytes()); +} + +/// Read a rendered `uint256 internal constant NAME = 0x...;` out of the +/// fixture's verifier source. +/// +/// Parsing the constants back out of the artifact under test keeps this file +/// from carrying a second copy of them: if codegen ever changes the encoding, +/// the vector below follows automatically instead of silently testing a stale +/// literal. +fn solidity_constant(source: &str, name: &str) -> [u8; 0x20] { + let needle = format!("constant {name} "); + let line = source + .lines() + .map(str::trim) + .find(|line| line.starts_with("uint256") && line.contains(&needle)) + .unwrap_or_else(|| panic!("verifier source has no constant `{name}`")); + let hex_value = line + .split_once("0x") + .expect("constant should be hex") + .1 + .trim_end_matches(';') + .trim(); + let bytes = hex::decode(format!("{hex_value:0>64}")) + .unwrap_or_else(|err| panic!("constant `{name}` is not hex: {err}")); + bytes.try_into().expect("constant should be one EVM word") +} + +/// Overwrite the four accumulator coordinate words at `point_word` with the +/// canonical encoded point at infinity. +/// +/// This is the exact quadruple `is_acc_encoded_identity` accepts: `p - 1` in +/// every packed word, with the identity flag (one radix base) folded into the +/// first word of `x`. +fn write_encoded_identity(calldata: &mut [u8], point_word: usize, verifier_solidity: &str) { + for (index, name) in [ + "BLS_P_MINUS_ONE_PACKED_0_WITH_ID_FLAG", + "BLS_P_MINUS_ONE_PACKED_1", + "BLS_P_MINUS_ONE_PACKED_0", + "BLS_P_MINUS_ONE_PACKED_1", + ] + .into_iter() + .enumerate() + { + let word = solidity_constant(verifier_solidity, name); + let at = point_word + index * 0x20; + calldata[at..at + 0x20].copy_from_slice(&word); + } +} diff --git a/proofs/solidity-verifier/tests/poseidon_fixture.rs b/proofs/solidity-verifier/tests/poseidon_fixture.rs index 58bfe73a3..36092af7f 100644 --- a/proofs/solidity-verifier/tests/poseidon_fixture.rs +++ b/proofs/solidity-verifier/tests/poseidon_fixture.rs @@ -125,13 +125,17 @@ fn poseidon_renders_compiles_and_verifies() { let srs_dir = srs_dir(); let srs_path = format!("{srs_dir}/bls_filecoin_2p{K}"); let fallback_srs_path = format!("{srs_dir}/bls_filecoin_2p19"); - if !Path::new(&srs_path).exists() && !Path::new(&fallback_srs_path).exists() { - eprintln!( - "skipping poseidon end-to-end smoke: SRS not found at {srs_path} or {fallback_srs_path}. \ - Set SRS_DIR or fetch the asset under midfall/zk_stdlib." - ); - return; - } + // The gate was explicitly requested, so a missing asset fails rather than + // silently reporting a pass that rendered and compiled nothing. + assert!( + Path::new(&srs_path).exists() || Path::new(&fallback_srs_path).exists(), + "{RUN_EVM_TESTS_ENV}=1 requires the test SRS, but it was not found at {srs_path} or \ + {fallback_srs_path}. +Fetch it with: + curl -L -o {fallback_srs_path} \ + https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19 +or point SRS_DIR at an existing copy." + ); env::set_var("SRS_DIR", &srs_dir); let relation = PoseidonExample; @@ -200,11 +204,13 @@ fn poseidon_renders_compiles_and_verifies() { verifier_solidity.len() ); - // Skip the EVM portion if the pinned solc is not available. - if !pinned_solc_available() { - eprintln!("skipping poseidon end-to-end smoke: pinned solc not available"); - return; - } + assert!( + pinned_solc_available(), + "{RUN_EVM_TESTS_ENV}=1 requires the pinned solc, which was not found or did not match. +\ + Install it, point SOLC at the binary, or set \ + HALO2_SOLIDITY_ALLOW_UNPINNED_SOLC=1 to accept another version." + ); let vk_creation_code = compile_solidity(&vk_solidity); let verifier_creation_code = compile_solidity(&verifier_solidity); diff --git a/proofs/solidity-verifier/tests/rsa_signature_fixture.rs b/proofs/solidity-verifier/tests/rsa_signature_fixture.rs index 132d1e4c0..f37cce3bc 100644 --- a/proofs/solidity-verifier/tests/rsa_signature_fixture.rs +++ b/proofs/solidity-verifier/tests/rsa_signature_fixture.rs @@ -103,13 +103,17 @@ fn rsa_signature_renders_compiles_and_verifies() { let srs_dir = srs_dir(); let srs_path = format!("{srs_dir}/bls_filecoin_2p{K}"); let fallback_srs_path = format!("{srs_dir}/bls_filecoin_2p19"); - if !Path::new(&srs_path).exists() && !Path::new(&fallback_srs_path).exists() { - eprintln!( - "skipping RSA signature Solidity smoke: SRS not found at {srs_path} or \ - {fallback_srs_path}. Set SRS_DIR or fetch the asset under midfall/zk_stdlib." - ); - return; - } + // The gate was explicitly requested, so a missing asset fails rather than + // silently reporting a pass that rendered and compiled nothing. + assert!( + Path::new(&srs_path).exists() || Path::new(&fallback_srs_path).exists(), + "{RUN_EVM_TESTS_ENV}=1 requires the test SRS, but it was not found at {srs_path} or \ + {fallback_srs_path}. +Fetch it with: + curl -L -o {fallback_srs_path} \ + https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19 +or point SRS_DIR at an existing copy." + ); env::set_var("SRS_DIR", &srs_dir); let relation = RsaSignatureCircuit; @@ -172,10 +176,13 @@ fn rsa_signature_renders_compiles_and_verifies() { verifier_solidity.len() ); - if !pinned_solc_available() { - eprintln!("skipping RSA signature EVM smoke: pinned solc not available"); - return; - } + assert!( + pinned_solc_available(), + "{RUN_EVM_TESTS_ENV}=1 requires the pinned solc, which was not found or did not match. +\ + Install it, point SOLC at the binary, or set \ + HALO2_SOLIDITY_ALLOW_UNPINNED_SOLC=1 to accept another version." + ); let vk_creation_code = compile_solidity(&vk_solidity); let verifier_creation_code = compile_solidity(&verifier_solidity); diff --git a/proofs/solidity-verifier/tests/sha_preimage_fixture.rs b/proofs/solidity-verifier/tests/sha_preimage_fixture.rs index 573038809..3ef8529f2 100644 --- a/proofs/solidity-verifier/tests/sha_preimage_fixture.rs +++ b/proofs/solidity-verifier/tests/sha_preimage_fixture.rs @@ -82,13 +82,17 @@ fn sha_preimage_renders_compiles_and_verifies() { let srs_dir = srs_dir(); let srs_path = format!("{srs_dir}/bls_filecoin_2p{K}"); let fallback_srs_path = format!("{srs_dir}/bls_filecoin_2p19"); - if !Path::new(&srs_path).exists() && !Path::new(&fallback_srs_path).exists() { - eprintln!( - "skipping SHA preimage Solidity smoke: SRS not found at {srs_path} or \ - {fallback_srs_path}. Set SRS_DIR or fetch the asset under midfall/zk_stdlib." - ); - return; - } + // The gate was explicitly requested, so a missing asset fails rather than + // silently reporting a pass that rendered and compiled nothing. + assert!( + Path::new(&srs_path).exists() || Path::new(&fallback_srs_path).exists(), + "{RUN_EVM_TESTS_ENV}=1 requires the test SRS, but it was not found at {srs_path} or \ + {fallback_srs_path}. +Fetch it with: + curl -L -o {fallback_srs_path} \ + https://midnight-s3-fileshare-dev-eu-west-1.s3.eu-west-1.amazonaws.com/bls_filecoin_2p19 +or point SRS_DIR at an existing copy." + ); env::set_var("SRS_DIR", &srs_dir); let relation = ShaPreimageCircuit; @@ -152,10 +156,13 @@ fn sha_preimage_renders_compiles_and_verifies() { verifier_solidity.len() ); - if !pinned_solc_available() { - eprintln!("skipping SHA preimage EVM smoke: pinned solc not available"); - return; - } + assert!( + pinned_solc_available(), + "{RUN_EVM_TESTS_ENV}=1 requires the pinned solc, which was not found or did not match. +\ + Install it, point SOLC at the binary, or set \ + HALO2_SOLIDITY_ALLOW_UNPINNED_SOLC=1 to accept another version." + ); let vk_creation_code = compile_solidity(&vk_solidity); let verifier_creation_code = compile_solidity(&verifier_solidity);