ethers は、アプリケーションが Ethereum とやり取りするために必要なもの(プロバイダー、コントラクト、ABI のエンコードとデコード、ENS)をすべて備えています。JavaScript のコードの多くは、その Wallet クラスと SigningKey クラスで署名しています。曲線の演算には @noble/curves を使っており、ethers 6.17.0 はそのバージョン 1.2.0 に固定しています。
wallet.signMessage() は personal_sign を実装しています。メッセージの前に \x19Ethereum Signed Message:\n とメッセージの長さを付け(EIP-191)、keccak-256 でハッシュし、v = 27 または 28 の r ‖ s ‖ v からなる 65 バイトを返します。チェックでは、ダイジェストを独立した別の keccak 実装と、署名を @noble/curves の RFC 6979 の出力と照合して確認しました。そのため、同じ鍵とメッセージからは常に同じバイト列が得られます。EIP-712 のダイジェストやトランザクションハッシュなど、計算済みのハッシュに署名するには SigningKey.sign() を使ってください。この関数はちょうど 32 バイトを受け取り、プレフィックスを付けません。それ以外の長さでは invalid digest length で失敗します。
ethers は入力の形状には寛容ですが、正規形であるかどうかには厳格です。verifyMessage() は v = 27/28、v = 0/1、64 バイトの EIP-2098 コンパクト形式のいずれからも署名者を復元しましたが、s を n − s に置き換えた high-S 署名は、数学的には有効であるにもかかわらず non-canonical s; use ._s で拒否しました。s を正規化しない OpenSSL、クラウドの KMS、ハードウェアモジュールが生成した署名を ethers で受け入れられるようにするには、先に low-S に変換(s を n − s に置き換え、v を反転)しなければなりません。署名がどの形式かは、Ethereum 署名検証ツールで確認できます。
ウォレットによるログインで起きる検証のバグの多くは、曲線とはまったく関係がありません。改行や末尾の空白だけが異なるメッセージ、EIP-712 ドメインで異なるチェーン ID、ecrecover ではなく ERC-1271 で署名を検証する必要があるスマートコントラクトウォレットなどが原因です。型付きデータは EIP-712 インスペクターで、ログインメッセージ全体は SIWE 検証ツールで確認してください。
サンプルでは、ログインメッセージに署名し、アドレスを復元して、コンパクト形式を示しています。同じ署名には有効なエンコーディングが複数あるため、比較するのは復元したアドレスにしてください。署名のバイト列を比較してはいけません。