web3.py / eth-account / eth-keys (Python)Web3 钱包与签名
错误信息
Unexpected recoverable signature length: Expected 65, but got 64 bytes
也可能输出为
- eth_utils.exceptions.ValidationError: Unexpected recoverable signature length: Expected 65, but got 64 bytes
“but got”之后的字节数就是你的签名长度。异常类为 `eth_utils.exceptions.ValidationError`。
web3.py / eth-account / eth-keys (Python) — eth_utils.exceptions.ValidationError from Account.recover_message
含义
eth-keys(eth-account 和 web3.py 都依赖它)只能从恰好 65 字节构建可恢复签名:r(32)、s(32)和恢复字节 v。你传入的签名长度不同——这里是 64 字节——因此恢复根本无法开始。web3.py 的 `w3.eth.account.recover_message` 也会抛出同样的错误,因为它把处理委托给了 eth-account。
原因
v 被单独存储或丢失了
常见只存储 `r` 和 `s` 列的后端,或者截取签名的前端,会丢掉最后一个字节。没有 v,就无法唯一地恢复出公钥。
EIP-2098 紧凑签名
偶见64 字节的紧凑签名把恢复位并入 s 的最高位。eth-account 不能直接接受这种签名。
把十六进制文本当作字节传入
偶见传入 `signature_hex.encode()` 得到的是 132 字节的 ASCII,而不是 65 字节的签名。应直接传入十六进制字符串本身,或者对去掉 `0x` 前缀的字符串调用 `bytes.fromhex(...)`。
修复方法
- 1.
原样传入钱包返回的完整签名
`personal_sign` / `eth_signTypedData_v4` 返回 65 字节的十六进制字符串;在整个流程中保持它完整不变。
python from eth_account import Account from eth_account.messages import encode_defunct # Pass the 0x-prefixed hex string exactly as the wallet returned it (132 hex chars + "0x"). signer = Account.recover_message(encode_defunct(text=message), signature=signature_hex) assert signer.lower() == expected_address.lower() - 2.
展开紧凑签名
从后半部分的最高位分离出 yParity,然后追加 v = 27 + yParity。
python def expand_eip2098(sig: bytes) -> bytes: """64-byte compact signature (r || yParityAndS) -> 65-byte r || s || v.""" r, ys = sig[:32], int.from_bytes(sig[32:], "big") y_parity = ys >> 255 s = ys & ((1 << 255) - 1) return r + s.to_bytes(32, "big") + bytes([27 + y_parity])