Binance hot wallet signed 50 EIP-7702 delegations 3 minutes before the drain
At 20:20:59 UTC on 29 October a transaction with no value and no calldata landed in Ethereum block 26300753. Its only payload was an EIP-7702 authorization list, fifty entries long. Every entry was signed by Binance 14, one of the exchange’s busiest Ethereum hot wallets, and every entry handed that wallet’s code to a contract deployed the same afternoon. Three minutes and twenty-four seconds later the contract emptied the wallet in one call, 167,464 ETH and 63 tokens with 1.615 billion USDT among them, somewhere between $2.5 and $2.8 billion at explorer prices that evening, to an address that had never received anything.
I pulled the raw transaction from a node that evening and pasted it into the raw transaction decoder on this site, which runs in the browser and names every byte. It showed fifty entries to one delegate and nothing about who had signed them, so I recovered the fifty signatures with seventy lines of Python. The decoder does that itself now, and shows the exact bytes each key signed. This transaction, decoded, is one of its examples. By 22:09 that night 0xQuit had already argued that whoever did this did not hold the key, because a relayer was needed at all. The fifty entries let me put a time on it.
Twenty-nine bytes
An EIP-7702 authorization is not a transaction. It is a signature over this:
05 EIP-7702 magic byte
db RLP list, 27 bytes follow
01 chain id 1
94 63e842b054bd624a465454d533c33f573e9c6fae delegate: 20-byte address
84 01209df9 nonce 18914809
Hash those twenty-nine bytes, sign the hash with the wallet’s key, and anyone can carry the result on chain in a type-4 transaction and pay the gas, 0.0065 ETH for all fifty. Here the carrier was 0x7C86…5CA0, an address that was empty two days earlier.
A signing service that is handed 32-byte digests cannot tell this digest from a transaction hash. A policy engine that checks destination, amount and calldata never gets a look, because the object has none of those. What the signature grants is “execute that contract’s code as me”, once the nonce matches, and the code stays attached until another authorization replaces it.
Fifty nonces
Each entry is only valid if its nonce equals the account’s nonce at the moment the carrier executes, and Binance 14 is not a quiet account. In the three minutes before the attack it sent 33 transactions, about one every six seconds, so any nonce you name is stale by the time the signature comes back. The attacker’s answer was to sign a window, nonces 18914775 through 18914824, fifty in a row, listed in descending order.
It had practised. The carrier’s first nine transactions that day, from 02:28 UTC, deploy a sweeper, delegate two throwaway accounts to it with windows of two and three nonces, sweep a few dollars out, then deploy a revised sweeper at 15:59 and run it all once more on a third throwaway. With keys it held, its own tooling used two or three. Pointed at Binance 14, it used fifty.
The low end dates the batch. Nonce 18914775 became the wallet’s live nonce in block 26300737, at 20:17:47, three minutes and twelve seconds before the carrier was broadcast. By inclusion the wallet had moved on to 18914808 and spent it on its own transaction, five places earlier in the same block. The sixteenth entry took effect. Thirty-three were already dead and sixteen were spare.
This is the decoder’s view of that entry, trimmed, with the warning it raises for the list as a whole:
authorization 15 0x63e842B054Bd624a465454D533C33F573e9c6fAe chain id 1, nonce 18914809
signed message 05db019463e842b054bd624a465454d533c33f573e9c6fae8401209df9
authority (recovered) 0x28C6c06298d514Db089934071355E5743bf21d60
warning: 50 authorizations from one account, all delegating to 0x63e8…6fAe,
with consecutive nonces 18914775 to 18914824
Paste the hex in yourself and hover any line to see which bytes it came from. Nothing leaves the page.
That window is the finding. A fifty-wide window is what you build when you submit digests to something and do not control when, or how fast, the signatures come back. I read it as a signing path that was handed fifty digests and returned fifty signatures in the ordinary course of its work. Binance has not said how the signatures were obtained, and its post-mortem may yet describe a plain key theft with an oddly cautious operator behind it.
The other wallets
Twenty-four seconds after the sweep, Binance 16 sent nine ordinary transactions to the same collector in a single block: eight token transfers and 28,005 ETH, consecutive nonces, 170 gwei each, no delegation anywhere. Blockaid counts ten more hot wallets on Arbitrum, Optimism and Avalanche doing the same within twelve minutes. I checked Binance 16 and take the rest on their word. Three other chains I checked myself. On the XRP Ledger, five Binance wallets paid 233.5 million XRP to one account between 20:05:00 and 20:09:20, a quarter of an hour before the delegation was broadcast. The first attempt, at 20:04:52, was 42.75 million RLUSD and failed with tecNO_DST: the account did not exist yet, and on XRPL a token payment cannot create one. The 185 million XRP that followed eight seconds later did. On Solana, 965,455 SOL left Binance 2 at 20:10:18. On Bitcoin, block 973732 spends 513 coins from Binance’s main BTC hot wallet, bc1qm34…, 18,270 BTC, to a fresh address that split it into seventeen pieces of exactly 1,000 BTC before the block closed. Bitcoin has no delegation. That is 513 signatures over payments to an address the wallet had never seen. Whatever signed for all of these wallets signed plain transfers to addresses that were an hour old, or eight seconds old.
So Binance 14 alone needed the 7702 route, and it came last: XRP at 20:05, Solana at 20:10, Bitcoin at 20:20, the delegation at 20:20:59. Either the attacker held every key and used a delegation on this one wallet because sixty-three tokens are easier to take in one call, or Binance 14’s signer sat behind a policy that would refuse a transfer to a new address and had no opinion about an authorization. The chain cannot tell those apart, and that is the question I want the post-mortem to answer.
Update, 30 October, 02:25 UTC. Binance’s initial finding is that a DevOps engineer’s laptop was compromised, that the attacker used it to reach a service container and submit withdrawal transactions through it, and that there is “no evidence of any wallet private key leak”. The wording is hedged, “suggests” and “may involve”, and the technical post-mortem is still to come. If it holds, it is the second reading: nobody stole a key. The attacker fed requests to the pipeline that owns the keys, and the pipeline signed fifty authorizations for Ethereum and plain payments for every other chain, which is what the fifty-wide window looked like from outside. The question for the post-mortem changes shape: not whether the signer had a policy, but how a withdrawal service came to put a 0x05 preimage in front of it. The statement puts the total at about $6.3 billion with about $3 billion frozen, and the address authorisation it says it revoked is the reset at 00:10:35 below.
Someone will say this is Bybit again, a signer shown one thing and signing another, with a 7702 delegation as the sweep instead of a proxy upgrade. Mostly, yes. Bybit’s signers approved a transaction that looked like a transfer and carried a delegatecall, and the lesson everyone drew was to show the signer the calldata. Here there is no calldata to show. Twenty-nine bytes, a prefix and three fields, is the whole object, and showing it faithfully gets you “chain 1, that address, nonce 18914809”. Refusing it means knowing what sits at that address.
I counted what a hot-wallet signing policy would need to check to have stopped this. The list is eight items. The second is to parse the preimage of everything the key signs and route anything that starts with 0x05 to its own rules, and no policy template I have seen has a line for it.
Binance’s own systems kept sending the wallet’s normal traffic afterwards, and deposits kept arriving, 326 USDT transfers after the drain, one of them 200 million at 21:42:59, swept 72 seconds later. A wallet that signs ten things a minute will sign the eleventh, and the only question a review can answer is what it was shown, which here was designed to be almost nothing. At 23:50 UTC, three and a half hours after the drain, Binance 14 was still running the attacker’s contract. Binance cleared it at 00:10:35 with one authorization to the zero address, then suspended withdrawals and promised a post-mortem.
Revised the morning of 30 October, a few hours after publication: added the rehearsal, Binance 16, the Bitcoin wallet and the other chains, the deposits swept after the drain, the time the delegation was cleared, and the credit to 0xQuit, whose post predates this piece. Later the same morning: the XRP and Solana legs, checked on their ledgers, the order the chains were hit in, and the dated update above with Binance’s initial finding.