On the night of June 21, 2026 (UTC), Taiko was attacked. Over the next few hours the attacker forged proofs, pushed a fake version of our Layer 2 finalized onto Ethereum as if it was real, and used it to withdraw roughly USD 1.75 million from the canonical Bridge and the ERC20 token vault. This post is our honest account of it: how we caught the attack, what we did, what we know so far, and everything we did to fix it.
TL;DR: The Taiko protocol has a defense-in-depth system. Proving is limited to a small whitelist of provers, the prover hardware is locked to an approved program, the Bridge enforces a daily withdrawal limit, and no single person or company can change the protocol. The attacker did not break the proving math or find a bug in the bridge contracts. The attack began off-chain with an exposed signing key and an attestation check that failed to reject a prover running in debug mode. Together those let the attacker forge proofs. The attacker used that ability to falsify the age of a proposal. By design, Taiko’s contracts allow any party to prove a proposal that has been unproven for more than five days. This is an intentional safeguard against the protocol depending on any single party, so others can keep the chain progressing even if our provers go offline. By making a new proposal appear to have crossed that five day threshold, the attacker triggered the safeguard and bypassed the whitelist. Withdrawal limits slowed the drain and the emergency pause stopped it. More than $11 million in bridge and vault funds never moved; the loss was contained to about $1.75 million. The attack path is closed and every user was made whole, as the bridge was recollateralized 1:1 and reopened on July 2. Subsequently, the Unzen upgrade that makes a ZK proof mandatory on every block is scheduled to activate on August 6, 2026.
Jun 21, 18:38 — Our prover submits its last legitimate proof. (L1 block 25,367,808, tx)
Jun 21, 19:04 — Attack begins. The attacker registers two rogue debug-mode prover instances and, in one transaction, submits the first fraudulent proof and files 10 forged withdrawal claims against the fake state. All 10 are accepted but left unpaid: the attacker's batching contract submits each with too little gas to run the payout, so the Bridge records them as valid 'retriable' claims to be cashed out later via retryMessage(). Nothing is paid out yet. (L1 block 25,367,938, tx)
Jun 21, ~19:27 — First automated alerts fire (reorg and finality-stall). We investigate them as a liveness / prover issue initially.
Jun 21, 22:07 — Funds start leaving. About three hours later the attacker cashes out the 8 token claims via retryMessage(), one transaction each: USDC 649,761, crvUSD 156,832, USDT 138,140, CRV 126,161, iZi 2,140,403, WETH 20.70, WBTC 0.43, weETH 0.53. Funds begin leaving the Bridge and ERC20Vault; the 999 ETH and 110.5M TAIKO claims stay retriable and never pay. (L1 block 25,368,853)
Jun 21, 22:12 — We open an internal call. Team members start joining.
Jun 21, 22:35 — Our CTO notices the L1 Inbox's (the L1 contract that records and finalizes L2 state) finalized state has diverged from the canonical chain, and escalates from a liveness investigation to a security investigation.
Jun 21, 23:02 — Confirmed as an exploit of the permissionless proving path. We notify the Security Council and ask members to stand by to sign an emergency pause.
Jun 21, 23:11 — We draft the first emergency pause proposals and circulate them to the Security Council for signing. (Proposals #29 and #30)
Jun 22, 00:44 — We publish a security notice on X, warning that Taiko's bridges can no longer be relied upon. (X post)
Jun 22, 02:11 — Our preconfers stop proposing blocks, forcing the attacker to submit the final fraudulent proof then stop, about 3.5 hours before the pause. In total the attacker finalized about 30 fraudulent checkpoints across roughly 6,100 fake L2 blocks. A single checkpoint was sufficient to prove any withdrawal, so the number of checkpoints did not affect the scale of the exploit. (L1 block 25,370,060, tx)
Jun 22, 04:37 — Enough signatures from the SC members are collected (7/9), but the previous proposals would revert due to an authorization issue. We create a new encrypted emergency DAO proposal to pause the Bridge and ERC20Vault. (Proposal #31)
Jun 22, 05:40 — Contained. Enough Security Council signatures are collected again and Proposal #31 is executed: the Bridge and ERC20Vault are paused. No further withdrawals are possible. (L1 block 25,371,104, tx)
Jun 22, 07:04 — Follow-up X post: the incident is contained, the Bridge and vault are paused, and the earlier guidance no longer applies. (X post)
Jun 22, 07:16 — We create a second encrypted emergency proposal to disable forced-inclusion processing and permissionless proving and proposing as an additional safeguard. (Proposal #32)
Jun 22, 09:24 — The second emergency proposal is executed. (L1 block 25,372,225, tx)
Jun 23, 21:49 — A full police report is filed with the Singapore Police Force. (Report No. A202606247010)
Jun 25 — OpenZeppelin completes its incident response review. Recovery contracts deployed.
Jun 29 — Proposal #34 executed: debug SGX fix live, fraudulent checkpoints invalidated, stuck claims resolved, chain restored to pre attack state. (Proposal #34)
Jun 30 — Bridge recollateralized 1:1 in kind. (Bridge, ERC20Vault)
Jul 2 — Proposal #35 executed: Bridge and ERC20Vault unpaused. Normal operations resume under conservative quotas. (Proposal #35)
Jul 7 — Unzen upgrade proposed to the DAO (Proposal #36). (Proposal #36)
Jul 14 — Security Council approves Unzen; community veto period opens. (Proposal #36)
Taiko is built with several independent layers of defense:
Prover whitelist: only a small, trusted set of addresses can submit proofs on-chain. This is a security measure we have in place until we fully transition to ZK, our guest program is fully audited, and zkVMs are mature enough.
Hardware attestation: prover hardware is locked to an approved measurement of our code, so an arbitrary program cannot pass itself off as a Taiko prover.
A daily withdrawal limit: the Bridge caps how much of each asset can be withdrawn in any 24-hour window.
No owner: the protocol is ownerless; no key, person, or company can change it unilaterally.
Most of these held during the attack. One did not, exposing a gap in our threat model.
The attack started off-chain, in how one of our SGX provers was set up. Two conditions had to line up:
The enclave signing key was committed to a public repository. An SGX enclave is identified by two measurements:
MRENCLAVE, the hash of its code, andMRSIGNER, the hash of the public key that signed it. The private key behind ourMRSIGNER(a 3072-bit RSA key in docker/enclave-key.pem was committed to our public repository, so anyone could sign their own enclave build and still present our expectedMRSIGNER. On its own, the exposed key was not enough to mount an attack because attestation should only accept a valid program.Attestation did not reject debug-mode enclaves. On registration we verified that a prover's
MRENCLAVEandMRSIGNERmatched the approved values, but not that the enclave ran in production rather than debug mode. A debug-mode enclave provides no memory protection. Its operator can read and modify everything inside it, so SGX's hardware guarantee no longer holds.



With both gaps present, the attacker took our genuine prover code (matching MRENCLAVE), re-signed it as a debug-enabled enclave using the exposed key (matching MRSIGNER), and registered it on-chain. Attestation accepted it because it never checked the debug flag. Running in debug mode, they fully controlled the enclave and the proof-signing key it holds, letting them produce correctly signed, correctly attested proofs over a fake version of our chain. At 19:04 UTC they registered two rogue debug-mode prover instances and submitted the first fraudulent proof in the same transaction (0x2f44dc1b...277260, L1 block 25,367,938).
We are no strangers to the risks of SGX and similar systems. That is why, until the guest program is fully audited and zkVMs are mature enough, we keep a strict prover whitelist, so that only trusted addresses can submit a proof on-chain.
As we announced back in December 2025 when we reached 100% ZK coverage for the first time, ZK was implemented as a soft requirement. Finalization required at least two proofs, and in edge cases where a ZK proof could not be produced, a block needing too many proving cycles or a zkVM bug, both proofs could come from SGX so the chain could keep finalizing rather than stall. That fallback is the layer this attack ran through. The attacker never touched our ZK cryptography. Unzen closes the gap by making ZK a hard requirement, so at least one of the proofs finalizing every block must always be ZK.
The attacker was not on the prover whitelist, so why were their proofs accepted?
The system includes one deliberate exception. If a proposal stays unproven for more than five days, anyone may prove it. This is an intentional safeguard so the chain keeps finalizing and never depends on any single party.

The weak point was that the contract measured a proposal's age from a timestamp carried inside the proof, and once the attacker could forge proofs, they could forge that timestamp too. This check did not hold up against an attacker who could already forge proofs, making a recent proposal appear old enough to cross the five day threshold. The contract believed the chain had gone unproven for days, opened the permissionless path, and accepted the attacker as a prover. The whitelist was never broken directly. It was bypassed by forging the number that decided when the whitelist could be skipped.
From there the rest followed. With forged proofs accepted, the attacker finalized a fork of our L2 that exists on no honest node, and kept extending it. Each one wrote a new fraudulent state root to the L1 SignalService (the contract that relays L2 state to L1), and the attacker proved each withdrawal against the fake state they had just finalized. The same SignalService the Inbox writes to when it finalizes is the one the Bridge reads when it pays out a withdrawal, fake state in, fake withdrawals out.
In total the attacker submitted 39 forged withdrawal messages, 36 of which paid out (35 cashed out by the attacker, and one 18-ETH message picked up by a third-party MEV bot)
So how is it possible that only a portion of the funds were drained?
Two things contained it: the Bridge's daily withdrawal limits, and the emergency pause. The limits (and the attacker setting a too small gasLimit that marked their messages as retriable and consumed the ETH quota) did the heavy lifting where they were configured.
In their opening batch, alongside the first token claims, the attacker filed a 999 ETH withdrawal. It never paid out and was later force resolved during recovery, together with the two stuck TAIKO claims, but in the attempt it consumed almost the entire day's ETH allowance. Everything taken after it could only trickle out as the allowance slowly refilled, which is why the ETH loss stopped at about 278 ETH over several hours instead of draining the entire Bridge.
The TAIKO limit did the same for TAIKO: the attacker could take only 1.99 million of the roughly 110.5 million TAIKO held in the vault, leaving about 98% untouched.
A few lower-value tokens were not subject to a daily limit and were drained in full. Because the protocol is ownerless there was no master switch to flip, but the Security Council and the DAO could act, and did, pausing the Bridge and the vault through emergency proposals. Together these layers are the reason this was a roughly 1.75 million dollar incident and not a larger one.
Technical detail: why ETH was throttled but some tokens were not?
The daily limit is enforced at a different point for ETH than for ERC-20 tokens, which is why the failed 999 ETH still mattered.
In the opening tx the attacker set agasLimitdifferent to zero (so that a different address can claim), but lower than the 800k that the contract enforces. This marked all the attacker’s withdrawals as retriable.
For an ETH withdrawal the Bridge consumes the daily allowance insideprocessMessage, before it even attempts the transfer, so the 999 ETH attempt burned about 999 of the 1,000-ETH daily allowance even though it failed and paid out nothing. For an ERC-20 withdrawal the allowance is consumed inside the token vault, which only runs when the withdrawal actually succeeds, so the token claims the attacker left retriable at 19:04 consumed no allowance until the attacker cashed them out at 22:07.
On-chain, the ETH allowance dropped from 1,000 to about 1 ETH at 19:04, while the USDC, USDT and TAIKO allowances were untouched at 19:04 and only fell when those claims succeeded later. The net effect: the attacker's own failed ETH withdrawal self-throttled their ETH drain to the slow refill rate (about 41.7 ETH per hour), while the limit constrained tokens only where one was both configured and exceeded (TAIKO).
The attack, end to end (sequence diagram):

The attacker drained about USD 1.75 million worth of tokens, mostly ETH plus stablecoins, TAIKO, and a few other tokens.
Source | Asset | Amount | Approx. USD |
|---|---|---|---|
Bridge | ETH | 295.972 | about $512,000 |
ERC20Vault | USDC | 675,761 | about $676,000 |
ERC20Vault | crvUSD | 156,832 | about $157,000 |
ERC20Vault | USDT | 138,140 | about $138,000 |
ERC20Vault | TAIKO | 1,990,000 | about $172,000 |
ERC20Vault | WETH | 20.70 | about $36,000 |
ERC20Vault | WBTC | 0.426 | about $27,000 |
ERC20Vault | CRV | 126,161 | about $26,000 |
ERC20Vault | iZi and weETH | 2,140,403 and 0.53 | about $4,000 |
Total | about $1,748,000 |
Figures above are the amounts released to the attacker; for TAIKO and USDC the net reduction in the vault's reserve was slightly smaller, because a small amount of each was legitimately bridged in by other users during the same window.
The ETH figure includes 18 ETH that never reached the attacker: one forged message set an oversized 18 ETH relayer fee, which a third party MEV bot (coffeebabe.eth) processed and received. Roughly 278 ETH went to the attacker. The bot returned the funds: 17 ETH is back in the Taiko treasury and 1 ETH went to the block builder.
The rest stayed safe. At the time of the pause the Bridge still held about 991 ETH and the ERC20Vault about 108.5 million TAIKO, together more than $11 million. The three stuck fraudulent claims, the 999 ETH and the two 110.5M TAIKO withdrawals, were force resolved during recovery and can never execute. The bridge was recollateralized 1:1 and reopened on July 2. If you hold funds bridged through Taiko they are fully backed, and you do not need to do anything.
Addresses we have linked to the attacker:
Address | Role |
|---|---|
| Primary wallet. Registered the rogue prover, submitted the fraudulent proofs, and received the drained tokens. Held about 117 ETH at the time of the pause; about 9 ETH as of June 24 21:24 UTC, after the attacker began moving funds on June 23. |
| Consolidation wallet. Received the fraudulent ETH withdrawals and the proceeds of selling stolen tokens. Held about 778 ETH and was dormant until June 23, when it made its first outbound; about 676 ETH as of June 24 21:24 UTC. |
| TAIKO laundering hop. |
| Centralized-exchange deposit address that received about 1.99M TAIKO. |
| Attacker-deployed contract used to swap stolen tokens. |
| Attacker-deployed contract used to batch transactions registering SGX instances and mark fraudulent ERC20 withdrawal messages as |
Key transactions
Setup: rogue prover registration + first fraudulent proof + 10 forged withdrawal claims, all left retriable:
0x2f44dc1b…(block 25,367,938, 19:03:59)Token cash-outs via
retryMessage()(~22:07, one transaction each): USDC 649,761, crvUSD 156,832, USDT 138,140, CRV 126,161, iZi 2,140,403, WETH 20.70, WBTC 0.426, weETH 0.53; plus a later USDC 26,000ETH withdrawals: about 278 ETH paid to the consolidation wallet across ~25 withdrawals as the daily allowance refilled, starting with 130 ETH (block 25,368,908, 22:18) and trickling out through ~02:10
TAIKO drain + off-ramp: 1,990,000 TAIKO drained, then forwarded through laundering hop
0x5FBc60…to the MEXC deposit address0x3CC936…Token → ETH swaps: stolen stablecoins, CRV and WBTC swapped to ETH via the attacker's swap contract
0x9108…; proceeds sent to the consolidation walletFiled but never paid (since force resolved via Proposal #34): 999 ETH (claimed in
0x2f44dc1b…) and 110.5M TAIKO ×2 (0x2f44dc1b…,0xabed9edc…)18 ETH fee grab: a forged message whose oversized relayer fee was processed and received by coffeebabe.eth; it never reached the attacker and has since been returned.
Last fraudulent proof:
0x68ff8e07…(block 25,370,060, 02:10), after which the attacker stopsFull activity: every transaction for
0x7506…b76aand0xA980…a846on Etherscan
With both emergency proposals executed, the attacker could no longer claim funds or submit new fraudulent proofs. From there we focused on three things, and all three are done.
First, the impact. We committed to making users whole and refilling the bridge 1:1, and we did. The bridge was recollateralized in kind before reopening, entirely from Foundation and Taiko Labs operating cash. No locked or unvested tokens were touched and no vesting schedule changed. Every L2 balance has been fully backed from the moment the network resumed.
Second, fixing the bug, hardening the protocol and adding emergency response mechanisms. These changes were reviewed by OpenZeppelin and independent Security Council members and executed on June 29 via Proposal #34. OpenZeppelin's incident response review found no critical, high or medium severity issues. Alongside the on-chain fixes we hardened our infrastructure, improving our SecOps monitoring, off-chain components and emergency procedures.
We are also working on:
Streamlining our emergency-response and Security Council signing process, so we can act faster under pressure.
Keeping our SGX attestation fresh: additional defense-in-depth. We will migrate to the upstream Automata DCAP Attestation stack with on-chain PCCS and put TCB (Trusted Computing Base) and certificate-revocation (CRL) updates on a regular maintenance schedule, so our attestation stays current with Intel's latest hardware-security baselines without relying on manual upkeep. The aforementioned debug-mode rejection already closes the path used in this incident; this work simply keeps the attestation layer current over time.
Making a ZK proof mandatory on every block through the Unzen hard fork. Finalization continues to require multiple proofs, but at least one must always be ZK, so SGX/TEE no longer provides an independent finality path and no single proof system can finalize the chain on its own. To keep every block provable, Unzen introduces zk gas, a metering mechanism analogous to execution gas that quantifies the proving cycles of each opcode, and we run two independent zkVMs (SP1 and RISC Zero) as protection against completeness bugs in either one. Unzen also upgrades Taiko EVM to Prague and re-enables forced inclusion, which was disabled as part of the incident response. The fork was proposed to the DAO on July 7 as Proposal #36, approved by the Security Council on July 14, and is in the community veto period until July 24. Mainnet activation is scheduled for August 6.
Third, reopening safely. Every change went through governance and the Security Council, and we brought the chain back in four reviewed stages. We deployed the remediation contracts and verified the finalized state was restored with no forged checkpoints or retriable messages reachable, replenished the bridge to back every L2 asset 1:1, resumed proposing, and unpaused the bridge on July 2. The quotas in place are deliberately conservative while we build confidence in the reopened chain, tighter than before the incident, and we raise them as utilization data comes in.
We reported the incident to the Singapore Police Force (Report No. A202606247010) on June 23 and provided the report to MEXC, where the 1.99 million TAIKO the attacker deposited is frozen. Converting that freeze into a return is a law enforcement led process and we are pursuing it actively. Of the stolen ETH, 17 ETH has been returned to the treasury. Approximately 530 ETH was moved through Tornado Cash and approximately 366 ETH remains in the two known attacker wallets, which we monitor together with a specialized traceability firm. None of this affects users. The shortfall was covered in full by the Foundation, recovery proceeds offset that cost and were never a precondition for making users whole.
Our operations team is available to exchanges, infrastructure providers, and ecosystem partners throughout this process, and security researchers can reach us at security@taiko.xyz.
