← Protocols
EIP-191 — Signed Data Standard
Standard / EIP·EVM

EIP-191 — Signed Data Standard

01Description

Standard envelope for non-transaction signed data: `0x19 <version byte> <version-specific data> <data to sign>`. The `0x19` prefix prevents signed messages from being confused with valid RLP transactions. Status: Final (Standards Track / ERC). Version `0x45` (`personal_sign`) is the default for human-readable strings; `0x01` is used by EIP-712; `0x00` for validator-prefixed data.

02Best for
  • 01personal_sign / login challenges (Sign-In With Ethereum)
  • 02off-chain message authentication
  • 03preventing tx-replay of signed payloads
  • 04the 0x01 envelope underlying EIP-712
03Install
  • pnpm add viem # signMessage / verifyMessage / recoverMessageAddress / hashMessage
  • pnpm add ethers # signer.signMessage / verifyMessage
  • pnpm add siwe # Sign-In With Ethereum (EIP-4361 over 191)
05Prompt snippet
Use EIP-191 to sign arbitrary off-chain data. The standard defines three versions: `0x00` = validator-specific data (`0x19 0x00 <validator address> <data>`), `0x01` = structured data per EIP-712 (`0x19 0x01 <domainSeparator> <hashStruct>`), `0x45` = `personal_sign` (`0x19 "Ethereum Signed Message:\n" <len> <message>`). With viem: `await walletClient.signMessage({ account, message })` produces a `personal_sign` (`0x45`) signature; `hashMessage(message)` reproduces the digest; `verifyMessage({ address, message, signature })` and `recoverMessageAddress(...)` complete the loop. For SIWE, build the EIP-4361 message string and hand it to `signMessage` — it rides on top of `personal_sign`. On-chain, recover with `ecrecover(hashMessage(msg), v, r, s)`. For contract signers, pair with EIP-1271.
06Gotchas
  • `personal_sign` prefixes `"\x19Ethereum Signed Message:\n" + length(message)` where `length` is the DECIMAL ASCII string length of the byte payload (e.g. 32-byte hash → `"32"`). Off-by-one or hex-vs-bytes confusion silently produces a wrong digest.
  • `eth_sign` (raw) is NOT EIP-191 — it signs the bare 32-byte hash and is dangerous (can be tricked into signing a transaction). Most wallets disable it; never use it.
  • Version byte `0x01` is just the envelope; the structured-data spec (typeHash etc.) lives in EIP-712. `0x19 0x01` alone is incomplete.
  • Different libraries normalize newlines differently (`\n` vs `\r\n`) — always pre-encode to UTF-8 bytes before signing if your message contains line breaks.
  • Replay protection is NOT built in — include a nonce + chainId + expiry in the message body (SIWE does this). The 0x19 prefix only separates messages from transactions, not messages from each other.
  • Smart-account signatures will not recover via `ecrecover` — gate verification on `code.length > 0 ? isValidSignature(...) : ecrecover(...)`.
07Alternatives