Foom.cash's ZK lottery shipped a Groth16 verifier with an unfinished trusted setup, making every withdrawal proof forgeable. Defimon detected the exploit and preemptively rescued $1.84M from the Ethereum deployment.
On February 26, 2026, Foom.cash, a zero-knowledge lottery and mixer deployed on Ethereum and Base, was found to have shipped a Groth16 verifier whose trusted setup was never finished. Its gamma and delta parameters were both left at their placeholder value, the BN254 G2 generator. With the two parameters equal, any withdrawal proof could be forged with no knowledge of a valid witness, making every deposit in the contract freely drainable by anyone who read the public verifying key. The flaw was discovered and exploited by the whitehat researcher duha_real, who drained the Base deployment under the terms of Foom.cash's public bug bounty. Once that drain put the forgery on-chain, the same technique was reproducible by anyone against the unprotected Ethereum deployment. Defimon detected the exploit in real time and preemptively drained the Ethereum contract into a whitehat address before a malicious actor could, then returned approximately $1.84 million to the project the same day. This is a writeup of the vulnerability and the rescue.
Foom.cash used Groth16 proofs to authorize withdrawals. A user deposits, then later calls collect() with a proof that they own an unspent note, without revealing which deposit is theirs. The on-chain Groth16 verifier is the only thing standing between a caller and the pooled funds, so its soundness is the entire security model.
Groth16 verification reduces to a single pairing product equation. The snarkjs-generated verifier checks:
// Groth16Verifier.verifyProof, simplified
// vk_x = IC[0] + sum(input[i] * IC[i])
// accept iff:
// e(-A, B) * e(alpha, beta) * e(vk_x, gamma) * e(C, delta) == 1Here alpha, beta, gamma, delta and the IC points are fixed in the verifying key and readable by anyone. A, B, C are the proof, and vk_x is a linear combination of the public inputs with the IC points. Soundness depends on gamma and delta being independent values whose discrete logs (the "toxic waste") were destroyed during the trusted setup ceremony.
The ceremony was never completed. In snarkjs, the Phase 2 setup is initialized with both parameters set to the group generator as a placeholder, expecting later contributions to randomize delta while gamma stays fixed:
// snarkjs zkey init — placeholders, meant to be replaced in Phase 2
const bg2 = new Uint8Array(sG2);
curve.G2.toRprLEM(bg2, 0, curve.G2.g); // G2 generator
await fdZKey.write(bg2); // gamma2 = G2 generator
await fdZKey.write(bg1); // delta1 = G1 generator
await fdZKey.write(bg2); // delta2 = G2 generator <-- must be randomizedFoom.cash deployed verifiers built straight from this initialized-but-uncontributed key. The command that draws a random delta was never run, so the production verifier carried delta == gamma == G2 generator.
With the two parameters equal, the last two terms of the verification equation collapse. Because gamma and delta are the same point, the pairings over them can be merged:
e(vk_x, gamma) * e(C, delta) = e(vk_x, G2) * e(C, G2) = e(vk_x + C, G2)Setting C = -vk_x sends that combined term to the identity:
e(vk_x + (-vk_x), G2) = e(O, G2) = 1That removes the public input and the C term entirely. What remains is e(-A, B) * e(alpha, beta) == 1, which is satisfied by reading alpha and beta straight from the verifying key and setting A = alpha, B = beta:
e(-alpha, beta) * e(alpha, beta) = 1The forged proof is therefore (A = alpha, B = beta, C = -vk_x). Every value in it comes from the public verifying key and the public inputs. No deposit, no note, and no secret witness are required. An attacker recomputes vk_x for each call with an incrementing nullifier and loops collect() until the contract is empty.
The flaw affected both deployments. Across Ethereum and Base, roughly $2.26 million in FOOM was drainable, backed by a liquidity pool of over five million dollars on Ethereum. Because the forgery needs nothing but the on-chain verifying key, the funds were exposed to any observer capable of reading the contract, not to a privileged few. This is the failure mode of a broken trusted setup: it does not degrade security, it removes it, and it does so silently because the verifier still accepts every legitimate proof exactly as before.
The Ethereum deployment held the larger balance, approximately $1.84 million, sitting behind the verifier at 0xc043865fb4D542E2bc5ed5Ed9A2F0939965671A6.
The vulnerability was found and exploited by the whitehat researcher duha_real, who drained the Base deployment under the terms of Foom.cash's public bug bounty on Bitcointalk, whose only stated rule was the code. That drain put the forgery on-chain in full view, and the Ethereum deployment, holding roughly $1.84 million, ran the same unfinished verifier with no bounty and no protection standing between its balance and the first person to copy the technique.
Defimon detected the exploit as it happened and launched a preemptive whitehat rescue. Using the same forged-proof technique, we drained the Ethereum lottery contract into the whitehat-rescue.eth address in a single sequence of collect() calls, securing the full balance before a malicious actor could reproduce it, then returned approximately $1.84 million to the project the same day at 10:11 UTC. Foom.cash offered a security fee, which we accepted, and published a post-mortem on March 1, 2026 confirming the root cause as the unfinished Phase 2 setup.
Trusted-setup failures are not exploitable by flash loans or oracle games, so they never appear in the usual monitoring signatures, yet the drain that follows looks identical on-chain to any other pool being emptied: an address with no deposit history pulling the full balance out in a tight loop. That is the signal Defimon fired on here, and the reason our feed watches for that shape of drain across major chains. When the same public exploit is available to rescuer and thief alike, the outcome comes down to who acts first, which is why teams wire an automated incident-response pipeline to a detection feed rather than to a human refreshing a block explorer.
Related Addresses:
Connect in minutes. Every attack across major chains, as JSON, in under a second.
@DefimonAlerts