In December 2024, someone connected their wallet to what looked like a legitimate RTFKT site. They saw a standard prompt: Sign message. They clicked. Sixty seconds later, 134 NFTs were gone — 168 ETH transferred to an address they'd never heard of. The transaction hash is public record: 0xbf2542540ce22abe7a1822e15d67a50b73a7ba18e036bb305103e51606122b69. No second confirmation. No on-chain approval step they missed. Just one signature.
That signature was an EIP-712 structured data payload — the same format every major NFT marketplace uses for its order system. The victim signed a valid Seaport marketplace order. Their wallet executed it faithfully.
Understanding why requires understanding what your wallet is actually computing when you click Sign. There are three core signing primitives in EVM chains, and each one hands an attacker a different weapon.
The Baseline: What Signing Actually Means
A wallet signature is a cryptographic proof that you — the holder of a private key — approved a specific piece of data. That proof is mathematically unforgeable without your key. The problem is that "approval" can mean wildly different things depending on what you approved.
Ethereum has three main signing methods with meaningfully different security properties. Each sits at a different point on the spectrum between convenient but dangerous and structured but abusable.
For a grounding in why private keys are the ultimate lever here, see public-keys-private-keys-explained.
eth_sign: The Blank Check
eth_sign takes a raw 32-byte hash and signs it directly — no prefix, no transformation. The signed bytes are exactly what you passed in.
This is catastrophic because a 32-byte hash is also the exact format of an Ethereum transaction hash. An attacker who wants you to authorize a transfer constructs the transaction, hashes it, and asks you to sign that hash via eth_sign. Your wallet has no way to distinguish "sign this arbitrary message" from "authorize this transaction that drains your wallet."
There is no structural barrier between the two. You are handing over a blank check and trusting the other party to fill in an amount you're comfortable with.
MetaMask deprecated eth_sign under MIP-3 and disabled it by default. Most modern wallets treat it the same way. If you ever encounter a dApp that specifically requires you to re-enable eth_sign, that is itself a red flag significant enough to stop the interaction.
Attack surface: Any crafted 32-byte input. Maximum possible — a properly constructed payload can authorize any on-chain action the attacker wants.
personal_sign (EIP-191): Safer, Still Abusable
personal_sign introduced a prefix designed to make transaction spoofing impossible. Before hashing, the wallet prepends:
\x19Ethereum Signed Message:\n<length>
The resulting hash is structurally incompatible with a raw Ethereum transaction hash. A personal_sign output cannot be replayed as a transaction. This was a meaningful safety improvement.
It is the method behind Sign-In With Ethereum (SIWE, EIP-4361) — the "Login with Ethereum" flow used by many dApps for authentication. When you sign a SIWE message to prove you control an address, personal_sign is what runs under the hood.
But "cannot be a transaction" does not mean "cannot cause harm."
The attack surface shifts to the contract level. Any on-chain contract that checks a personal_sign signature as authorization — a custom minting contract, an off-chain vote that triggers on-chain actions, a legacy approval mechanism — can be targeted. If an attacker controls or compromises the contract consuming that signature, your personal_sign output becomes a weapon against you.
The attack isn't usually a direct fund drain via personal_sign alone. It's using personal_sign to impersonate you in a system that trusts the output. The risk profile depends entirely on the contract verifying the signature.
Attack surface: Contract-level replay, application-layer impersonation, legacy approval systems. Lower than eth_sign, but non-zero.
signTypedData_v4 (EIP-712): The Modern Weapon of Choice
This is the signing method behind every NFT marketplace order, every Permit approval, every Permit2 batch authorization. It is also responsible for the majority of large-scale NFT thefts in the past two years.
EIP-712 signs structured data rather than raw hashes. The wallet computes:
keccak256('\x19\x01' ‖ domainSeparator ‖ hashStruct(message))
The domainSeparator encodes the contract name, version, chain ID, and verifying contract address. The hashStruct encodes the typed message fields recursively. Modern wallets display these fields in human-readable form — you see field names and values instead of a hex blob.
This is a genuine improvement. It is also why attackers have migrated almost entirely to EIP-712 phishing.
Why EIP-712 Is the Attacker's Tool of Choice
The very thing that makes EIP-712 powerful — it encodes real, enforceable actions — makes it dangerous when the user doesn't read carefully.
Seaport orders. The Seaport protocol (OpenSea's contract backbone, also used by many other platforms) uses EIP-712 typed data for its order format. A Seaport order struct includes: offerer (you), offer (what you're selling — your NFTs), consideration (what you receive), start/end time, salt, and several other fields.
An attacker crafts a valid Seaport order. In the offer array: your 134 NFTs. In the consideration array: 0 ETH, or 1 wei, directed to the attacker. Start time: now. End time: 30 seconds from now.
They present this as a "verification" signature on a cloned RTFKT page. You sign. Seaport's contract validates your signature, confirms the order is well-formed and unexpired, and executes it. This is exactly what happened in the December 2024 RTFKT incident.
In July 2024, a Blur user signed a manipulated EIP-712 order and lost 6 BAYC, 40 Beanz, and 3 Elementals — roughly $240,000 at the time — receiving 1 wei each in return.
Permit (EIP-2612). The Permit standard allows ERC-20 token holders to grant spending allowances via signature instead of an on-chain approve() transaction. The signed struct is {owner, spender, value, nonce, deadline}. Your signature alone — no on-chain transaction required from you — authorizes a spender to move value tokens on your behalf.
According to Scam Sniffer's 2024 annual report, Permit-based phishing accounted for 56.7% of all token phishing losses. Total losses that year: $494 million across 332,000 victim addresses — a 67% year-on-year increase.
Permit2. Uniswap's Permit2 contract is a canonical approval router: users grant it a one-time unlimited approval, and from then on Permit2 manages per-dApp allowances via signature. The efficiency gain is real. The risk is that a single phishing signature to Permit2 can drain allowances across every token you've ever approved to it.
The setOwner vector — responsible for the largest single theft in Scam Sniffer's 2024 report ($55.48 million in DAI in August 2024) — similarly exploits an EIP-712 signature that modifies a high-value contract role rather than directly moving tokens.
The Aggregate Picture
Scam Sniffer reported that 2025 losses fell 83% to $84 million — attributed to improved wallet warnings, transaction simulation, and user education. That drop is real progress. It is also a ceiling, not a floor: the attack infrastructure has not gone away, and phishing-as-a-service kits have grown dramatically more effective on a per-target basis.
For a comprehensive view of how signature phishing fits into the broader drainer ecosystem this series expands on, see nft-scams-and-wallet-drainers.
How to Survive Every Signature Prompt
Every rule below is derived from the mechanics above. They are not general security hygiene — they are direct mitigations against specific attack vectors.
Rule 1: Never sign eth_sign. Full stop.
If a dApp requires eth_sign to function, leave the site. No legitimate modern protocol requires raw hash signing. This is a pre-compromise signal, not an edge case.
Rule 2: Read the domain, not just the action.
EIP-712 prompts include a domain separator with a verifyingContract address. Before reading the message fields, verify the contract address matches what the legitimate protocol publishes. Attackers clone frontends; they cannot clone the real contract address.
Rule 3: Zero-value consideration is a drain.
In any Seaport or marketplace order, locate the consideration field and verify what you receive. If consideration is 0, or 1 wei, or any amount that doesn't match your expected sale price, reject immediately. This single check catches the majority of NFT drainer orders.
Rule 4: Verify Permit and Permit2 spender addresses before signing.
A Permit signature grants real allowance to the spender field. If the spender address is not a contract you recognize from the dApp's official documentation, do not sign. Use a hardware wallet — which requires you to confirm fields on the device screen — for any Permit interaction with significant token balances. See the hardware wallet threat model for the full custody argument.
Rule 5: Use simulation before signing unknown prompts. Wallets and browser extensions that offer transaction simulation (Rabby, recent MetaMask versions, Fire) preview the state changes your signature will cause before you confirm. Enable and use simulation for every unfamiliar dApp interaction. Simulation doesn't catch everything — particularly order-based flows with delayed fulfillment — but it catches the majority of direct drain attempts and reframes the prompt from "is this text scary" to "does this state change match what I expect."
Every signature prompt your wallet shows you is a potential transaction. The UI says Sign message. The protocol may say sell your NFTs for nothing, grant unlimited token access, or transfer contract ownership. The gap between those two descriptions is where attackers live.



