エラーメッセージ
invalid raw signature length
別の表記
- invalid raw signature length (argument="signature", value="0x…", code=INVALID_ARGUMENT, version=6.17.0)
- TypeError: invalid raw signature length
`err.shortMessage` は「invalid raw signature length」で、`err.message` には引数、値、ethers のバージョンが追加されます。エラーは `code: "INVALID_ARGUMENT"` を持つ `TypeError` です。
ethers v6 (JavaScript) — Signature.from() inside verifyMessage / verifyTypedData / recoverAddress
このエラーの意味
ethers はアドレスを復元するために署名のバイト列を ECDSA 署名として解釈しようとしましたが、長さが ethers の知っているどちらの形式、つまり 65 バイト(r‖s‖v)にも 64 バイト(EIP-2098 のコンパクト形式)にも当てはまりませんでした。復元は試行されていません。このバイト列は通常、まったく別の種類の署名です。オンチェーンで検証しなければならないスマートアカウントの署名か、鍵管理サービスが返す DER エンコードの署名です。
発生する理由
スマートコントラクトウォレットの署名
よくあるSafe、Coinbase Smart Wallet、Argent などの ERC-4337 アカウントは、アカウントのコントラクトでしか検証できない署名(ERC-1271)を返します。まだデプロイされていないアカウントでは、ERC-6492 に従ってラップされていることがよくあります。長さは数百バイトあり、`verifyMessage` では検証できません。
AWS KMS または Google Cloud KMS からの DER 署名
よくあるAWS KMS の `ECC_SECG_P256K1` 鍵と Google Cloud KMS の `EC_SIGN_SECP256K1_SHA256` 鍵は、リカバリ ID を含まない ASN.1 DER(70〜72 バイト)を返します。まず r‖s‖v に変換する必要があります。
切り詰められている、または二重にエンコードされている
ときどきデータベースの列の長さで切り詰められた署名、16 進文字列をさらに 16 進にしたもの、`v` を別に保存したときに最後のバイトが失われた値などが該当します。
修正方法
- 1.
スマートアカウントはオンチェーンで検証する
アカウントの `isValidSignature`(ERC-1271)を呼び出すか、まだデプロイされていないアカウントにも対応できるよう ERC-6492 を実装したライブラリを使ってください(たとえば、パブリッククライアントを使う viem の `verifyMessage`)。
js import { Contract, hashMessage, JsonRpcProvider } from "ethers"; // Smart accounts (Safe, Coinbase Smart Wallet, ERC-4337 accounts) are verified on-chain (ERC-1271). const provider = new JsonRpcProvider(process.env.RPC_URL); const account = new Contract(address, ["function isValidSignature(bytes32,bytes) view returns (bytes4)"], provider); const magic = await account.isValidSignature(hashMessage(message), signature); const valid = magic === "0x1626ba7e"; - 2.
DER を r‖s‖v に変換する
r と s を解析し、s を位数の下半分に正規化したうえで(Ethereum は high-S の値を拒否します)、両方のリカバリビットを試して、期待するアドレスが得られるほうを見つけてください。
js import { Signature, recoverAddress, hashMessage, toBeHex } from "ethers"; import { secp256k1 } from "@noble/curves/secp256k1.js"; // @noble/curves v2 const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n; // secp256k1 order // KMS/HSM: parse DER, move s to the lower half (Ethereum rejects high-s), then find the recovery bit. function derToEthSignature(derBytes, digest, expectedAddress) { const { r, s } = secp256k1.Signature.fromBytes(derBytes, "der"); const lowS = s > N / 2n ? N - s : s; for (const yParity of [0, 1]) { const candidate = Signature.from({ r: toBeHex(r, 32), s: toBeHex(lowS, 32), yParity }); if (recoverAddress(digest, candidate) === expectedAddress) return candidate.serialized; // 65 bytes } throw new Error("signature does not belong to " + expectedAddress); } const signature = derToEthSignature(kmsDer, hashMessage(message), kmsAddress);