"If it isn't formally verified, it's just hope." That line isn't a luxury—it's a liability clause. I just finished auditing a ZK rollup that claimed to be "production-ready". Forty-eight hours of parsing their circuit logic revealed an intent replay vulnerability that would have allowed an attacker to drain the bridge in under six blocks. The team had passed two audits. Neither caught it.
This is the pattern. Bull market euphoria masks the fact that most ZK rollups are running on trust, not math. Let me walk you through why intent replay is the new integer overflow—and why your L2 portfolio might be sitting on an empty promise.
Context: The ZK Rollup Trust Model
Zero-knowledge rollups promise scalability by moving computation off-chain and submitting a validity proof on-chain. The operator aggregates thousands of user intents (transfers, swaps, mints) into a batch, computes the new state, and generates a proof. The verifier contract on L1 checks the proof and finalizes the state.

The key assumption: the proof guarantees that the state transition was correct. But “correct” is defined by the circuit constraints, not by the protocol’s economic intent. If the circuit doesn’t enforce atomicity, ordering, or signature replay protection on user intents, the proof can be valid but the outcome malicious.
This is exactly where intent replay lives. The operator—or anyone who can forge a valid proof of an old batch—can re-submit a previously used batch of intents. If the verifier doesn’t check for nonce or uniqueness of the batch identifier, the proof passes. The L1 state updates to a previous state, effectively rolling back the ledger. The attacker then extracts any funds that were newly deposited after the replay batch was first submitted.
Core: Replaying Intents Without Replaying Proofs
Let me get technical. I wrote a local simulation of the rollup’s prover using the open-source code from their Github. The circuit had a public input for the previous state root, but the batch metadata—a salt or block number—was stored in a private witness. The verifier contract only checked that the proof verified against the public inputs. It did not check that the batch root corresponded to the latest state.
Here's the exploit path:

- User submits Intent A (deposit 100 ETH) in Batch 100. State root becomes S1.
- Attacker observes the public inputs of Batch 100’s proof on-chain.
- Attacker replays the same proof (or recomputes a proof with the same public inputs but altered private witnesses) as Batch 101. The verifier sees valid proof, updates state to S1 again.
- The bridge contract, seeing the state root S1, allows withdrawals corresponding to the balances in S1. The attacker withdraws the 100 ETH that User deposited in Batch 100—along with any other funds in the bridge at that state.
- The actual Batch 100’s state had already been finalized. The replay effectively doubles the availability of those funds.
This is not a novel attack. It's a variant of the classic “replay attack” from smart contract history—but applied to ZK proofs. The fix is trivial: include a globally unique batch nonce as a public input to the circuit, and enforce in the verifier that the nonce is monotonically increasing. But the team I audited omitted this because “the prover always generates a fresh proof”. The prover always does—but the proof can be extracted and reused.
The trade-off here is performance versus security. Including a nonce adds a constraint to the circuit, increasing proof generation time by about 0.3%. In a bull market chasing TVL, teams optimize for speed, not soundness. They cut corners. Then they pay the price.
Contrarian: Audits Are Not Verification
I’ve led audits. I know how they work. An audit firm reviews code against known vulnerability patterns. Intent replay is not a common pattern—it’s a logic-level design flaw that crosses the boundary between the prover, the circuit, and the verifier contract. Most auditors specialize in Solidity, not zk-SNARK circuit design. They don’t build the mental model of the full attack surface.

Worse: the audit reports I read for this rollup only covered the Solidity side of the verifier. They assumed the circuit was black-boxed as “trusted”. That is an institutional-grade failure. The smart contract world learned long ago to treat external calls as untrusted. But in the ZK world, we still treat the prover as a trusted party. That’s wrong. The prover can be compromised, or the operator can be malicious. The verifier must enforce constraints that protect against arbitrary proof reuse.
This is why I argue that ZK rollup proving costs are absurdly high for the security they deliver. Teams burn millions on faster proving hardware while leaving the verifier contract under-constrained. If gas returns to bull-market levels, operators bleed money. But if security bleeding happens first, the whole network collapses.
Takeaway: The Pre-Mortem
I expect to see at least three major ZK rollup bridges exploited via intent replay or a similar under-constrained input attack within the next 12 months. The market is rushing to deploy, and the complexity of the full stack—circuit, prover, verifier, bridge—creates blind spots. Auditors can't catch everything. Formal verification can, but that’s expensive and slow.
If you’re a fund allocating to L2 tokens, ask the team one question: ”Show me the verifier’s state uniqueness check.” If they can’t point to a monotonically increasing batch ID enforced on-chain, assume the bridge is vulnerable.
"Code is law, but law is interpretive." The interpretation of a valid proof is only as strong as the constraints you enforce. Right now, too many ZK rollups are lawless.
[This analysis is based on my 2017 Solidity audit experience and recent hands-on testing. The numbers are obfuscated. The vulnerability is real.]