The one-sentence version
"Signing a message with your wallet to prove the address is yours" (Sign-In with Ethereum, EIP-4361) is more secure than a password, provided that the message clearly specifies who it is for, when it was signed, and that it can only be used once. Without any of these, an attacker can replay your signature: use it on a different website, use it a month later, or use it on a different chain. The three-piece toolkit for replay protection is: a one-time random number (nonce), domain binding, and a time window.
This article has three layers:
- Layer 1: The Intuition: What is a replay attack
- Layer 2: Real-World Use: SIWE, cross-chain replay events, API signatures
- Layer 3: Principles and Engineering details
Layer 1: The Intuition
1.1 Replay Attack
You present a signed slip to a security guard: "Please let me in." The guard verifies the signature and lets you through.
The problem is: if someone nearby photocopies this slip and presents it again tomorrow, the guard verifies the signature, finds it is still valid, and lets them in as well. The signature itself wasn't forged; it was simply reused. This is a replay attack.
1.2 How to Prevent It
Add a few more details to the slip:
| Add this | Effect |
|---|---|
| "Today's secret code from the guard is 7341" | The code is different every time; old slips become invalid (nonce) |
| "For the security guard at Company A" | It is useless if taken to Company B (domain binding) |
| "Valid from 3:00 PM to 3:10 PM" | Becomes invalid after expiration (time window) |
Layer 2: Real-World Use
- Sign-In with Ethereum (EIP-4361): The standard format for logging into websites using an Ethereum address. Wallets parse key fields (especially the domain) and display them prominently; if the domain does not match, a warning is issued.
- The Ethereum and Ethereum Classic Fork (2016): After the fork, the transaction formats of the two chains were identical. A transaction signed on ETH could be taken as-is and executed on ETC, causing many people to "mysteriously" transfer funds on the other chain. The subsequent EIP-155 included the
chainIdin the transaction signature, closing this loophole. - Cloud Service API Signatures: AWS request signing (SigV4) includes the timestamp, request path, and service region in the signature; requests exceeding a few minutes are rejected directly.
- Payment Interfaces: Payment callbacks usually include an order ID and a timestamp. The server records processed order IDs to prevent the same callback from being credited multiple times.
Layer 3: Principles and Engineering
3.1 What an SIWE message looks like
example.com wants you to sign in with your Ethereum account:
0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed
Bind this address to your account.
URI: https://example.com
Version: 1
Chain ID: 1
Nonce: 8f3k2QpZ7mXw
Issued At: 2026-11-05T10:00:00Z
Expiration Time: 2026-11-05T10:05:00Z
| Field | Protects against |
|---|---|
| Domain in the first line | Phishing sites tricking you into signing a "message for example.com": the wallet will compare the current website with the domain in the message |
| Address | The signature must recover to this address |
| Chain ID | Cross-chain replay |
| Nonce | Using the same signature a second time |
| Issued At / Expiration Time | Using a signature intercepted a long time ago |
3.2 Server-side Verification Checklist
- The nonce must be generated and recorded by the server, bound to the current session, and invalidated immediately after use;
- The domain and URI in the message must be your own;
- The current time must be between
Issued AtandExpiration Time; - Use
ecrecoverto recover the address from the signature; it must match the address in the message; - Smart contract wallets (like Safe) do not have a private key; you must use the contract's
isValidSignaturevia EIP-1271 for verification.
The 1st point is the most common mistake: if the nonce is generated by the client, or if the server does not record "used nonces," replay protection is effectively non-existent.
3.3 EIP-155: Signing the Chain ID into the Transaction
Before EIP-155, the content of a transaction signature was (nonce, gasPrice, gasLimit, to, value, data), which contained no information regarding "which chain this is." After EIP-155, it became:
The value in the signature also encodes the chainId. Now, mainstream wallets strictly enforce EIP-155, and EIP-712 structured signatures also have their own domain (including chainId and contract address); see "Blind Signing vs. What You See Is What You Sign."
3.4 Only sign what you can understand
Another prerequisite for secure signature login is that the wallet can parse and display the message. If a website asks you to sign a string of incomprehensible hexadecimal (signing a raw hash eth_sign), it might not be a login message at all, but rather a transaction hash. Therefore, a correct implementation requires: login should only use personal_sign to sign readable text, and never use eth_sign.
Summary
| Layer | Key points |
|---|---|
| Intuition | A real signature can still be photocopied and reused; you must specify "for whom, when, and only once" |
| Three-piece toolkit | nonce (sent by server, invalidated after use), domain, time window |
| On-chain | EIP-155 signs the chainId into the transaction, ending cross-chain replay |
| Bottom line | Only sign readable, parsable messages |