Reproduced Exploit
Aztec Escape-Hatch Exploit (variant 2) — Unconstrained Inner `proof_id` Witness
Per the embedded root cause: escape_hatch_circuit.cpp publishes the inner proof id with public_witness_ct(&composer, 0); // proof_id. public_witness_ct() makes the value public but does not constrain it to 0. A prover can therefore publish proof_id = 1 while still proving a join-split with public_i…
Loss
educational reproduction (the Connect contracts were already drained via exp1); disclosure Jun 22 2026
Chain
Ethereum
Category
Frontend / Off-chain
Date
Jun 2026
Source & credit. Exploit reproduction, trace data, and analysis adapted from DeFiHackLabs by SunWeb3Sec — an open registry of reproduced on-chain exploits. Standalone Foundry PoC and full write-up: 2026-06-AztecEscapeHatch_exp2 in the
evm-hack-registrymirror. Upstream DeFiHackLabs PoC:src/test/…/AztecEscapeHatch_exp.sol.
Reproduction: the PoC compiles & runs in an isolated Foundry project at this project folder. Full verbose trace: output.txt. Verified vulnerable source: RollupProcessor.
Key info#
| Loss | educational reproduction (the Connect contracts were already drained via exp1); disclosure Jun 22 2026 |
| Vulnerable contract | Aztec RollupProcessor 0x737901be… + escape_hatch_circuit.cpp |
| Chain / block / date | Ethereum mainnet / fork 25,295,800 / Jun 22, 2026 |
| Bug class | Unconstrained ZK witness — the escape-hatch circuit publishes the inner proof_id via public_witness_ct(&composer, 0) which makes the value public but does not constrain it to 0; a prover can set proof_id = 1 while still proving a join-split funded by a public input. |
TL;DR#
Per the embedded root cause: escape_hatch_circuit.cpp publishes the inner proof id with
public_witness_ct(&composer, 0); // proof_id. public_witness_ct() makes the value public but does
not constrain it to 0. A prover can therefore publish proof_id = 1 while still proving a join-split
with public_input > 0 and output notes funded by that public input. RollupProcessor. processDepositsAndWithdrawals() settles public deposits/withdrawals only when
proofId == 0 && (publicInput != 0 || publicOutput != 0) — so proof_id = 1 skips settlement while the
funded output notes are still honoured.
Root cause#
An unconstrained public witness for proof_id in the escape-hatch circuit: the prover can choose
any proof_id, decoupling the "fund output notes" path from the "settle deposits" path.
Diagrams#
Remediation#
- Use
constant_ct(0)(constrained) instead ofpublic_witness_ct(0)forproof_id. - Settlement should not branch on
proof_id; fund/settle paths must be jointly constrained. - Circuit-level audit of every
public_witness_ctfor missing constraints.
How to reproduce#
_shared/run_poc.sh 2026-06-AztecEscapeHatch_exp2 -vvvvv
- RPC: mainnet archive (block 25,295,800). Result:
[PASS] 2 tests—proof_id=1funded output honoured without settlement.
Reference: Aztec escape-hatch unconstrained proof_id witness, mainnet, Jun 22 2026 (educational).
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 2026-06-AztecEscapeHatch_exp2 (evm-hack-registry mirror).
- Upstream DeFiHackLabs PoC:
AztecEscapeHatch_exp.sol.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "Aztec Escape-Hatch Exploit (variant 2)".
- Web3Sec X hacked database: search.
- Rekt leaderboard: search.
- Solodit incident search: search.
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.