# Report: Scroll Mainnet Emergency Upgrade on 2026-11-10

**URL:** <https://forum.scroll.io/t/report-scroll-mainnet-emergency-upgrade-on-2026-11-10/1586>\
**Category:** General\
**Tags:** announcement\
**Created:** [November 11, 2026, 1:48pm UTC](https://forum.scroll.io/t/report-scroll-mainnet-emergency-upgrade-on-2026-11-10/1586 "2026-11-11T13:48:23Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![thegaram33](https://sea2.discourse-cdn.com/flex002/user_avatar/forum.scroll.io/thegaram33/32/173_2.png) [@thegaram33](https://forum.scroll.io/u/thegaram33)\
**Post date:** [November 11, 2026, 1:48pm UTC](https://forum.scroll.io/t/report-scroll-mainnet-emergency-upgrade-on-2026-11-10/1586/1 "2026-11-11T13:48:24Z")

</div>

## Overview

On 2026-11-03, Scroll received a bug bounty report about the bundle proof verifier deployed on Ethereum mainnet. On 2026-11-10, we registered a fixed verifier for batch version 10. We verified that the bug was not exploited on Scroll mainnet.

## Summary of the Bug

`ZkEvmVerifierPostFeynman` ([0x7966FE0De13e3B81533470640Ac489223476B254](https://etherscan.io/address/0x7966FE0De13e3B81533470640Ac489223476B254)) verifies OpenVM bundle proofs for `ScrollChain`. It built the verifier input by hand and sent it to the OpenVM 2.0 Halo2 verifier with a raw `staticcall`. The first 4 bytes of that call came from the caller-supplied proof.

The OpenVM 2.0 verifier exposes a typed function `verify(bytes,bytes,bytes32,bytes32)` (selector `0x24270d54`) next to its Halo2 fallback. A proof that starts with `0x24270d54` reaches that function instead of the fallback. The function then reads the public values from offsets the caller controls. An old, valid proof could therefore be accepted for a new public input, which would allow finalizing an arbitrary state root and withdraw root.

Exploiting the bug required either the permissioned prover role or enforced batch mode, which opens after 7 days without finalization. Neither was used to exploit it: enforced mode never opened, and every finalization used a regular proof. Earlier verifier versions were not affected: their Halo2 verifiers have no function selector, so every call reached the fallback.

## The Fix

The new verifier calls the OpenVM wrapper through its typed entry point, so the selector, commitments and public-input hash all come from the contract, never from the proof. The input that reaches the Halo2 fallback is byte-identical to the previous version. The constructor now also rejects an empty verifier address and out-of-range digests.

- New verifier: [0xb15417122fB6E853d84e498517268d33d7301e74](https://etherscan.io/address/0xb15417122fb6e853d84e498517268d33d7301e74)
- Registration in `MultipleVersionRollupVerifier` in [this transaction](https://etherscan.io/tx/0x40b00411a189c46157f16b60041887687398d13394387962b899d7b548e599b0).
- Code change: [scroll-tech/scroll-contracts#181](https://github.com/scroll-tech/scroll-contracts/pull/181) (merge commit `8264260`)

## What We Verified

- **Equivalence:** a differential fuzz test and a mainnet-fork byte comparison show the new verifier passes identical input to the Halo2 verifier.
- **Compatibility:** 233 past finalize transactions, replayed through `ScrollChain` on mainnet forks, finalize with the same roots; tampered copies are rejected.
- **Upgrade path:** every Safe and timelock transaction was simulated before signing.
- **Bytecode:** the deployed verifier matches [#181](https://github.com/scroll-tech/scroll-contracts/pull/181) and keeps the previous verifier’s settings.
- **No exploitation:** all 748 finalizations while the vulnerable verifier was active used regular proofs with no malicious selector prefix, confirmed by both an RPC node and Etherscan. Enforced mode never opened.

## Verify It Yourself

Copy this prompt into your coding agent:

```auto
Verify Scroll's verifier upgrade on Ethereum mainnet. Use read-only RPC calls and public sources only; do not send transactions. Report PASS or FAIL for each check, with evidence.

Old verifier: 0x7966FE0De13e3B81533470640Ac489223476B254,
New verifier: 0xb15417122fb6e853d84e498517268d33d7301e74,
MultipleVersionRollupVerifier: 0x4CEA3E866e7c57fD75CB0CA3E9F5f1151D4Ead3F,
ScrollChain: 0xa13BAF47339d63B743e7Da8741db5456DAc1E556,

1. Compatibility: read the verified Etherscan sources of the old verifier, the new verifier and its `plonkVerifier` (0x0d2A59fd7060460F07c30C2817b4E79576da01C6, `OpenVmHalo2Verifier`). Confirm that the payload the old verifier built by hand is byte-for-byte the payload `_constructProof` builds from the new verifier's arguments: same proof bytes, same digests, and the same 32 public-input-hash bytes in the same order.

2. Bytecode: build `ZkEvmVerifierPostFeynman` from scroll-tech/scroll-contracts at commit 8264260718fc7bfcde84bb77ba2b90352d02df76 with `forge build`. The new verifier's deployed runtime equals the build output at every byte outside its immutable references.

3. Immutables: the old and new verifiers return the same `plonkVerifier()`, `verifierDigest1()`, `verifierDigest2()` and `protocolVersion()`.

4. Registration: `latestVerifier(10)` on MultipleVersionRollupVerifier returns the new verifier.

5. Live finalization: the `finalizeBundlePostEuclidV2` transactions sent to ScrollChain after the registration succeeded, and their traces call the new verifier.

```

## Acknowledgement

We would like to thank the whitehat ZHOUCHUNGONG for reporting this issue.
