web3.py 是以太坊节点的 Python 客户端;消息和交易的签名交给 eth-account 包完成(0.14.0,搭配 web3 8.0.0),而它又依靠 eth-keys 完成曲线运算。大多数签名代码会像示例那样直接导入 eth_account。
Account.sign_message(encode_defunct(text=...)) 实现了 personal_sign:带十进制字节长度的 EIP-191 前缀、keccak-256、RFC 6979 nonce(已与一个独立的参考签名实现核对)以及位于低半区的 s,返回 65 字节,末尾为 v = 27 或 28。encode_defunct 是人们容易忘记的一环:用 unsafe_sign_hash 对原始字节签名会跳过前缀,而这恰恰会让签名无法在别处用作登录证明。
Account.recover_message() 既能从镜像得到的 high-S 签名中恢复出签名者,也能从 v = 0/1 的签名中恢复,因此它接受的形式比 ethers 更多——如果要存储签名或对签名去重,需要自行对 s 做规范化。64 字节的 EIP-2098 紧凑形式被拒绝,报错 Unexpected recoverable signature length: Expected 65, but got 64 bytes;恢复之前要先把它展开。
对类型化数据应使用 encode_typed_data(EIP-712),而不是自己对 JSON 计算哈希;可以用 EIP-712 查看器检查钱包被要求签署的内容。智能合约钱包需要通过 ERC-1271 调用合约,而不是使用 recover_message;登录消息在 SIWE 验证工具中最容易调试。
示例对一条登录消息签名,并比较恢复出的地址——任何逐字节比较签名的检查都应当换成这种比较。