错误信息
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 签名,但其长度不符合它所知道的任何一种形式:65 字节(r‖s‖v)或 64 字节(EIP-2098 紧凑形式)。ethers 没有尝试恢复。这些字节通常完全是另一种签名——需要在链上验证的智能账户签名,或者来自密钥管理服务的 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` 密钥返回的是 ASN.1 DER(70~72 字节),不带恢复 ID。必须先把它转换成 r‖s‖v。
被截断或被二次编码
偶见签名在数据库列中被截断、对十六进制字符串又做了一次十六进制编码,或者在单独存储 `v` 时丢失了最后一个字节。
修复方法
- 1.
在链上验证智能账户签名
在账户上调用 `isValidSignature`(ERC-1271),或者使用为反事实(counterfactual)账户实现了 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 规范化到阶的下半区间(以太坊拒绝 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);