密码学 26:私钥、公钥、地址与钱包为什么是不同对象
钱包界面只显示一条收款地址,却能管理多条链、多个账户、找零和不同的接收方式。把“私钥→公钥→地址”画成三格直线,确实能解释某一类外部账户,却解释不了为什么 Bitcoin 同一公钥能对应不同地址、为什么合约也有地址,以及只有地址的钱包为什么不能签交易。地址是告诉付款者如何指向一种接收条件的编码,钱包则是保存、发现、构造并可能签署相关交易的程序或设备。
从一个临时私钥到两种 Bitcoin 接收方式
在本章的临时实验中,coincurve 随机生成一把 secp256k1 私钥;绝不保存到 Git 或文章中。对应公钥有 33 字节压缩表示和 65 字节以 04 开头的非压缩表示。密码学上的椭圆曲线点相同,但进入后续哈希的字节序列不同。Bitcoin 常见 P2PKH、P2WPKH v0 可以取压缩公钥的 SHA256→RIPEMD160,生成 20 字节 HASH160,然后选择不同的锁定条件与网络编码:
1 | |
同一把私钥并不改变,改变的是输出脚本表达的支付条件和把条件给人类或钱包交换时使用的地址格式。编码中的校验和能发现部分输入错误,不证明收款对象是谁、接收条件是否适合自己,更不能阻止付款者被钓鱼替换地址。bcrt 标识 regtest,不能把它原样拿到主网;P2PKH 在 regtest 使用与 testnet 相同的版本前缀,地址本身并不足以证明当前节点真的在 regtest。脚本地址、Taproot 的 tweaked 公钥地址和多方控制条件有各自规则,不能把上图的 HASH160 当成“所有 Bitcoin 地址都是公钥哈希”。
Ethereum 的 EOA 不是 Bitcoin 的换前缀版本
对于 Ethereum 外部账户(EOA),同样可以从 secp256k1 公钥的非压缩 XY 坐标(65 字节格式先去掉 04 标记)取 64 字节,用 Ethereum 的 Keccak-256 求摘要,再取最后 20 字节形成 0x 后面 40 个十六进制字符的地址。本章先用小写形式显示;可选择 EIP-55 大小写校验显示,但地址数值是同一份 20 字节。
1 | |
这里的 Keccak-256 与 Python hashlib.sha3_256 不兼容;虽然都和 Keccak 家族有关,实际 padding 与输出不同,直接替换会导出另一条地址。验证器要按照目标链的规范确定被哈希的公钥表示,不能把旧式 Bitcoin 的 HASH160、Ethereum 的 Keccak-256 与“浏览器证书公钥”混成一个通用地址算法。Ethereum 还有合约账户地址;它不是由“合约有一把 EOA 私钥,取最后 20 字节”这样产生,合约执行与账户控制规则也不同。
钱包不等于它显示的地址
钱包可能持有签名私钥,也可能只有公钥、扩展公钥或脚本描述符以便只观察接收记录;后者能给出预期地址或发现匹配输出,却不能冒出有效签名。HD 派生可以让一个备份恢复许多子密钥,但还需要路径、网络、接收脚本和扫描范围,详见本系列 31;不能把“拥有某个地址的文本”当作“持有能满足其花费条件的私钥”。
在贯穿的 HTTPS RPC 测试交易中,钱包用其密钥签具体交易字节,RPC TLS 只负责这一跳通信身份/机密性。把钱包里某个地址复制给 RPC,既不证明用户批准了链上转账,也不证明交易已执行、进块或对应现实订单已经履约。反过来,区块链共识需要检查锁定条件、账户状态和交易规则,不能只看签名能否被同一个公钥验证。
原语实验与明示的空白
examples/cryptography/26_wallet_objects.py 在隔离环境使用成熟 secp256k1 与 Keccak 库做真原语:分别编码 regtest P2PKH 和 P2WPKH v0 地址、计算 Ethereum EOA 小写地址;用库对一条教学消息做 ECDSA 真签名,正确公钥验签成功,错误公钥或改消息失败。消息明写 not a Bitcoin sighash or Ethereum transaction,因此不能拿验签结果冒充真实账本交易授权。另以公开的 secp256k1 生成元公钥验证熟知的 Bitcoin/Ethereum 地址派生向量,不需要把任何生产私钥放入文件。
先按仓库内 examples/cryptography/README.md 完成环境准备;以下命令从仓库根目录执行。
1 | |
2026-10-06 UTC 两条命令退出码 0、2 项测试通过;临时私钥只在进程里,不输出签名材料。这个实验验证规定字节下的原语与编码,不包含 Bitcoin Core regtest 收款、UTXO 花费、描述符导入或 Ethereum 节点交易。官方 Bitcoin Core v29.1 大包下载在当前环境未成功,不能用上游源码 commit 填充“节点实验通过”;节点级条目保持 NOT_RUN,在 27 中继续尝试与单独记录。
两道带答案的练习
画图题: 给出一份 secp256k1 压缩公钥,不给私钥。画出派生 P2PKH、P2WPKH v0、Ethereum EOA 小写地址的分支,标注各自哈希输入;哪些分支可以在只有公钥的钱包里完成?哪项操作不能完成?
可核对答案: Bitcoin 两分支各取压缩公钥的 HASH160,但输出脚本/地址编码不同;Ethereum 将对应点的 64 字节未压缩 XY 输入 Keccak-256、取后 20 字节。只有公钥可计算这些接收标识及观察范围;没有相应签名密钥,不能签发满足花费/账户条件的真实交易。合约账户地址不适用上图 EOA 派生。
实验变更题: 将 26_wallet_objects.py 算 Ethereum 地址的 keccak.new(digest_bits=256, data=uncompressed[1:]) 错换为 hashlib.sha3_256(uncompressed[1:]),再运行 26 测试。哪条断言应失败?为什么另一个 bcrt1q... 地址不因此变化?
可核对答案: 原脚本的 ethereum_hash[-20:] != wrong_sha3_address 变为相等,ethereum_eoa_address_from_uncompressed_xy_keccak 失败;生成元公开向量的 Ethereum 地址比较也应失败,若同时修改该测试的计算分支。Bitcoin 路径用 SHA256→RIPEMD160 与 Bech32,不用 Keccak/SHA3;错误替换 Ethereum 哈希并不会改变 Bitcoin 地址。
资料与导航
- BIP 141、BIP 173:v0 见证程序与 Bech32 编码;Taproot v1 的不同编码留给 32。
- Ethereum Yellow Paper、EIP-55:EOA 地址运算与可选显示校验格式。
- 12 数字签名验证了什么;已有区块链 08 钱包描述符与 PSBT专门讲观察钱包与交易准备,本文不重复。






