Incident date: 24 September 2026

On-chain status checked: 28 September 2026, 12:11 UTC

Gross unauthorized withdrawals: 1,918,793.198148 USDC across three burns and two Ethereum rollup settlements

Summary

Based on our investigation, it appears that an attacker submitted an invalid burn proof that Payy’s deployed Noir/Barretenberg verifier accepted. The first burn released 1,828,589.378132 USDC from the Payy rollup even though it appears that the transaction was invalid. A second burn of 1 USDC was paid early by a burn substitutor and later reimbursed. The second transaction released 90,202.820016 USDC to the same receiving wallet.

It appears there was a proving-system vulnerability in the Noir/Barretenberg verifier, developed by Aztec.

Aztec committed the small-subgroup IPA soundness fix as 58bf73a289f7da78c8920c9c73604a8e5d105025 (authored on 14 May 2026). That fix was included in release v5.0.0 on 13 July. Aztec did not notify Payy of the vulnerability or the fix.  Payy was not aware of the vulnerability.

Technical Failure

Payy was using a pinned Barretenberg build, 3.0.0-manual.20251030 with Noir 1.0.0-beta.14. The first attack transaction on Payy L2 was 1591f5837a73ede6bff04e896f38c78e9ac1caa2f4ff185eea74ab74ffc9cc94 at height 33,195,439 (24 September, 03:25:41 UTC). Its proof’s public inputs specified:

Public input Value
Input note commitments 0, 0
Output note commitments 0, 0
Transaction kind 3 (burn)
Burn amount 1,828,589,378,132 USDC base units
Burn hash 0x0000000000000000000000000000000000000000000000000000000000000000
Recipient 0xAa4985dBDaBfACa344237D40F7E06C4a0BB57E70

noir-zk-proof-2608-vulnerability.txt

The note-tree root before and after that L2 transaction was identical: 0x1351c20d383d0b3169fda958c81d54eb17597bd3935a8d4d7c01e9c71f02b9ea. Payy’s Noir burn circuit requires conservation of value and requires a padding note with zero commitment to have zero value. It also binds the burn hash to the first input commitment. An honest burn with all four commitments zero cannot withdraw a nonzero amount of USDC.

Nevertheless, the exact 16,256-byte proof verifies against the checked-in verification key using Payy’s pinned bb build. The ultra_honkverification command used by Payy defaults to the zero-knowledge flavor, which uses the small-subgroup IPA path changed by Aztec’s fix. Changing the public burn hash from zero to one causes verification to fail. The proof was therefore bound to the anomalous public inputs; the result was not a post-proof edit of the burn message. The reproduction package contains the verifier inputs, verification key, circuit source, on-chain evidence, and repeatable commands. The supplied bad proof and public inputs record the same anomalous statement.

payy-zero-burn-proof-repro.zip

The recursive aggregation circuits agg_utxo and agg_agg rely on that UTXO verification result; this is required to preserve privacy. agg_utxo verifies each UTXO proof against the UTXO verification key, then checks note-tree membership and changes the root only for nonzero commitments. With all four commitments zero, it had no note to remove or add, so the root stayed unchanged while it carried the proof’s nonzero burn amount and recipient into its public messages. It did not independently recompute the private note values or the burn’s value conservation; those facts were entrusted to the UTXO proof. agg_agg verified the resulting agg_utxo proof (or another agg_agg proof), checked root continuity and message compaction, and propagated the same burn message. agg_final verified the top-level agg_agg proof for Ethereum settlement. Once the invalid UTXO proof was accepted, each aggregation layer could satisfy its own checks while preserving the unauthorized withdrawal request. Both agg_utxo and agg_agg relied upon the validity of utxo proof.

On Ethereum, settlement transaction 0xf43abdac5422087f645d77923eb1c825178bff3eb86d17d40fa18d89701e1814 called verifyRollup on 0x367C1eAF14AA06b78ce76bd0243297de79d85270 at 04:21:23 UTC. The accepted aggregate proof caused the rollup to transfer real Circle USDC (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) to the attacker’s receiving wallet. The Burned event recorded the zero burn hash and success. The Solidity burn path trusts the proof for note validity and has no independent nonzero-burn-hash check. That made the proof-soundness failure directly payable from rollup reserves.

Aztec’s cited fix adds the missing boundary opening A(1) = 0 to its small-subgroup IPA verification path, closing an algebraic degree of freedom that a malicious prover could use to forge an inner-product claim. The Payy reproduction establishes the invalid-proof acceptance and the on-chain payout. The following patch has been verified to resolve the bug and would have prevented exploit.

barretenberg-58bf73a-v3-causal-test.patch

This analysis is supported by the postmortem report conducted by Spearbit.

Payy-bridge-exploit-2026-09-24.pdf