如何使用Python的eth-utils复现ethereumjs-util的以太坊签名验证逻辑?
解决以太坊签名验证的Python复现问题
我来帮你一步步搞定用Python的eth-utils复现以太坊签名验证的问题,针对你提到的两个核心痛点,我会逐个拆解并给出可直接运行的完整代码。
一、让JSON字符串格式与JavaScript完全对齐
JavaScript的JSON.stringify()默认输出无空格的紧凑格式,但Python的json.dumps()默认会添加空格分隔符,这会导致后续哈希计算完全不同。要解决这个问题,你只需要:
- 给
json.dumps()加上separators=(',', ':')参数,彻底去掉所有空格 - 确保Python字典的键顺序和JavaScript对象的键顺序一致(Python 3.7+的普通字典已经保留插入顺序,直接按JS对象的键顺序构造字典即可)
比如对应你JS里的message对象,Python中用json.dumps(message, separators=(',', ':'))就能生成和JSON.stringify(message)完全相同的字符串。
二、eth-utils库的等价函数对应关系
eth-utils本身提供了大部分和ethereumjs-util对应的功能,部分场景可以配合eth-account库(它依赖eth-utils)简化操作,以下是精准的对应关系:
| ethereumjs-util方法 | Python等价实现 |
|---|---|
util.keccak() | eth_utils.keccak(),传入字节数据即可 |
util.fromRpcSig() | eth_utils.ec_recoverable_signature_from_rpc(),解析RPC格式签名返回(v, r, s) |
util.ecrecover() | eth_utils.ecrecover(),传入消息哈希、v、r、s恢复公钥 |
util.pubToAddress() | eth_utils.public_key_to_address(),公钥转地址 |
util.bufferToHex() | eth_utils.to_hex(),将字节转为十六进制字符串 |
util.toBuffer() | eth_utils.to_bytes(),将十六进制字符串转为字节 |
三、完整Python代码实现
import json import eth_utils # 对应JS中的message对象 message = {"id": 1, "metadata": "abc"} # 替换成你的实际签名字符串(需是0x开头的RPC格式) signature = "<你的签名字符串,比如0x...>" # 1. 生成与JS逻辑一致的nonce字符串 message_json = json.dumps(message, separators=(',', ':')) nonce_str = "\x19Ethereum Signed Message:\n" + str(len(message_json)) + message_json # 2. 计算keccak哈希 nonce_hash = eth_utils.keccak(nonce_str.encode('utf-8')) # 3. 解析RPC格式的签名,提取v、r、s v, r, s = eth_utils.ec_recoverable_signature_from_rpc(signature) # 4. 从哈希和签名恢复公钥 pub_key = eth_utils.ecrecover(nonce_hash, v, r, s) # 5. 公钥转以太坊地址 addr_buf = eth_utils.public_key_to_address(pub_key) addr = eth_utils.to_hex(addr_buf) print("恢复出的以太坊地址:", addr)
额外注意事项
- 确保签名字符串是标准RPC格式(以
0x开头,包含完整的v、r、s十六进制数据) - 若遇到公钥恢复失败,优先检查JSON字符串是否和JS端完全一致——这是最容易踩坑的地方
- 建议使用最新版本的
eth-utils和eth-account库,避免旧版本的函数差异问题
内容的提问来源于stack exchange,提问作者David Cheung
相关产品推荐
相关产品推荐

