Add EIP-191 personal message (personal_sign) support#265
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces first-class support for EIP-191 “personal message” signing (personal_sign) across the Ethers API surface (hashing/recovery/verification utilities plus signer integrations), enabling common wallet-login/auth flows.
Changes:
- Add
Ethers.PersonalMessagefor EIP-191 hashing, signer recovery, and signature verification. - Add optional
sign_message/2callback toEthers.Signer, implemented byEthers.Signer.LocalandEthers.Signer.JsonRPC(viapersonal_sign). - Add top-level
Ethers.sign_message/2andsign_message!/2, plus comprehensive tests and changelog entry.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/ethers/signer/local_test.exs | Adds Local signer sign_message/2 test vectors and JsonRPC parity check. |
| test/ethers/signer/json_rpc_test.exs | Adds JsonRPC signer personal_sign coverage and verification assertions. |
| test/ethers/personal_message_test.exs | New end-to-end unit tests for hashing, recovery, verification, and Ethers.sign_message/*. |
| lib/ethers/signer/local.ex | Implements sign_message/2 for local secp256k1 signing over the EIP-191 digest. |
| lib/ethers/signer/json_rpc.ex | Implements sign_message/2 delegating to JSON-RPC personal_sign. |
| lib/ethers/signer.ex | Extends signer behaviour with optional sign_message/2 callback and docs. |
| lib/ethers/personal_message.ex | Adds EIP-191 personal message primitives (hash, recover, verify). |
| lib/ethers.ex | Adds top-level sign_message/2 and sign_message!/2 routing through configured signer. |
| CHANGELOG.md | Documents the new EIP-191 personal message support. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Introduce Ethers.PersonalMessage with pure EIP-191 (version 0x45) primitives — hash/1, recover/2, recover!/2 and verify/3 — plus a new optional sign_message/2 signer callback implemented by the Local signer (secp256k1) and the JsonRPC signer (personal_sign), exposed through Ethers.sign_message/2 and Ethers.sign_message!/2. Messages are treated as raw bytes exactly as given; 0x-prefixed strings are signed as literal text, matching ethers.js string semantics. All vectors are cross-checked against foundry's cast and anvil. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alisinabh
force-pushed
the
feat/eip-191-personal-message
branch
from
July 19, 2026 04:29
adbc89c to
4ed65a5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First PR of the v0.7 "Auth" tier (stacked chain: this → #266 universal verification → #267 SIWE).
What
Ethers.PersonalMessagemodule:hash/1(EIP-191 version0x45),recover/2,recover!/2, and EOA-onlyverify/3(case-insensitive address compare, checksummed output).personal_sign/2onEthers.Signer— named after the RPC method it mirrors — implemented for:Ethers.Signer.Local(secp256k1 sign over the EIP-191 hash,v ∈ {27, 28})Ethers.Signer.JsonRPC(delegates topersonal_sign)Ethers.personal_sign/2andpersonal_sign!/2, mirroringsign_typed_data/2.Design notes
personal_sign(rather than ethers.js/viem-stylesign_message) is unambiguous about the signing scheme and matches the module (PersonalMessage) and the underlying RPC method; it also leaves clean room for futuresign_*callbacks (e.g. EIP-7702sign_authorization)."0x..."string signs as literal text (documented) — matching ethers.jssignMessagestring semantics. No{:raw, ...}variant.0x-hex or raw 65 bytes,v ∈ {0, 1, 27, 28}.Testing
cast wallet sign/cast hash-message) across 2 keys and 4 message shapes (ASCII, empty, multibyte UTF-8, raw bytes), plus a live byte-for-byte Local↔JsonRPC cross-check against anvil'spersonal_sign.mix format,mix credo --strict,mix dialyzerclean.🤖 Generated with Claude Code