Every signature in this article is valid.
That is not a figure of speech and it is not a near miss. Run the verification yourself and it passes. The curve arithmetic is correct, the keys are the right keys, the messages hash to what they claim to hash to. A verifier handed any one of these signatures would return true, and be right to.
About $6.3 billion left Binance anyway.
The assets moved; that part is on-chain and anyone can check it. Binance says no wallet private key was leaked. Both are true at once, and the space between them is this article.
A valid signature proves the key agreed. It doesn’t prove the owner did.
Note what it does not say. It does not say signatures are worthless, or that cryptography failed anywhere in this story. It did not, at any point, in any of the incidents below. It also does not promise that a system which does inspect what it signs is safe. A policy can only refuse what it can describe, and the rules and the toy are both about that limit.
A signature is a proof about a key. It says: whoever holds this secret applied it to this message. It says nothing whatever about why, or whether the human or the policy or the business that owns that key would have wanted this. We spend a great deal of engineering effort making the first proof unforgeable, and then we read it as if it were the second one.
This is the fourth article in a series about claims that outrun their evidence. Success Means Verified was about a success signal arriving from somewhere other than the check that establishes it. Nothing Starts Clean was about reuse: a thing handed to you carrying what the last caller left in it. This one is about the gap between authentic and authorised, which is the gap almost every signing system quietly treats as zero.
That gap opens in three ways, and each one can happen on its own:
The Unread Seal: nothing inspected the content. Did anything look at what was being signed?
The Switched Page: someone did approve it, and was shown something else. Did the approver see what they approved?
The Signature That Outlived Its Moment: the authorisation was real and kept working. How long does this signature stay true?
A system can fail any one of these on its own, which is why they are separate. The bad incidents fail several: Binance failed the first and the third on the same night, and Bybit, examined below, failed all three, which is a large part of why it cost what it did. A later section shows the first clause in a system with no cryptocurrency in it at all, because this is not a crypto problem.
How an exchange pays you
What actually turns a withdrawal into a signature?
Hot, because cold is slow
When you withdraw from an exchange, nobody walks to a vault. The exchange keeps two kinds of custody. Cold storage is offline, slow, and requires humans and ceremony. Hot wallets are online and automated, holding the working float, because a withdrawal that takes four hours is a product nobody uses.
The hot side needs to sign transactions constantly and at machine speed. So the key material lives inside a signing service: a component that holds or can reach the keys, receives requests, and returns signatures. Around it is the thing that actually matters, and the thing this article is about: the part that decides which requests deserve a signature.
The question the signer is usually asked
In most systems, the signing service is asked one question, and it is the wrong one:
Is this request coming from somewhere I trust?
If yes, sign. The request arrived through the right internal service, carrying the right internal credential, from the right network position, so it must be legitimate. The signer checks the provenance of the request and never inspects the content of what it is about to sign.
That design is not stupid. It is fast, it is simple, and it concentrates the security into one place: keep the perimeter intact and the signer behaves. It also means that anything which can reach the signer inherits the full authority of every key the signer holds. There is no second question. There is no object in the system whose job is to look at a transaction and ask whether this is a thing we do.
That is the whole vulnerability, and nothing that follows is more complicated than it.
%%{init: {"theme": "base", "flowchart": {"curve": "basis", "rankSpacing": 36, "nodeSpacing": 26, "padding": 14, "htmlLabels": false}, "themeVariables": {"fontFamily": "Helvetica, Arial, sans-serif", "fontSize": "14px", "clusterBkg": "#141026", "clusterBorder": "#2e274e", "titleColor": "#b9aeef", "lineColor": "#50448c", "textColor": "#b9aeef", "mainBkg": "#1c1733", "nodeBorder": "#50448c", "edgeLabelBackground": "#141026"}}}%%
flowchart LR
accTitle: The question the signer asks, and the question it never asks
accDescr: A signing service that checks only where a request came from will sign anything a trusted caller sends, including a delegation that replaces the account's code. A signing service that checks the content of the transaction refuses that same request, because the answer does not depend on who sent it.
subgraph NEVER["What it never asked"]
direction LR
b1(["request"]) --> b2{"what am I signing?"}
b2 --> b3["a transfer we make: sign"]
b2 --> b4(["replace this code: refuse"])
end
subgraph ASKED["What the signer asked"]
direction LR
a1(["request"]) --> a2{"who is asking?"}
a2 --> a3["a service I trust: sign"]
a3 --> a4(["valid signature"])
end
classDef plain fill:#1c1733,stroke:#50448c,color:#b9aeef,stroke-width:1.5px;
classDef gate fill:#2e274e,stroke:#b9aeef,color:#f4eee2,stroke-width:1.5px;
classDef bad fill:#8a3321,stroke:#e4703a,color:#f4eee2,stroke-width:1.5px;
classDef good fill:#1f4733,stroke:#3dd68c,color:#f4eee2,stroke-width:1.5px;
class a1,a3,b1,b3 plain;
class a2,b2 gate;
class a4 bad;
class b4 good;
linkStyle default stroke:#50448c,stroke-width:2px;
style ASKED fill:#141026,stroke:#2e274e,color:#b9aeef;
style NEVER fill:#141026,stroke:#2e274e,color:#b9aeef;
The valid signature is the red one. That is the whole article in a picture: the left-hand path is working exactly as designed.
The Unread Seal
Did anything look at what was being signed?
Five past eight, and everywhere at once
Hypernative reports the following, in UTC on 29 October 2026. These per-chain times are single-sourced; TRM Labs puts Solana and Optimism “shortly after 20:10 UTC” against Hypernative’s 20:16–20:18 for Optimism.
| Time | What moved |
|---|---|
| 20:05–20:10 | XRP Ledger and Solana, the first movement |
| 20:10 | The bulk of outflows begins |
| 20:16–20:18 | Base, Optimism |
| 20:20–20:22 | Bitcoin and Tron |
| 20:20:59 | Ethereum: the delegation on Binance 14 is set |
| 20:24 | Binance 14 swept; outflows begin from Binance 16 |
| 20:30–20:36 | Arbitrum, Avalanche |
| 20:41, 20:59 | Further Ethereum USDC pulls |
| 23:04–23:05 | Avalanche, Polygon |
TRM Labs and Hypernative both put the first movement at 20:05 UTC. Chainalysis starts its account at 4:20 p.m. ET (20:20 UTC, fifteen minutes later), which is when the Ethereum delegation landed. That is a difference about where a story begins, not a disagreement about facts.
Even the total is contested. Binance says about $6.3 billion, the lowest estimate going; TRM Labs reports $6.36bn at the prices of the night, Chainalysis about $6.4bn, Elliptic $6.43bn. Fireblocks gives a range of $3.3–6.3bn and notes that “Binance has not published a verified figure.”
TRM Labs enumerates ten networks: “Ethereum, Bitcoin, Tron, the XRP Ledger, Solana, Arbitrum, Avalanche, Optimism, Base and Polygon.” Elliptic counts eleven, and names one the others do not: NEAR. Nobody has reconciled the two.
How long it took is contested the same way:
| Source | Duration of the drain |
|---|---|
| TRM Labs | “most of it in about 20 minutes starting at 20:05 UTC” |
| Hypernative | bulk of outflows within ~1h from ~20:10 UTC |
| Fireblocks | the first sweep in minutes, “activity continued for over an hour” |
| Elliptic | “across 11 blockchains in around three hours” |
Twenty minutes, one hour and three hours are not roundings of each other. They are probably measuring different things: the main sweep, the bulk, and the long tail including the later Avalanche and Polygon movements, but no source says so, and the reconciliation would be a guess.
What simultaneity tells you
Look at the first five rows. XRP Ledger, Solana, Base, Optimism, Bitcoin, Tron and Ethereum, inside about twenty minutes.
These are separate wallets holding separate keys. TRM Labs puts the point precisely: “These wallets use different chains, key types and signing schemes.” Not different curves: Bitcoin, Ethereum and Tron all use secp256k1, and the XRP Ledger defaults to it, but different accounts, different formats, different signing paths. There is no single secret whose theft unlocks all of them, and no mathematical attack that reaches them at once.
So the breadth itself is evidence, and Hypernative draws exactly the right conclusion from it:
A compromise that reaches that many wallets and keys at once points at Binance’s signing infrastructure rather than any single leaked key.
Not the keys. The thing that decides when the keys get used. Everything after this is a demonstration of what that distinction costs.
What a leaked key looks like
A leaked key has a shape. One key controls one account on one chain. Steal it and you can drain that account, once, until somebody moves what is left. The damage is bounded by that account’s balance and by the key’s scope.
That is not the shape of 29 October.
Two things happened on Ethereum, and they are different in kind.
The USDC did not come from a third compromised key. Hypernative explains it: the wallet holding it was “a Circle smart account whose only owner was Binance 14, so once the attacker controlled Binance 14 they could call it too: a 7702 delegation inherits every permission the delegating address holds on other contracts, not just its own token balance.” Elliptic says the same: the attacker used “Binance 14’s owner rights over another Binance wallet.”
That is one delegation reaching an account it was never pointed at, which is a sharper version of this article’s argument than a stolen key would have been.
Binance 16 is the other case, and it is the one that matters for breadth. Elliptic reports it “also sent 28,006 ETH and other tokens worth $174 million in total directly to the attacker, signed with its own key.” A separate wallet, a separate key, signing on its own. Add near-simultaneous movement on nine or ten further networks, and Binance says no wallet private key was leaked.
Those statements are consistent, and only one explanation fits both. The keys were never taken anywhere. They stayed where they were and did their job. Somebody else decided what that job was.
The distinction the industry keeps collapsing
“Binance was hacked and lost $6.3 billion” and “Binance’s keys were stolen” sound like the same sentence. They are not, and the difference is the entire lesson.
If keys had been stolen, the remedy is rotation: generate new keys, move the funds, revoke the old. Painful, bounded, well understood.
Nothing was stolen that can be rotated. The keys are fine. What failed was the set of conditions under which those keys would sign, and you cannot rotate a missing policy. Confirmed on-chain: every signature verified. Binance says: no key leaked. The FBI attributed the theft to North Korea on 31 October, naming the TraderTraitor group; TRM Labs had already assessed this with high confidence, and Elliptic reports the attribution confirmed its own. Fireblocks refers to the same actor as Lazarus Group. Not yet known: exactly how the attacker reached the signing path.
But notice that the last item is the only one still open, and the invariant does not depend on it. However they got in, the signing service answered the only question it knew how to ask, and the answer was yes.
What every check established
| Check | Property it established | Did it hold? |
|---|---|---|
| Request provenance | The caller is an internal service | Yes |
| Signature validity | The key agreed to exactly these bytes | Yes |
| Nonce match | This authorisation is current | Yes, for one of the fifty |
| Transaction content | This is a transaction we make | Nothing ever asked |
Every row but the last did its job perfectly. That is what makes this a clause and not an outage: there is no failed check to point at. There is a question with no owner.
The Unread Seal, restated: a signer that validates only the provenance of a request has delegated the decision to whoever can reach it. The signature it returns is a true statement about a key and says nothing at all about the transaction.
The Switched Page
Did the approver see what they approved?
Every control worked
The Unread Seal is the easy clause: nothing inspected the content, so of course the content was not inspected. It invites a comfortable response: we have approvals, we are fine.
Bybit had approvals. In February 2025 it lost about $1.5 billion, and its multisignature scheme was never broken.
What the signers approved was a single transaction. Malicious JavaScript, served from Safe’s web interface after a developer machine was compromised, displayed a legitimate-looking transfer in the browser while sending a different payload to the hardware wallets. The payload was a delegatecall that replaced the Safe proxy’s implementation contract.
The signers held Ledgers, and this is the part worth slowing down for: the Ledger displayed the real payload. The trusted screen told the truth. It was simply not intelligible: raw transaction data for a complex multisig operation, which is close to unreadable in practice. Ben Zhou has said he did not check it. So the browser lied, the hardware told the truth, and the approval happened against the lie.
Then the threshold stopped mattering. With the implementation replaced, the attacker called sweep functions directly. Sygnia’s investigation puts it plainly: the malicious contract introduced functions “allowing the attackers to transfer funds without requiring multisig approval.” BlockSec records five drain transactions totalling about $1.5 billion, executed “without any signature verification.”
So the famous number did not leave under valid signatures. One signature changed what the account was; the $1.5 billion left under none.
What every check established
| Check | Property it established | Did it hold? |
|---|---|---|
| Signer authentication | The approver is who they claim to be | Yes |
| Multisig threshold | Enough distinct owners approved | Yes, for the one transaction that mattered |
| Signature validity | Each key agreed to the payload it received | Yes |
| Trusted display | The hardware wallet showed the real payload | Yes, and nobody could read it |
| Intelligibility of the payload | The approver understood what they were approving | Nothing ever asked |
| Scope of the approved action | This approval moves value; it does not replace the account | Nothing ever asked |
Row four is the uncomfortable one. The control everybody recommends, approving on a separate trusted device, was present and working, and it did not help, because “displayed” and “understood” are different properties and only one of them was ever checked.
This is why “add human approval” is not by itself an answer, and why the second fix later carries a condition. An approval is worth the intelligibility of what the approver read, not merely its integrity.
The Switched Page, restated: an approval proves a person pressed a button, not that the transaction they believed they were approving is the one that got signed. Inspection performed on attacker-controlled content is not inspection.
The lineage
The FBI, TRM Labs, Chainalysis and Elliptic place these incidents in a line of North Korean operations:
| Incident | What was defeated | Clause |
|---|---|---|
| DMM Bitcoin, May 2024 | A malicious Python script sent to an employee of wallet provider Ginco “under the guise of a pre-employment test”; the access gained was then used, the FBI says, to “manipulate a legitimate transaction request by a DMM employee” | The Switched Page |
| Bybit, February 2025 | Malicious JavaScript in the Safe web interface changed what the signers were approving | The Switched Page |
| Bitget, September 2026 | Stolen funds later observed commingling with Binance’s, per Chainalysis | n/a |
| Binance, October 2026 | The signing system produced valid signatures on every chain at once | The Unread Seal |
Chainalysis’s strongest evidence is not stylistic; it is that Binance’s stolen funds were observed commingling on-chain with funds from the Bitget theft, a link ZachXBT reported first.
The mechanisms differ completely. Phishing a developer, poisoning a web interface, and whatever reached Binance’s signing path are three different attacks, and that is exactly why they are three clauses rather than one lesson. What they share is the outcome:
The victim signs its own robbery.
Nobody in this table had their cryptography broken. Every one of them had their cryptography used.
Why it keeps working
Because the industry hardened the wrong thing. Fifteen years of work went into making keys unstealable: HSMs, MPC, threshold schemes, air gaps, ceremonies. That work succeeded. Binance says no private key leaked, and I believe it.
So attackers stopped trying to take keys and started arranging for keys to be used. Against a signer that only asks who is asking, a perfect key custody story is not a defence. It is a guarantee that the signature on your robbery will verify.
The Signature That Outlived Its Moment
How long does this signature stay true?
A wallet that can run code
Most of what happened on 29 October was ordinary theft at unusual scale: valid signatures, ordinary transfers, large numbers. On Ethereum, something more interesting happened, and it is the clearest illustration of the invariant I have seen.
Ethereum has two kinds of account. A contract account has code. An externally owned account, a plain wallet controlled by a private key, does not. That division was fundamental for a decade.
EIP-7702 changes it. Fireblocks states the mechanism plainly:
An authorization is a signed message that assigns a contract’s code to an externally owned account (EOA), so that code runs whenever the wallet is called.
The feature exists for good reasons: it lets ordinary wallets gain the conveniences of smart accounts (batching, sponsored gas, session keys) without migrating to a new address. It is a genuinely useful piece of engineering.
It is also, read precisely, a mechanism by which one signature replaces the behaviour of an account. Not a transfer. Not an approval for a fixed amount. The code that runs when anyone touches that wallet, from now until revoked.
Why fifty
Here is the part that turns a capability into a heist.
An EIP-7702 authorisation is bound to a nonce, a counter that increments with every transaction an account sends, there to stop a signed message being replayed. A valid authorisation must match the account’s nonce at the moment it is processed.
Binance 14 is a production hot wallet. It is processing customer withdrawals continuously, and every one of them increments the nonce. An attacker who obtained a single authorisation for nonce N would be racing a moving target: by the time the transaction was included, the wallet would likely be at N+3, and the authorisation would be worthless.
So the attacker did not take one. Confirmed on-chain: a single transaction carried fifty authorisations covering consecutive nonces of Binance 14. Hypernative’s description of why:
Spreading the authorizations across 50 nonces is how the attacker made sure one would still match the wallet’s nonce while it kept processing normal withdrawals; in the event, exactly one of the 50 matched and took effect.
Fifty tickets for fifty consecutive values of a counter that was moving. Forty-nine were dead on arrival. One matched. One is all it takes.
Fifty signatures, restated: the attacker did not defeat the nonce. They bought the whole window. A replay defence that assumes the attacker gets one guess is not a defence against someone who can ask the signer fifty times.
And every one of those fifty was valid. The signing service produced them. Each is a correct proof that Binance’s key agreed to assign that code to that account at that nonce. Not one of them is a proof that Binance wanted it.
Three minutes
Once one authorisation landed, Binance 14 had attacker code. The delegation was set at 20:20:59. TRM Labs reports the first sweep “three minutes and 24 seconds after the delegation”; Elliptic says “three and a half minutes later”; Hypernative’s timestamps give 20:24:23. They agree.
That sweep took 167,464 ETH and about $1.615 billion in USDT from Binance 14.
The USDC came later and separately. Hypernative reports pulls at 20:41 and 20:59, seventeen and thirty-nine minutes after the delegation. The amount is contested: Hypernative and Elliptic both say about 823 million USDC, Fireblocks says 780 million.
Fireblocks reports one receiving address collecting approximately $3.52 billion within 40 minutes. Elliptic and Hypernative put the largest single transaction lower, at $2.53 and $2.57 billion.
No further signature was required for any of it. The signature was spent on the delegation. The delegation did the rest.
Three hours and fifty minutes
A transfer is an event. It happens, it is over, and the damage is bounded by the amount. A delegation is not an event. It is a state, and it persists until something removes it.
Confirmed on-chain: the malicious delegation on Binance 14 was set at 20:20 UTC on 29 October and revoked at 00:10 UTC on 30 October. Three hours and fifty minutes.
During that window the wallet was still a Binance hot wallet, still published, still in address books and integrations, still receiving customer deposits. Those deposits landed in an account whose code belonged to the attacker. Fireblocks:
the delegation let one attacker-controlled address drain the wallet, including new deposits, and the attacker reused that access more than an hour later.
Confirmed on-chain: four further sweeps, at 21:04, 21:44, 22:39 and 23:56 UTC.
The incident that kept happening
Read those timestamps as a sequence rather than a list. The large theft was over by about 21:30. The next three sweeps are not the theft. They are the aftermath of a permission nobody had yet removed: money that arrived after the robbery, from people who had no idea, into a wallet that was still obeying someone else.
This is what makes delegation different in kind from transfer, and why the distinction deserves its own vocabulary. If you steal money, you have the money. If you steal the behaviour of an account, you have everything that account will ever receive until someone notices.
The permission that stayed, restated: a transfer’s blast radius is its amount. A delegation’s blast radius is every future interaction, and it is bounded only by how long it takes somebody to look.
Three hours and fifty minutes is not a scandalous response time for a novel incident at this scale. That is rather the point. It was a good response, and the design still let four sweeps happen inside it.
What every check established
| Check | Property it established | Did it hold? |
|---|---|---|
| Nonce check | This authorisation has not been replayed | Yes |
| Authorisation validity | The key agreed to this delegation | Yes |
| Replay window | Only one authorisation can be live at a time | Yes, exactly one of the fifty took effect |
| Number the signer will issue | One live authorisation means one signature | Never bounded: fifty were produced |
| Lifetime of effect | The delegation ends when the request does | Nothing ever asked |
The nonce rule worked exactly as designed: only one authorisation could ever take effect, and only one did. What nothing bounded was how many the signer would produce. Fifty valid authorisations for a counter that only honours one is not a replay failure. It is a signer with no opinion about volume.
That absence composes with the next one. Buying the window got the attacker in once; nothing ending the delegation meant once was enough.
The Signature That Outlived Its Moment, restated: a signature is a statement about an instant, and systems routinely treat it as a statement about a period. Ask of every authorisation not only whether it is valid, but for how long it will remain so and who is watching that window close.
The same clause, outside crypto
Is this a cryptocurrency problem?
A build server with a certificate
Every incident so far involves an exchange, and that is a problem for the argument. If all four cases are crypto, the comfortable conclusion is that this is a crypto failure mode, something that happens to companies with hot wallets, not to companies with release pipelines.
So: SolarWinds, 2020. No cryptocurrency, a mature product, an audited enterprise vendor, and the Unread Seal exactly.
Attackers reached the Orion build system. SolarWinds says its “initial investigations point to an issue in the Orion software build system in which the SUNBURST vulnerability was inserted”, not the source repository, which is the detail that matters here. The backdoor entered during the build. What came out the other end was then signed, by the legitimate build server, with the production code-signing certificate, as an ordinary step of the ordinary release workflow.
The signature was valid and chained correctly to SolarWinds’ certificate authority, which is what made the update indistinguishable from every other Orion update customers had installed for years. SolarWinds says the affected updates were released between March and June 2020, and that the certificate used to sign them “was revoked March 8, 2021.”
A caveat the sources are clear about and this article should be too: the signature is not what did the evading. SUNBURST carried its own measures. Mandiant describes “multiple obfuscated blocklists to identify forensic and anti-virus tools running as processes, services, and drivers,” and an initial dormant period of up to two weeks. The signature’s contribution was narrower and is the part relevant here: it made a backdoored artifact look like a legitimate release, because that is precisely what the signature attested.
What every check established
| Check | Property it established | Did it hold? |
|---|---|---|
| Certificate chain | The signature was made by SolarWinds’ key | Yes |
| Build provenance | The artifact came from the official build server | Yes |
| Source review | The code in the repository was the reviewed code | Yes |
| Artifact content | The binary corresponds to the reviewed source | Nothing ever asked |
Row three is the one that stings. The source control was fine. The review was fine. Nobody compared what was reviewed against what was signed, because the build server was trusted, which is to say, the signer asked who is asking and the answer was the build server, and that was the end of it.
Swap “build server” for “service container” and “release workflow” for “withdrawal path”, and this is Binance with different nouns. Reproducible builds exist precisely to put a row in that last line, and most organisations still do not have them.
The race
How fast can the money be made unfreezable?
Frozen, if you can catch it
Stablecoins are not bearer assets. USDT and USDC are issued by companies that can blacklist an address, and those companies were watching. TRM Labs reports that “Tether froze 971.1 million USDT in the main receiving wallet at 21:01 UTC, 37 minutes after the main sweep.” Elliptic independently describes the same 37-minute gap.
Thirty-seven minutes is the number to hold onto. That is the industry’s real response time, measured.
The attacker was converting. Chainalysis reports that “over nearly 20 minutes it swapped $325 million in stablecoins into $285 million in ETH”, about a 12% loss. TRM describes what looks like the same trade and calls it “a discount of more than 10%.” Elliptic describes a larger set of swaps, “around $786 million of stablecoins into ETH and TRX,” and puts the implied loss at “12% to 26%.”
So: a substantial haircut to turn freezable assets into unfreezable ones, somewhere between a tenth and a quarter of what was moved. The exact figure is not settled.
It worked for what got converted and failed for the rest. Frozen totals as reported: TRM $2.7bn, Chainalysis $2.8bn, Elliptic $2.85bn, and Binance says around $3bn. Binance says it will cover user losses from its reserves and SAFU fund.
It is tempting to read this as someone watching the freeze notices and pricing finality at twelve percent. Elliptic reads it the other way: that “the attacker was slow to convert them,” with $3.46bn of the stolen funds in stablecoins. A calculated race or a sluggish exit: the firms watching it disagree. What is not in dispute is the shape: freezability is a property of the asset, and the attacker’s exit cost is a function of how fast the issuer moves.
What we do not know yet
What is still not known, and what changes when it is?
This is the part that will age, and it is marked rather than smoothed over.
Binance says its initial investigation indicated a compromised DevOps engineer’s laptop, which allowed an attacker to gain access to a service container and submit unauthorised withdrawal transactions. Binance’s own language was hedged: the incident “may involve” that laptop.
Binance says, subsequently, that the investigation has uncovered additional points of breach and that the attack involved a more complex process than first described. Binance says, via co-CEO Richard Teng in a video update on 30 October, that the attack “was not carried out by one person” and that the attacker used a self-destruct mechanism which destroyed tooling and traces as it ran. That claim has not been independently corroborated, and it is one that conveniently explains a slow investigation.
So the honest status of the initial-access story is: superseded in part, incomplete, and still moving. Not yet known:
- How the attacker reached the signing path, beyond “a workstation was involved”
- What the service container’s role and permissions actually were
- How the fifty authorisations were obtained from the signing service
Two things that might look unknown have in fact been said. Binance says, via Teng, that the delegation “did not pass through our normal approval controls”, so controls existed, and this path went around them. And TRM Labs reports “50 pre-signed authorizations prepared in advance,” adding that the batch “suggests the access may have been limited in time.” That is an assessment rather than a finding, but it is more than nothing.
Binance says it will publish a detailed technical post-mortem once investigators have established and verified the facts.
None of the argument above rests on the laptop. The load-bearing claims are on-chain and checkable today, which means the post-mortem can revise this section without disturbing the rest.
When it does land, these questions get their own follow-up rather than a quiet edit here. How the attacker reached a signing service, and what that service would and would not do on request, is the whole subject of this article, and a verified answer deserves more than a revised paragraph.
Rules for signatures that mean something
What would have to change, and which failure taught it?
Ask the signer a better question
The signing service answered is this request from somewhere I trust? The fix is not a better perimeter. It is a second question, asked by the signer, about the thing it is being asked to sign:
Is this a transaction we do?
That question is answerable without knowing anything about who asked, and that independence is the whole value. A policy that evaluates content cannot be satisfied by compromising the requester.
Each rule below is tagged with the failure that taught it, because a rule with no incident behind it is an opinion.
1. The signer inspects what it signs. Not the envelope: the transaction. Recipient, amount, asset, and critically the type. A delegation is not a transfer, and a signer that cannot tell them apart is not reading what it signs. An EIP-7702 authorisation to an unrecognised contract should be refusable as a category. Taught by: The Unread Seal (Binance, SolarWinds).
2. Large or irreversible actions need a quorum bound to the specific action, and the approver must be able to read it. Not approval to use the signer: approval of that transaction, on a device not in the request path, rendering it in terms a human can check. Bybit had everything up to the last clause: the Ledgers showed the true payload and it was raw multisig call data, which is not a rendering, it is a dump. A trusted display nobody can interpret buys you the audit trail and none of the safety. Taught by: The Switched Page (Bybit).
3. Limits that do not ask who is calling. Per-window caps on value, enforced by the signer on itself. Billions to a single address inside an hour, whichever of the reported figures is right, should be arithmetically impossible, not merely unusual. A limit the caller can raise by being trusted is not a limit. Taught by: The Unread Seal (Binance).
4. Authorisations are bounded in number and in time, and state changes are alarms. One live authorisation, not a window of fifty. A delegation set on a production hot wallet is a different class of event from a withdrawal and should page a human in seconds. The gap between 20:20 and 00:10 is not an operational failure; it is a monitoring design with no concept for “this account’s behaviour was replaced.” Taught by: The Signature That Outlived Its Moment (Binance).
The rule: authority must be a property of the request, not of the requester. Anything that derives permission from position can be defeated by occupying that position.
Note what is absent. No item here is “train staff better” or “be careful with laptops”. Those may be true and they are not fixes. They are hopes with a budget. Every item above changes what the system can do, not what people should do.
And note what they do not buy. All four together still leave rule 1’s limit: a policy refuses only what it can describe. A transaction type nobody modelled passes every one of these controls, which is the same shape as the absence in each table above, one level up.
The table to build for your own signer
The instrument here is not the list. It is this, filled in for whatever signs on your behalf, one row per thing it can be asked to sign:
| Transaction type | What the signer checks | What it refuses | Proof the refusal ran |
|---|---|---|---|
| transfer to a known payee | ? | ? | ? |
| transfer to a new address | ? | ? | ? |
| a delegation or code change | ? | ? | ? |
| an unlimited token approval | ? | ? | ? |
| anything not on this list | ? | ? | ? |
Columns two and three are usually answerable from memory, and that is the trap. The fourth is the one that matters, and in every case in this article it was empty. If you cannot say what would prove your signer refused something, you do not have a policy. You have a default.
The last row is the important one. A signer that has no answer for “anything not on this list” fails open, and failing open is what every table above records.
Closer to home
What signs on your behalf, and what does it check?
Your cloud provider ships the Unread Seal as the default
Nothing in this article requires a hot wallet. It requires a thing that signs on request, and a request path that reaches it. Most of us have built one.
Take AWS KMS, which is where a great deal of production signing actually happens. A KMS key policy is the control. Read what AWS says it controls: the Principal is “the identity that gets the permissions specified in the policy statement,” and the Action element is where you “specify the API operations to allow or deny.” So a key policy says who may call which operation.
Conditions look like they might narrow it. AWS says conditions “specify requirements that must be met for a key policy to take effect,” and that “AWS can evaluate the context of an API request to determine whether or not the policy statement applies.” Some of those keys do reach into the request’s parameters rather than its ambient context. kms:SigningAlgorithm constrains which algorithm may be used, and kms:MessageType constrains whether you are passing a raw message or a digest.
What none of them reach is the message. There is no condition key for the bytes you pass to Sign.
So a role with kms:Sign on a key can sign anything, and the policy is satisfied by the caller’s identity alone. That is trusting::Signer from the next section, in production, by default, at an enormous number of companies. It is not a defect in KMS. KMS is a key service and it does its job well. It is that the content question has no home, and most architectures never build one.
Three places to look first
A relayer or keeper that signs whatever its queue hands it. If the queue is the authorisation, then compromising whatever writes to the queue is compromising the key. The question to ask is not “who can reach the relayer” but “what transaction shapes will it refuse.”
A CI job holding a release or deploy key. The workflow asks whether the job is trusted. It does not ask whether this artifact is a release you intended to cut. SolarWinds is this exact shape, and the mitigation (reproducible builds, comparing what was reviewed against what was signed) exists and is not widely deployed.
Any Sign permission with no content policy in front of it. Grep your infrastructure for who holds signing permissions, then ask what inspects the payload. If the answer is “the service that calls it”, the authority lives in that service’s network position.
Closer to home, restated: you are probably not deciding whether to have a signing service. You already have one. You are deciding whether anything in your system has the job of reading what it signs, and whether you could prove it refused something.
The toy
The property, executable
One runnable file, no dependencies. The artifact is a property test, because the property is the invariant:
A signing policy’s decision must depend only on what is being signed, never on who asked.
Download and run it; it needs only rustc.
1
2
3
4
5
6
7
8
9
10
$ rustc --test signing.rs -o signing && ./signing --test-threads=1
running 5 tests
test tests::policy_outcome_never_depends_on_caller ... ok
test tests::policy_refuses_the_sweeper_even_to_a_trusted_caller ... ok
test tests::policy_window_cap_stops_drain_by_repetition ... ok
test tests::trusting_signer_outcome_is_entirely_the_caller ... ok
test tests::trusting_signer_signs_the_sweeper ... ok
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
trusting::Signer asks only whether the caller is trusted: the shape above. policy::Signer ignores the caller entirely and evaluates the transaction: type, recipient, amount, and a per-window cap it enforces on itself.
The search then asks the question that matters: is there any request that a trusted caller can get signed but an untrusted one cannot? For the trusting signer, every dangerous request is such a case. For the policy signer the answer is none, which is the formal way of saying that compromising the caller buys the attacker nothing.
What the test does not prove
None of this is a signer. signing.rs computes no signatures: there is no curve, no key and no verification anywhere in it. It models the policy decision and nothing else, so copying policy::Signer gives you an allow-list, not a signing service. All Rust in this article is illustrative.
And the search is not a proof. It is a bounded exhaustive search over a small alphabet: eleven request shapes: three payees by three amounts, plus two delegation targets. Under the trusting signer all eleven come out caller-dependent; under the policy signer, none do. A dangerous transaction type I did not model would pass it untouched, and that is precisely the failure mode of real allow-lists, so the toy’s limitation is also the lesson. A policy can only refuse what it can describe.
What I still find hard
- EIP-7702 is not the villain and I do not want it read that way. It does exactly what it says. The failure was a signing service that would authorise a code-assignment with the same shrug it gave a withdrawal. Blaming the feature would let every system without it feel safe, and most of them have the same hole.
- I still cannot see the shape of the oracle. Fifty valid authorisations for consecutive nonces means the signing service answered fifty times. TRM assesses they were pre-signed in advance; that is an inference from the batch, not a finding about how the signer was induced to produce them, and the defence depends on which it was.
- I do not know what any of these fixes cost. The article rests on hot wallets existing because cold storage is too slow, and then proposes a content policy on the signing path, a per-transaction quorum and per-window caps, every one of which adds latency or throttles legitimate volume. I have not priced a single one, and an exchange would. Binance’s post-mortem will not settle this either: it will explain how the attack worked, not what refusing it would have cost in throughput. Someone has to measure that separately, and until they do, every recommendation in this article is cheap to make and expensive to adopt.
- Thirty-seven minutes bothers me more than the theft. The theft needed a sophisticated chain of compromise. Outrunning the freeze needed only that issuers be slower than a swap, and the measured gap was thirty-seven minutes. Whether the attacker was racing that clock or merely slow is disputed, which is itself the problem: nobody can say what the exit cost actually was, so nobody can say what shortening the window would buy.
Sources
- Hypernative, Inside the largest crypto theft of all time: the per-chain timeline, the fifty authorisations across consecutive nonces, the delegation window, the 823M USDC and the Circle smart account owned by Binance 14, and the conclusion that the breadth points at signing infrastructure rather than a leaked key. Note its at-a-glance list names ten chains while its prose says “the seven other chains”; the enumeration used in this article is TRM’s
- TRM Labs: the ten-chain enumeration, the 20:05 start and twenty-minute reading, the 21:01 UTC Tether freeze, “50 pre-signed authorizations prepared in advance”, the $6.36bn total, and the correct formulation about key types and signing schemes
- Fireblocks, Deconstructing the Binance exploit: the EIP-7702 mechanism, the sweep figures, and the reuse of access over an hour later. Its 780M USDC and $3.52bn figures are outliers against Hypernative and Elliptic, and are attributed to it in the text
- Chainalysis, Binance hack: tracing the theft: the $325M→$285M swap over “nearly 20 minutes”, the $2.8bn frozen, and the Bitget commingling that underpins attribution. Chainalysis publishes no duration for the theft and gives no timestamps for the swap; its freeze time is an upper bound in ET (“by 5:14 p.m.”), so the 21:01 UTC first freeze in this article is TRM’s and Elliptic’s
- Elliptic: the eleven-blockchain count including NEAR, Binance 16 signing with its own key, the three-hour reading, the 12–26% swap loss, “the attacker was slow to convert them”, Teng’s remarks, and the log entry recording the FBI attribution at 23:55 UTC on 31 October
- Sygnia and BlockSec on Bybit, February 2025: the Safe developer-machine compromise, the
delegatecallreplacing the proxy implementation, the Ledger displaying the real payload, and the five drain transactions executed “without any signature verification” - FBI, DC3 and NPA joint statement on DMM Bitcoin: the pre-employment-test lure to a Ginco employee and the manipulated transaction request
- SolarWinds security advisory FAQ: the build-system insertion, the absence from the source repository, the affected release window and the certificate revocation; and Mandiant on SUNBURST for the backdoor’s own evasion measures
- AWS, Creating a key policy and the AWS KMS condition keys reference: what a key policy controls, and the absence of any condition key covering the message passed to
Sign - Binance’s own statements on X, 29–30 October 2026, for the DevOps laptop, the service container, the absence of private key compromise, the additional breach points, and the forthcoming post-mortem
- CoinDesk and TechCrunch for context and the withdrawal suspension
- Success Means Verified and Nothing Starts Clean, the earlier articles in this series
Facts as of 2 November 2026; Binance’s technical post-mortem had not been published at the time of writing. Confirmed on-chain means a reader can check it on a block explorer today; everything else carries the name of whoever reported it. Where sources disagree, the disagreement is printed rather than averaged.
