Caloogy Article
中文

Signature Login and Replay Protection: nonce, Domain, and Expiration Time

Date
Jul 23, 2026
Series
Web Security
Reading
4 min
Topics
SiweEip 4361Replay attackNonce

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:

  1. Layer 1: The Intuition: What is a replay attack
  2. Layer 2: Real-World Use: SIWE, cross-chain replay events, API signatures
  3. 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


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

  1. The nonce must be generated and recorded by the server, bound to the current session, and invalidated immediately after use;
  2. The domain and URI in the message must be your own;
  3. The current time must be between Issued At and Expiration Time;
  4. Use ecrecover to recover the address from the signature; it must match the address in the message;
  5. Smart contract wallets (like Safe) do not have a private key; you must use the contract's isValidSignature via 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:

Sign(RLP(nonce,gasPrice,gasLimit,to,value,data,chainId,0,0)),\mathrm{Sign}\big(\mathrm{RLP}(\text{nonce}, \text{gasPrice}, \text{gasLimit}, \text{to}, \text{value}, \text{data}, \textbf{chainId}, 0, 0)\big),

The vv 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