Key, signer and infrastructure attacks
In one line
Section titled “In one line”The largest losses of recent years were not contract bugs. A signer approved something other than what its screen showed, or a single machine or RPC lied. The fix is independence: several parties that each read the raw transaction and can each refuse.
Why it exists
Section titled “Why it exists”A private key is like an SSH key with money behind it. It cannot be revoked or reset, and whoever has it is you. The industry answered with a series of controls, and attackers moved one step up each time.
- Single key. One stolen key loses everything.
- Multisig. M of N keys must sign, enforced by a contract. Attackers responded by compromising several signers at once (Ronin 2022, 5 of 9 keys).
- Hardware wallets. Keys cannot be copied off the device. Attackers stopped stealing keys and started changing what the signers see: the device signs a hash, and the screen that explains the hash belongs to a compromised web UI (Radiant 2024, WazirX 2024, Bybit 2025).
- Pre-signed transactions. Convenient for operations. Attackers collected signatures in advance through social engineering and used them later (Drift 2026).
- Off-chain verifiers and RPC. A service that attests “this happened on chain A” believes its RPC nodes. Poison the nodes and the service signs a lie (KelpDAO 2026).
In every case the contracts did what they were told.
How it works
Section titled “How it works”Bybit, February 2025
Section titled “Bybit, February 2025”- Attackers compromised a Safe{Wallet} developer’s machine.
- They swapped the JavaScript served from Safe’s S3 bucket. The malicious code targeted Bybit’s wallet.
- Bybit’s signers opened the usual web UI. It showed a normal transfer.
- Their devices signed what the UI built: a
delegatecallto an attacker contract. - A
delegatecallruns foreign code on the wallet’s own storage. That code replaced the Safe’s implementation address. - The wallet now ran the attacker’s code. About $1.46B was drained.
The screen said X and the signature meant Y.
Intent, validator, signer
Section titled “Intent, validator, signer”- Intent. The request is recorded in plain terms first: “move 1,000 ETH to X”.
- Validator. A separate service takes the raw transaction, decodes the calldata itself, simulates it on a fork, and compares the result with the intent and with policy: no
delegatecall, no admin functions, destination on an allowlist, amount within limits. Anything not in the intent is rejected. - Signer. The signing service re-checks its own policy on the same raw bytes before it signs. It runs on separate infrastructure, owned by a separate team.
- Chain. Only what was meant gets through.
- Timelock. Admin actions go through a delay, so a bad signature still leaves a window to react.
Treat the signing UI as untrusted. It is a convenience for humans, not a source of truth.
The incidents
Section titled “The incidents”Loss figures are approximate, at the time of the attack.
| Incident | Loss | What happened | The control that would have stopped it |
|---|---|---|---|
| Bybit, Feb 2025 | about $1.46B | Safe{Wallet} developer machine compromised; JavaScript on their S3 bucket swapped. Signers saw a normal transfer and signed a delegatecall that replaced the Safe’s implementation. |
Decode and simulate the raw transaction on an independent device; verify the Safe transaction hash outside the web UI; a policy that rejects delegatecall and implementation changes from a cold wallet used for routine transfers. |
| Radiant, Oct 2024 | about $50M | Malware on three signer devices; the UI showed benign transactions, hardware wallets signed an ownership transfer. | Clear signing on hardware, independent transaction verification, timelock on ownership changes. |
| WazirX, Jul 2024 | about $230M | The multisig signing UI showed different data from what was signed; the contract was upgraded. | Same: do not trust one UI, verify calldata independently. |
| Drift, Apr 2026 | about $285M | Months of social engineering of contributors; signers pre-signed transactions using Solana durable nonces; these were later used to hand admin to the attacker. | Never pre-sign; admin changes behind a timelock; signer devices managed and isolated; per-transaction policy, not per-person trust. |
| KelpDAO, Apr 2026 | about $292M | The rsETH bridge route was verified by a single (1-of-1) LayerZero verifier whose RPC infrastructure was poisoned; a forged message released funds. Kelp and LayerZero dispute who chose the setup. | Multiple independent verifiers on independent RPC; no single node’s view of the chain decides money. |
| Ronin, Mar 2022 | about $625M | 5 of 9 validator keys taken, one through a fake job offer PDF; noticed six days later. | Key diversity across organisations, outflow monitoring that pages in minutes. |
Compare
Section titled “Compare”Ways to hold a key
Section titled “Ways to hold a key”| Model | How it works | Where it is enforced | What the chain sees | Fails when |
|---|---|---|---|---|
| Single EOA key (hot wallet) | One private key in a file, a KMS or a hardware wallet | Nowhere beyond possession | One signature | The key or the machine using it is compromised |
| Multisig (for example Safe) | The 2-person rule for prod: M of N keys must sign | On-chain, in the wallet contract’s code | A contract call carrying M signatures | M signers are tricked or compromised together, often through one shared UI |
| MPC (multi-party computation) | A split key; the whole key never exists in one place | Off-chain, among the parties holding shares | One normal signature | One operator controls enough shares (Multichain 2023), or all parties sign what one compromised service asked for |
| Multisig or MPC plus timelock | As above, and the action only takes effect after a delay | On-chain delay | A queued action that anyone can see | Nobody watches the queue, or the delay is set to zero |
| Intent, validator, signer | Any of the above, with independent policy checks on the raw transaction before signing | Off-chain, in two separate services | Only transactions that match a recorded intent | Validator and signer share infrastructure, code or a team |
What the signer sees before approving
Section titled “What the signer sees before approving”| Method | What is shown | What it protects against | What it misses |
|---|---|---|---|
| Blind signing | A hash, or nothing | Nothing | Everything; the signer approves whatever was built |
| UI summary | The web app’s description of the transaction | Honest mistakes | A compromised web app describes one thing and builds another |
| Clear signing | The device decodes the fields and shows them on its own screen | A lying web UI | Calls the device cannot decode; meaning that only shows in simulation |
| Independent decode and simulation | A second system decodes the raw calldata and simulates the state change | A lying UI and unexpected side effects such as an implementation change | A compromise of that second system, unless the signer also checks |
| Hash check outside the UI | The Safe transaction hash is recomputed from the intended fields and compared with the hash on the device | A UI that swaps the transaction | Mistakes in the intended fields themselves |
What breaks
Section titled “What breaks”| Symptom | Cause | Check |
|---|---|---|
A signing request has a long opaque data field |
You do not know what it calls | cast 4byte-calldata $CALLDATA, or cast decode-calldata "f(address,uint256)" $CALLDATA when the signature is known |
A Safe transaction for a routine transfer has operation = 1 |
It is a delegatecall, not a call. In Safe, operation 0 is call and 1 is delegatecall. |
Reject by policy unless the target is on a short allowlist |
| A transfer request targets an unknown contract instead of the token or the recipient | The UI built something other than what it shows | Compare to, value and decoded arguments with the intent |
| After a signed transaction, the wallet behaves differently | Its implementation was replaced | cast storage $SAFE 0 reads slot 0, where a Safe proxy keeps its implementation (singleton) address; compare with the known value |
| Ownership or admin changed with no change ticket | Pre-signed or socially engineered transaction | Alert on OwnershipTransferred and RoleGranted on contracts, AddedOwner, RemovedOwner and ChangedThreshold on a Safe |
| A verifier or bot acted on an event that never happened | Its RPC was poisoned or lagging | Compare block hash and balances across two or more providers plus your own node. See Chain mechanics. |
| Funds left days ago and nobody noticed | No outflow monitoring | Alert on outflows against a baseline, paging within minutes |
Operational rules that follow from the incidents:
- Never pre-sign anything except a pause. See Defence stack.
- Put ownership, admin and upgrade changes behind a timelock. See Proxies and upgrades.
- Spread keys across organisations and devices, not across people in one company on one fleet of laptops.
- Policy is per transaction, not per person. A trusted person on a compromised machine signs what the attacker wants.
- No single node’s view of the chain decides money.
Keywords
Section titled “Keywords”private key, signer, blind signing, clear signing, hardware wallet, multisig, Safe, Safe{Wallet}, threshold, M of N, MPC, threshold signature, key share, timelock, delegatecall, operation 1, singleton, implementation swap, UI tampering, supply chain, S3 bucket, intent, independent validator, calldata decoding, transaction simulation, policy engine, allowlist, pre-signed transaction, durable nonce, social engineering, fake job offer, RPC poisoning, DVN, 1-of-1 verifier, outflow monitoring, OwnershipTransferred, Bybit, Radiant, WazirX, Drift, KelpDAO, Ronin, Multichain, Lazarus, TraderTraitor.
Sources
Section titled “Sources”- FBI: North Korea responsible for $1.5 billion Bybit hack (PSA, 26 February 2025)
- Sygnia: investigation into the Bybit hack
- Safe Ecosystem Foundation: statement on the Bybit incident
- LayerZero: KelpDAO incident statement
- LayerZero Labs: KelpDAO incident report
- Safe docs: Smart Account overview
- Safe docs: Smart Account signatures
- Safe smart account source code (SafeProxy, Enum.Operation, OwnerManager events)
- OpenZeppelin Contracts: Governance, including TimelockController
- Foundry: cast decode-calldata
- Foundry: cast 4byte-calldata