Calculated from recorded token losses using historical USD prices at the incident time.
0x5ff6dbc33e77fab8dc086bb9ea3c88f1ba81df198d24ec9fc0c5b50fb1a4a17d0xd3560ef60dd06e27b699372c3da1b741c80b7d90Base0x1e65c075989189e607ddafa30fa1a0001c376cfdBaseAt Base block 42410817, transaction 0x5ff6dbc33e77fab8dc086bb9ea3c88f1ba81df198d24ec9fc0c5b50fb1a4a17d was sent by 0x49a7ca88094b59b15eaa28c8c6d9bfab78d5f903, which deployed helper contract 0x5f68ad46f500949fa7e94971441f279a85cb3354 and executed a forged-withdraw loop against Veil_01_ETH at 0xd3560ef60dd06e27b699372c3da1b741c80b7d90.
The transaction executed 29 successful withdraw operations and drained the full 2.9 ETH pool balance. The root cause is a malformed Groth16 verifier key in Verifier at 0x1e65c075989189e607ddafa30fa1a0001c376cfd: gamma2 and delta2 are identical constants, making proof acceptance forgeable for attacker-chosen public inputs.
Veil_01_ETH.withdraw authorizes payout using three conditions before transfer:
verifier.verifyProof(...) returns true.The pool transfers denomination - fee to the _recipient after passes. For this pool, .
verifyProofdenomination = 0.1 ETHGroth16 verification key structure requires independent trusted setup elements, including distinct gamma2 and delta2. If these are equal, the pairing equation can be satisfied by crafted curve points without a valid witness, breaking the intended ownership proof guarantee.
Root cause category: ATTACK.
This is a cryptographic soundness failure in the deployed verifier configuration, not a private-key compromise. The victim contract depends on the verifier as its main authorization boundary for withdrawals. Because gamma2 == delta2 in the verifier constants, the attacker can construct (A, B, C) tuples that satisfy the pairing check for chosen public signals (root, nullifier, recipient, relayer, fee, refund) without owning a valid deposit note witness. The attacker then iterates nullifiers (dead0000 to dead001c) to satisfy one-time-spend checks and repeatedly collect 0.1 ETH per call. This exploit path is permissionless and reproducible by an unprivileged actor from public state and code, so it is ACT.
Origin: Veil pool source snapshot (Veil_01_ETH.sol).
require(!nullifierHashes[_nullifierHash], "The note has been already spent");
require(isKnownRoot(_root), "Cannot find your merkle root");
require(
verifier.verifyProof(
_pA,
_pB,
_pC,
[
uint256(_root),
uint256(_nullifierHash),
uint256(uint160(_recipient)),
uint256(uint160(_relayer)),
_fee,
_refund
]
),
"Invalid withdraw proof"
);
nullifierHashes[_nullifierHash] = true;
_processWithdraw(_recipient, _relayer, _fee, _refund);
This makes verifier correctness a hard security invariant: only witness-backed proofs should unlock ETH payouts.
Origin: verifier source snapshot (Verifier.sol).
uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
uint256 constant deltax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant deltax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant deltay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 constant deltay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
gamma2 and delta2 are byte-for-byte identical. That is the concrete code-level breakpoint violating the intended Groth16 soundness assumption.
Origin: Base receipt + collector artifacts for tx 0x5ff6...a17d.
status: 0x1
contractAddress(created): 0x5f68ad46f500949fa7e94971441f279a85cb3354
victim Withdrawal logs: 29
recipient topic (all 29): 0x49a7ca88094b59b15eaa28c8c6d9bfab78d5f903
relayer topic (all 29): 0x0000000000000000000000000000000000000000
first decoded nullifier: 0x...dead0000
last decoded nullifier: 0x...dead001c
Balance deltas from collector evidence:
0xd356... : -2.9 ETH0x49a7... : +2.899923980938290525 ETH (net, gas-adjusted)This matches the ACT success predicate and complete drain behavior.
Attack contract deployment:
0x49a7... sends one transaction and creates 0x5f68....Forged-proof withdrawal loop:
getLastRoot, denomination).withdraw repeatedly with sequential nullifiers from dead0000 through dead001c.Profit realization and teardown:
0.1 ETH each drain the full pool.Adversary-related cluster:
0x49a7ca88094b59b15eaa28c8c6d9bfab78d5f9030x5f68ad46f500949fa7e94971441f279a85cb3354Victim/protocol components:
0xd3560ef60dd06e27b699372c3da1b741c80b7d900x1e65c075989189e607ddafa30fa1a0001c376cfd2.9 ETH2.899923980938290525 ETH net after gas while victim lost 2.9 ETH gross.Security principles violated:
0x5ff6dbc33e77fab8dc086bb9ea3c88f1ba81df198d24ec9fc0c5b50fb1a4a17dhttps://basescan.org/tx/0x5ff6dbc33e77fab8dc086bb9ea3c88f1ba81df198d24ec9fc0c5b50fb1a4a17d/workspace/session/artifacts/collector/seed/8453/0x5ff6dbc33e77fab8dc086bb9ea3c88f1ba81df198d24ec9fc0c5b50fb1a4a17d/metadata.json/workspace/session/artifacts/collector/seed/8453/0x5ff6dbc33e77fab8dc086bb9ea3c88f1ba81df198d24ec9fc0c5b50fb1a4a17d/trace.cast.log/workspace/session/artifacts/collector/seed/8453/0x5ff6dbc33e77fab8dc086bb9ea3c88f1ba81df198d24ec9fc0c5b50fb1a4a17d/balance_diff.json/workspace/session/artifacts/auditor/iter_0/Veil_01_ETH.sol/workspace/session/artifacts/auditor/iter_0/Verifier.sol