Ethers.PersonalMessage (Ethers v0.7.0)

Copy Markdown View Source

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

Calculates the EIP-191 (version 0x45) hash of a personal message.

Recovers the address which signed a personal message.

Same as recover/2 but raises on error.

Checks whether signature over message was produced by expected_address.

Functions

hash(message)

@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"

recover(message, signature)

@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"}

recover!(message, signature)

@spec recover!(binary(), binary()) :: Ethers.Types.t_address() | no_return()

Same as recover/2 but raises on error.

verify(message, signature, expected_address)

@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