比特币密钥体系(私钥 / 公钥 / 椭圆曲线 / 助记词)—— 从熵到地址的完整流程

136 阅读7分钟

摘要

本文从原理与实现两个角度,讲清比特币密钥体系的每一步:secp256k1 的直观与本质、私钥到公钥的标量乘法、助记词(BIP39)到种子的 PBKDF2 拉伸、以及 BIP32 的分层派生与地址生成流程(从熵到地址)。

目录

  1. 为什么要讲这些
  2. 简要回顾:公钥密码学与哈希函数在比特币中的角色
  3. 椭圆曲线(ECC)与 secp256k1:直观与本质
  4. 私钥 → 公钥:到底发生了什么
  5. 从公钥到地址(SHA256、RIPEMD160、Base58Check)
  6. 助记词(BIP39):如何把随机数变成可读的种子
  7. HD 钱包与派生(BIP32/BIP44):助记词到无数私钥的桥梁
  8. 三者之间的关系总结(表格) + 常见问答
  9. 实用示例:一段从熵到地址的伪代码
  10. 小结与安全建议

1. 为什么要讲这些

理解私钥、公钥、助记词及它们的生成关系,不只是学术兴趣:

  • 决定如何安全备份与恢复钱包;
  • 帮助审计 / 实现轻量钱包(或定位密钥相关问题);
  • 理解签名、交易验证与密钥泄露的传播路径。

2. 简要回顾:公钥密码学与哈希在比特币中的角色

  • 私钥(Private Key):控制资金的根秘密,通常是一个 256-bit 的大整数。
  • 公钥(Public Key):由私钥单向计算得到,用于验证签名。
  • 地址(Address):公钥经过哈希和编码后的短字符串,便于展示与输入。
  • 哈希函数(SHA-256、RIPEMD-160):压缩公钥并提供不可逆性与校验。

这些构成保证了“能花的是能签名的人”,同时地址更便于人类交流。

3. 椭圆曲线(ECC)与 secp256k1:直观与本质

直观说明

比特币使用的曲线 secp256k1 的方程形式为:

[ y^2 = x^3 + 7 ]

但要记住:实际运算是在一个有限域 (\mathbb{F}_p) 上(模一个大素数),因此点集合是离散的。

核心运算是 标量乘法:对基点 (G) 做 (k) 次加法(记作 (Q = k\cdot G))得到公钥点。这个过程是单向的——知道 (Q) 很难反推出 (k)(椭圆曲线离散对数问题 ECDLP)。

secp256k1 的关键要点(概览)

  • 曲线方程:y^2 = x^3 + 7
  • 域为:大素数 p = 2^256 - 2^32 - 977(具体数值由标准给出)。
  • 基点 G:曲线上一个固定的生成点,私钥 k 与公钥 Q 的关系是 Q = k * G

注:曲线图在实数域上是连续曲线,但在有限域上是离散点集,直观图主要帮助理解运算规则。

4. 私钥 → 公钥:到底发生了什么

私钥是什么

私钥通常是一个范围在 ([1, n-1]) 内的整数(n 是群的阶,接近 2^256),在钱包里通常以 32 字节二进制保存。

公钥如何计算

公钥是私钥对基点的标量乘法结果:

[ Q = k \cdot G ]

其中 Q 是曲线上的点,包含 xy 两个坐标。该运算是单向的(基于 ECDLP 的假设),因此私钥泄露会导致资金被控制,但仅知道公钥并不能逆算私钥。

压缩公钥与未压缩公钥

  • 未压缩公钥格式:0x04 || x || y(65 字节)。
  • 压缩公钥格式:0x02/0x03 || x(33 字节),用 x 和 y 的奇偶性表示 y 的符号,节省空间。

5. 从公钥到地址(步骤拆解)

以传统 P2PKH(以 1 开头的地址)为例,生成流程:

  1. 得到公钥字节(压缩或未压缩)。
  2. sha256(pubkey)
  3. ripemd160(sha256(pubkey)) —— 得到 20 字节的 PubKeyHash
  4. 在前面加版本字节(主网 P2PKH 为 0x00),对拼接的数据做双 SHA256 并取前 4 字节作为 checksum。
  5. version + PubKeyHash + checksum 做 Base58Check 编码,得到可读地址(以 1 开头)。

6. 助记词(BIP39):如何把随机数变成可读的种子

三步走

  1. 生成熵(Entropy):通常为 128 / 160 / 192 / 224 / 256 位(对应 12/15/18/21/24 词)。
  2. 熵 → 助记词:把熵补上校验位(checksum),分成每组 11 位,映射到 BIP39 词表中的词,得到助记词序列。
  3. 助记词 → 种子(Seed):用 PBKDF2(HMAC-SHA512)salt = "mnemonic" + passphrase,迭代 2048 次,输出 512-bit(64 字节)的种子。

Seed 是 BIP32 派生主私钥与链码的输入。

注:助记词还可以带一个可选的 passphrase(有些钱包称为额外密码或第 25 个词),这个 passphrase 会被加入 PBKDF2 的 salt 中,大幅改变派生出的种子。

7. HD 钱包与派生(BIP32/BIP44):助记词到无数私钥的桥梁

核心思想

BIP32 定义了层级确定性(HD)密钥:从一个主 seed 生成 master private key + chain code,之后通过 CKD(Child Key Derivation)函数不断派生出子密钥,形成树结构。

  • 非 hardened 派生:可以用父公钥加 index 推导子公钥(便于观察/热钱包)。
  • Hardened 派生(index >= 2^31):只能用父私钥派生,防止某些信息泄露导致安全问题。

派生函数内部使用 HMAC-SHA512(chain_code, data),输出 512-bit,左右各 256-bit 分别作为子私钥的调整量与新链码。

8. 三者之间的关系

简化流程:

助记词(Mnemonic) --PBKDF2--> Seed (64 bytes) --BIP32--> Master (privkey + chaincode) --派生--> 私钥 k --ECDSA/点乘--> 公钥 Q = k*G --hash--> 地址
元素作用是否公开
助记词(BIP39)恢复钱包的可读种子不得公开(敏感)
种子(Seed)派生主私钥的原料不得公开
私钥 k控制资金最敏感
公钥 Q验签 / 生成地址可公开
地址收款标识公开

9. 常见问答(FAQ,直奔重点)

Q:已知地址能否算回公钥或私钥?

A:地址是公钥的哈希(RIPEMD160(SHA256(pubkey))),哈希不可逆,因此不能从地址直接恢复公钥或私钥。只有当你把某次交易花掉并在交易中公布了公钥时,区块链上才有该地址对应的公钥。

Q:助记词可以被穷举吗?

A:理论上可以,但 12/24 词助记词所对应的熵级和 PBKDF2 的迭代(2048 次)使得穷举在没有其它信息的情况下不可行(现实上不可行)。

Q:压缩公钥和未压缩公钥有什么差别?

A:压缩公钥只保留 x 坐标和 y 的奇偶位(0x02/0x03 || x),节省空间,但对地址生成的哈希输入不同(因此地址会不同)。

10. 实用示例:从熵到地址

说明:下面是伪代码,便于理解流程。生产环境请使用成熟库(libsecp256k1、bip39、bip32 等)并在离线/受控环境下测试。

# 伪代码(逻辑清晰,但非生产级别)
import os, hashlib, hmac

# 1. 生成 128-bit 熵 -> 12 词
entropy = os.urandom(16)  # 128 bits

# 2. entropy -> mnemonic (按 BIP39 字典映射,这里用 helper 函数)
mnemonic = entropy_to_mnemonic(entropy)  # 返回空格分隔的 12 个单词

# 3. mnemonic -> seed (PBKDF2-HMAC-SHA512, iterations=2048)
salt = ("mnemonic" + (passphrase_or_empty)).encode('utf-8')
seed = pbkdf2_hmac('sha512', mnemonic.encode('utf-8'), salt, 2048, dklen=64)

# 4. seed -> master private key + chain code
I = hmac.new(b"Bitcoin seed", seed, hashlib.sha512).digest()
master_priv = I[:32]
master_chaincode = I[32:]

# 5. 派生第 0 个私钥(简化示意)
child_priv, child_chaincode = CKD_priv(master_priv, master_chaincode, index=0)

# 6. 私钥 -> 公钥(椭圆曲线标量乘)
pubkey = scalar_multiply_G(child_priv)

# 7. 公钥 -> 地址(P2PKH)
sha = hashlib.sha256(pubkey).digest()
rip = ripemd160(sha)
payload = b'\x00' + rip
checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
address = base58_encode(payload + checksum)

print(mnemonic)
print(address)

11. 小结与安全建议

  • 助记词 = 钱包:任何人拿到助记词或主私钥就能恢复并花掉资产。务必离线备份,避免拍照上传云端。
  • Passphrase(可选):可以显著提升安全性,但千万不要忘记。
  • 使用成熟库与硬件钱包:不要在生产环境自己实现加密原语;仅在受控环境中实现学习代码。
  • 最小化公钥/地址的暴露:地址通常可公开,但尽量避免在不必要的场合泄露公钥。