# Aztec Connect: numRealTxs proof/settlement mismatch mints unbacked deposits

Aztec Connect RollupProcessorV3 proved all 32 inner-rollup slots but settled only numRealTxs, letting an attacker mint unbacked deposits in slot 1 and withdraw ~$2.19M.

- Payout: Real-world hack
- Severity: critical
- Program: Aztec Connect
- Credited: DeFiHackLabs
- Disclosed: 2026-06-14
- Write-up quality: 9/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2026-06/AztecConnect_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20260614-aztecconnect
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:business-logic

**Target:** RollupProcessorV3.processRollup (Ethereum mainnet, deprecated Aztec Connect)
**Root cause:** decodeProof() hashes full inner-rollup chunks (ceil(numRealTxs/32)), proving all 32 slots, while processDepositsAndWithdrawals() settles only numRealTxs txs. numRealTxs sits at proofData[0x11c0], outside the hashed header, so the same valid proof verifies for any value 1..32.
**Steps:**
1. Fork mainnet at block 25,300,000; target ROLLUP 0xFF1F2B4ADb9dF6FC8eAFecDcbF96A2B351680455.
2. Craft rollup 13277: slot0 = settled no-op account, slot1 = DEPOSIT 908.99 ETH, declare numRealTxs=1 at proofData[0x11c0].
3. Call `processRollup(proofData, signature)` — permissionless on V3; proof verifies (1 chunk covers all 32 slots), deposit note minted, but L1 deposit validation skipped.
4. Repeat for DAI/wstETH/yvDAI/yvWETH/LUSD/yvLUSD (rollups 13278..13283).
5. Submit withdraw rollups 13284..13289 and 13290 (slot0 WITHDRAW 908.99 ETH) to drain to attacker EOA.
6. Verify: flipping only numRealTxs 1->2 on rollup 13277 makes the same proof revert with INSUFFICIENT_DEPOSIT().
**How found:** BlockSec initially suspected missing access control in processRollup(), then traced the real flaw: proof verification and L1 settlement interpret numRealTxs differently, and the field lies outside the hashed header so it is unbound by the proof.
**Exploitation:** Attacker pre-deployed an orchestrator contract (0x06f5...0fcD) and funded EOA via Tornado Cash, then submitted 14 state-chained rollups. Each mint rollup placed a real deposit in slot 1 while declaring numRealTxs=1, so the zk proof covered the deposit but L1 settlement skipped its decreasePendingDepositBalance/shield-signature check, creating unbacked L2 balances. Follow-up rollups withdrew those balances as real ETH/DAI/wstETH/yv tokens to the attacker EOA.
**Escalation:** Chained 14 sequential rollups across 7 assets, reusing the identical proof-validity window (numRealTxs 1..32 -> one chunk) to scale from a single free mint to ~$2.19M drained.
**Insight:** When a zk circuit commits data in fixed-size chunks but settlement iterates a caller-supplied count, check whether that count is inside the hashed public inputs — if not, any value mapping to the same chunk count is a free knob. Test by flipping the count to its honest value and confirming the same proof reverts.
