处理以太坊签名的 Go 服务通常会导入 github.com/ethereum/go-ethereum/crypto 和 accounts。启用 cgo 时,这个包使用 go-ethereum 内置的 libsecp256k1 副本;设置 CGO_ENABLED=0 时,则退回到纯 Go 的 decred 实现——本次检查分别运行了两种构建,所有事实的结果完全一致。
crypto.Sign(hash, key) 只接受恰好 32 字节的输入(否则报错 hash is required to be exactly 32 bytes),既不计算哈希,也不添加前缀。登录消息必须先经过 accounts.TextHash,它会添加 EIP-191 前缀并用 keccak-256 计算哈希——本次检查用一个独立的 keccak 实现核对了结果。如果直接对消息的 keccak 哈希签名,得到的签名永远无法与 ethers、viem 和钱包的结果匹配。
签名为 65 字节,恢复 ID 作为 v,取值为 0 或 1,而钱包、ethers、viem 和 web3.py 在这个位置放的是 27 或 28。crypto.Ecrecover 拒绝了 v = 27/28,报错 invalid signature recovery id,因此要像示例那样,输入时减去 27,输出时再加回去。nonce 遵循 RFC 6979——签名与一个独立的参考实现一致——并且 s 始终是 low-S。
两条验证路径对 high-S 的处理不一致。crypto.VerifySignature 接收不含 v 的 64 字节,拒绝了镜像签名 (r, n − s),但 crypto.Ecrecover 和 crypto.SigToPub 能从中恢复出正确的公钥,EVM 的 ecrecover 预编译合约也是如此。因此,恢复地址的合约和服务如果把签名当作唯一标识,就必须自行拒绝 high-S。
公钥以 65 字节的 SEC1 未压缩点或 33 字节的 SEC1 压缩点传递,私钥以 32 字节的标量传递。要查看签名遵循哪种约定,可以把它粘贴到以太坊签名验证工具中;同一地址的多笔交易中重复出现的 nonce,可以在以太坊 nonce 重用检测中发现。