EIP-191 personal message utilities
(the personal_sign scheme, version byte 0x45).
Personal messages are what wallets sign when an app requests personal_sign — wallet
login flows, off-chain agreements, API authentication and the like. The signed payload
is not the raw message but its EIP-191 hash:
keccak256("\x19Ethereum Signed Message:\n" <> Integer.to_string(byte_size(message)) <> message)This module provides the pure primitives: hashing (hash/1), signer recovery
(recover/2) and signature verification (verify/3). To produce a signature use
Ethers.personal_sign/2, which routes through the configured Ethers.Signer.
Message encoding
The message is always treated as raw bytes, exactly as given. In particular a
"0x..."-prefixed string is signed as the literal text, not hex-decoded. If you
want to sign pre-encoded bytes, decode them yourself first (e.g. with
Ethers.Utils.hex_decode!/1).
Verification scope
Recovery and verification here are ecrecover-based and therefore work for
externally-owned accounts (EOAs) only. Signatures from smart-contract wallets
(ERC-1271/ERC-6492) always fail these checks. Prefer
Ethers.Signature.verify_message/4, which verifies EOA and smart-contract wallet
signatures alike; use the functions here when you specifically need a pure, RPC-free
check for signatures known to come from EOA keys.
Summary
Functions
@spec hash(binary()) :: <<_::256>>
Calculates the EIP-191 (version 0x45) hash of a personal message.
The message is treated as raw bytes. Returns the 32-byte digest.
Examples
iex> Ethers.PersonalMessage.hash("Hello world") |> Ethers.Utils.hex_encode()
"0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede"
@spec recover(binary(), binary()) :: {:ok, Ethers.Types.t_address()} | {:error, term()}
Recovers the address which signed a personal message.
signature may be a 0x-prefixed hex string or a raw 65-byte binary (r ‖ s ‖ v).
Both the message-signature convention (v ∈ {27, 28}) and raw parity (v ∈ {0, 1})
are accepted.
Returns
{:ok, address}with the checksummed signer address on success.{:error, reason}if the signature is malformed or recovery fails.
Examples
iex> signature =
...> "0x15a3fe3974ebe469b00e67ad67bb3860ad3fc3d739287cdbc4ba558ce7130bee" <>
...> "205e5e38d6ef156f1ff6a4df17bfa72a1e61c429f92613f3efbc58394d00c9891b"
iex> Ethers.PersonalMessage.recover("Hello world", signature)
{:ok, "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"}
@spec recover!(binary(), binary()) :: Ethers.Types.t_address() | no_return()
Same as recover/2 but raises on error.
@spec verify(binary(), binary(), Ethers.Types.t_address()) :: boolean()
Checks whether signature over message was produced by expected_address.
Recovers the signer via recover/2 and compares it to expected_address. The
comparison is done on the decoded 20-byte addresses, so checksum/case differences are
ignored. Returns false (does not raise) when the signature is malformed.
EOA-only — prefer Ethers.Signature.verify_message/4 to also support smart-contract
wallets (see "Verification scope" in the module docs).
Examples
iex> signature =
...> "0x15a3fe3974ebe469b00e67ad67bb3860ad3fc3d739287cdbc4ba558ce7130bee" <>
...> "205e5e38d6ef156f1ff6a4df17bfa72a1e61c429f92613f3efbc58394d00c9891b"
iex> Ethers.PersonalMessage.verify("Hello world", signature, "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266")
true