第1章:私钥、公钥与地址编码#
参考:
examples/ch01_keys_and_addresses.py,code/chapter01/
最后更新: 2025-12-05
要学 Taproot,先得把 Bitcoin 的密码学基础搞清楚。这一章讲私钥(private key)、公钥(public key)和地址生成——它们是所有交易的底层构件。
# 本章环境(一次性加载,后续代码块复用)
from btcaaron import Key, TapTree
from bitcoinutils.setup import setup
from bitcoinutils.keys import PrivateKey, P2shAddress, P2wpkhAddress
from bitcoinutils.script import Script
密码学层次结构#
Bitcoin 的安全性建立在一条单向链上:
Private Key (256-bit) → Public Key (ECDSA point) → Address (encoded hash)
每一层都有明确分工:
私钥:证明资产归属,用于签名
公钥:验证签名,授权支付
地址:方便收款,同时隐藏公钥
私钥:所有权的基础#
Bitcoin 私钥就是一个 256 位的随机整数,用它来证明资产归属。2^256 有多大?比可观测宇宙里所有原子加起来还多。
生成私钥#
使用 btcaaron 简洁 API(本书示例库):
# 示例 1: 生成私钥(btcaaron)
# 参考: examples/ch01_keys_and_addresses.py
alice = Key.from_wif("cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT")
print(f"Private Key (WIF): {alice.wif}")
Private Key (WIF): cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT
示例输出:
Private Key (HEX): e9873d79c6d87dc0fb6a5778633389dfa5c32fa27f99b5199abf2f9848ee0289
Private Key (WIF): L1aW4aubDFB7yfras2S1mN3bqg9w3KmCPSM3Qh4rQG9E1e84n5Bd
十六进制表示包含 64 个字符(每个字符代表 4 位),总共 256 位或 32 字节。这种格式在数学上精确,但不适合存储或导入/导出操作。
钱包导入格式(WIF)#
钱包导入格式(Wallet Import Format,WIF)用 Base58Check 编码让私钥更好用:
加入校验和,能检测输入错误
去掉容易混淆的字符(0、O、I、l)
统一了钱包之间导入导出的格式
WIF 编码过程如下:
添加版本前缀:主网使用
0x80,测试网使用0xEF(可选)添加压缩标志:如果对应的公钥将被压缩,则在有效载荷后追加 0x01,此步骤会改变 WIF 的最终 Base58 前缀
计算校验和:应用 SHA256(SHA256(data)) 并取前 4 字节
应用 Base58 编码:转成易读的字符串

图 1-1:WIF 编码将 32 字节私钥转换为 Base58Check 编码字符串
生成的 WIF 字符串具有独特的前缀:
L 或 K:主网私钥
c:测试网私钥
公钥:密码学验证点#
公钥是 secp256k1 椭圆曲线上的一个点,由私钥通过椭圆曲线乘法算出。数学原理比较复杂,但代码写起来很简单。
ECDSA 与 secp256k1#
Bitcoin 使用 secp256k1 曲线实现椭圆曲线数字签名算法(ECDSA)。secp256k1 曲线由以下方程定义:
y² = x³ + 7

图 1-2:Bitcoin 使用的 secp256k1 椭圆曲线
不必深究数学,只需理解:
每个私钥
k在曲线上生成一个唯一点(x, y)这种关系在计算上是不可逆的
曲线的数学性质保证了安全性
压缩与未压缩公钥#
公钥可以用两种格式表示:
未压缩格式(65 字节):
04 + x-coordinate (32 bytes) + y-coordinate (32 bytes)
压缩格式(33 字节):
02/03 + x-coordinate (32 bytes)
为什么能压缩?椭圆曲线的性质决定了:只要有 x 坐标和 y 的奇偶性,就能算出完整的 y:
02前缀:y 坐标为偶数03前缀:y 坐标为奇数
# 示例 2: 生成公钥 + X-only(btcaaron)
# 参考: examples/ch01_keys_and_addresses.py
# 压缩公钥(33 字节)与 x-only(32 字节)
print(f"Compressed (33 bytes): {alice.pubkey}")
print(f"X-only (32 bytes): {alice.xonly}")
print(f"验证: x-only = 压缩公钥去掉 02/03 前缀 → {alice.pubkey[2:]} == {alice.xonly}")
Compressed (33 bytes): 02898711e6bf63f5cbe1b38c05e89d6c391c59e9f8f695da44bf3d20ca674c8519
X-only (32 bytes): 898711e6bf63f5cbe1b38c05e89d6c391c59e9f8f695da44bf3d20ca674c8519
验证: x-only = 压缩公钥去掉 02/03 前缀 → 898711e6bf63f5cbe1b38c05e89d6c391c59e9f8f695da44bf3d20ca674c8519 == 898711e6bf63f5cbe1b38c05e89d6c391c59e9f8f695da44bf3d20ca674c8519
示例输出:
Compressed: 0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3
Uncompressed: 0450be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3...
现在的 Bitcoin 软件都只用压缩公钥,因为更省空间,安全性一样。
X-Only 公钥:Taproot 的创新#
Taproot 更进一步,引入了 x-only 公钥——连 y 的奇偶性都不存了,只保留 x 坐标(32 字节)。好处是:
交易更小
验签更快
方便做密钥聚合
# 示例 3: Taproot X-Only 公钥(复用示例 1/2 的 alice)
print(f"Taproot X-only 公钥 (32 bytes): {alice.xonly}")
Taproot X-only 公钥 (32 bytes): 898711e6bf63f5cbe1b38c05e89d6c391c59e9f8f695da44bf3d20ca674c8519
这是 Taproot 效率提升的关键,后面章节会详细讲。
地址生成:从公钥到支付目的地#
Bitcoin 地址不是公钥,而是公钥哈希后再编码的结果。多这一层有几个好处:
隐私:花费之前不暴露公钥
抗量子:哈希提供额外一层保护
防错:编码自带校验和
地址生成过程#
生成地址的流程大同小异:
哈希公钥:先 SHA256,再 RIPEMD160(合称 Hash160)
加版本字节:标明地址类型
加校验和:用于检测输入错误
编码:转成 Base58Check 或 Bech32 字符串

图 1-3:传统 Bitcoin 地址生成流程
# 示例 4: 生成不同类型的地址
# Legacy/SegWit/P2SH: bitcoinutils | Taproot: btcaaron
setup('mainnet')
priv = PrivateKey()
pub = priv.get_public_key()
# Legacy / SegWit / P2SH-P2WPKH(bitcoin-utils)
legacy_address = pub.get_address()
segwit_native = pub.get_segwit_address()
segwit_p2sh = P2shAddress.from_script(segwit_native.to_script_pub_key())
# Taproot:btcaaron 一行搞定
program = TapTree(internal_key=Key.from_wif(priv.to_wif())).build()
print(f"Legacy (P2PKH): {legacy_address.to_string()}")
print(f"SegWit Native: {segwit_native.to_string()}")
print(f"SegWit P2SH: {segwit_p2sh.to_string()}")
print(f"Taproot (P2TR): {program.address}")
Legacy (P2PKH): 1C45GpJKoBMpJfHdfVu1uL2zR7CSkjDFCQ
SegWit Native: bc1q0ylr9w4nu0gth8uv7wd6l5mn22xcyda2l7pntm
SegWit P2SH: 3Ps5zqeRr6jojKxdESWP3FxA5sotUNd8fD
Taproot (P2TR): bc1pf2jessjqdpzmjm2nkhxqksurq3uf7mst6pn53x0ndwnlwrg5jlcsyllyjw
示例输出:
Legacy (P2PKH): 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
SegWit Native: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080
SegWit P2SH: 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy
Taproot (P2TR): bc1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h (~62 chars)
地址类型与编码格式#
Base58Check 编码#
Base58Check 用于传统地址,去掉了容易看错的字符,还自带校验:
排除的字符: 0(零)、O(大写 o)、I(大写 i)、l(小写 L)
P2PKH(Pay-to-Public-Key-Hash):
前缀:
1格式:Base58Check 编码
用途:原始 Bitcoin 地址格式
示例:
1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
P2SH(Pay-to-Script-Hash):
前缀:
3格式:Base58Check 编码
用途:多重签名和包装的 SegWit 地址
示例:
3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy
Bech32 编码:SegWit 的创新#
Bech32 是 SegWit 引入的新编码,错误检测能力更强:
P2WPKH(Pay-to-Witness-Public-Key-Hash):
前缀:
bc1q格式:Bech32 编码
优势:更低费用,更好的错误检测
示例:
bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080
Bech32m 编码:Taproot 的增强#
Taproot 用 Bech32m,是 Bech32 的改进版:
P2TR(Pay-to-Taproot):
前缀:
bc1p格式:Bech32m 编码
优势:增强隐私,脚本灵活性
示例:
bc1p53ncq9ytax924ps66z6al3wfhy6a29w8h6xfu27xem06t98zkmvsakd43h(约 62 字符)
地址格式对比#
Address Type |
Encoding |
Data Size |
Address Length |
Prefix |
Primary Use Case |
|---|---|---|---|---|---|
P2PKH |
Base58Check |
25 bytes |
~34 chars |
|
Legacy payments |
P2SH |
Base58Check |
25 bytes |
~34 chars |
|
Multi-sig, wrapped SegWit |
P2WPKH |
Bech32 |
21 bytes |
42-46 chars |
|
SegWit payments |
P2TR |
Bech32m |
33 bytes |
58-62 chars |
|
Taproot payments |
# 示例 5: 验证地址格式(简要)
# 参考: code/chapter01/05_verify_addresses.py
setup('mainnet')
priv = PrivateKey()
pub = priv.get_public_key()
legacy = pub.get_address()
segwit = pub.get_segwit_address()
taproot = pub.get_taproot_address()
print(f"P2PKH: {len(legacy.to_string())} chars | P2WPKH: {len(segwit.to_string())} chars | P2TR: {len(taproot.to_string())} chars")
print(f"P2TR ScriptPubKey: OP_1 + 32B x-only = {len(taproot.to_script_pub_key().to_hex())//2} bytes")
P2PKH: 34 chars | P2WPKH: 42 chars | P2TR: 62 chars
P2TR ScriptPubKey: OP_1 + 32B x-only = 34 bytes
地址编码的细节很多——版本字节、校验和、不同编码方案——但核心思路就一条:
👉 地址是给人看的。本质上只是锁定脚本(scriptPubKey)的友好表示,协议层面并不需要它。
看到前缀(1、3、bc1q、bc1p)就知道背后是什么脚本。节点眼里没有”地址”这个概念,只有脚本。
后面几章我们会聚焦真正重要的东西:各类地址对应的 scriptPubKey。逻辑都在那里——Bitcoin Script 和可编程性的起点也在那里。能推断地址背后的脚本,就能理解它怎么被花费。
派生模型#
理解从私钥到地址的完整派生链路很重要。下图展示了整个流程——从私钥一路到链上脚本。普通用户只看到地址,但开发者要能追踪每一步,才能理解 Bitcoin 怎么验证所有权、执行花费条件。

图 1-4:从私钥到地址的完整派生模型
Private Key (k)
↓ ECDSA multiplication
Public Key (x, y)
↓ SHA256 + RIPEMD160
Public Key Hash (20 bytes)
↓ Version + Checksum + Encoding
Address (Base58/Bech32)
↓ Decoded by wallet/node
ScriptPubKey (locking script on-chain)
安全性保证:
正向:每一步都能快速算出来
逆向:每一步都无法反推
抗碰撞:不同公钥撞出同一地址的概率可以忽略
对 Taproot 的实际意义#
后面会看到,Taproot 就是建立在这些基础上的:
X-only 公钥:省空间,还能做密钥聚合
Bech32m 编码:错误检测更强
统一的地址外观:多签和单签看起来一样,提升隐私
搞懂这些编码和密钥格式,就为理解 Taproot 的高级特性打好了基础——多个花费条件可以藏在同一个地址里。
章节总结#
本章梳理了 Bitcoin 交易的密码学基础:
✅ 私钥是 256 位随机数,用 WIF 编码方便导入导出
✅ 公钥是椭圆曲线上的点,现在都用压缩格式
✅ 地址是公钥哈希后编码的结果,不是公钥本身
✅ 不同地址类型用不同编码:Base58Check、Bech32、Bech32m
✅ Taproot 引入 x-only 公钥和 Bech32m,进一步提升效率
这些组件——密钥、哈希、编码——都是 Bitcoin Script 要处理和验证的对象。下一章我们看它们怎么跟 Bitcoin Script 配合——这门脚本语言定义了花费条件,也是 Taproot 高级功能的基础。