第1章:私钥、公钥与地址编码#
参考:
book/translations/zh-Hans/Chapter 1.md
代码示例:code/chapter01/
最后更新: 2025-12-05
要学 Taproot,先得把 Bitcoin 的密码学基础搞清楚。这一章讲私钥(private key)、公钥(public key)和地址生成——它们是所有交易的底层构件。
密码学层次结构#
Bitcoin 的安全性建立在一条单向链上:
Private Key (256-bit) → Public Key (ECDSA point) → Address (encoded hash)
每一层都有明确分工:
私钥:证明资产归属,用于签名
公钥:验证签名,授权支付
地址:方便收款,同时隐藏公钥
私钥:所有权的基础#
Bitcoin 私钥就是一个 256 位的随机整数,用它来证明资产归属。2^256 有多大?比可观测宇宙里所有原子加起来还多。
生成私钥#
使用 Python 的 bitcoin-utils 示例:
# 示例 1: 生成私钥
# 参考: code/chapter01/01_generate_private_key.py
from bitcoinutils.setup import setup
from bitcoinutils.keys import PrivateKey
# 设置主网(或使用 'testnet' 用于测试网络)
setup('mainnet')
# 生成一个新的 Bitcoin 私钥
priv = PrivateKey()
# 提取不同格式的私钥
private_key_hex = priv.to_bytes().hex() # 32 字节(256 位)十六进制格式
private_key_wif = priv.to_wif() # 钱包导入格式(WIF)
print(f"Private Key (HEX): {private_key_hex}")
print(f"Private Key (WIF): {private_key_wif}")
Private Key (HEX): 0eaa5921133bc8c62fa0272de9e4af44d2e3e1200174cd0a481a2957d8724a07
Private Key (WIF): KwiDdSeZPrnycwvCPPsqxJsEXLfm1HGNfwEQKmxucYbqGBY8VBKz
示例输出:
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: 生成公钥
# 参考: code/chapter01/02_generate_public_key.py
from bitcoinutils.setup import setup
from bitcoinutils.keys import PrivateKey
# 设置主网(或使用 'testnet' 用于测试网络)
setup('mainnet')
# 生成一个新的 Bitcoin 私钥
priv = PrivateKey()
# 获取公钥(默认压缩)
pub = priv.get_public_key()
# 生成两种格式的公钥
public_key_compressed = pub.to_hex(compressed=True) # 33 字节
public_key_uncompressed = pub.to_hex(compressed=False) # 65 字节
print(f"Compressed: {public_key_compressed}")
print(f"Uncompressed: {public_key_uncompressed[:70]}...")
# 截断显示
Compressed: 02b6731895f114c2ed61f19e72de5b236b9a958082192e7589b8d7c8c33d3e9d3c
Uncompressed: 04b6731895f114c2ed61f19e72de5b236b9a958082192e7589b8d7c8c33d3e9d3c9b44...
示例输出:
Compressed: 0250be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3
Uncompressed: 0450be5fc44ec580c387bf45df275aaa8b27e2d7716af31f10eeed357d126bb4d3...
现在的 Bitcoin 软件都只用压缩公钥,因为更省空间,安全性一样。
X-Only 公钥:Taproot 的创新#
Taproot 更进一步,引入了 x-only 公钥——连 y 的奇偶性都不存了,只保留 x 坐标(32 字节)。好处是:
交易更小
验签更快
方便做密钥聚合
# 示例 3: Taproot X-Only 公钥
# 参考: code/chapter01/03_taproot_xonly_pubkey.py
from bitcoinutils.setup import setup
from bitcoinutils.keys import PrivateKey
# 设置主网(或使用 'testnet' 用于测试网络)
setup('mainnet')
# 生成一个新的 Bitcoin 私钥
priv = PrivateKey()
# 获取公钥
pub = priv.get_public_key()
# Taproot 使用 x-only 公钥(32 字节)
# 仅获取 x 坐标
taproot_pubkey = pub.to_x_only_hex() # 32 字节,仅 x 坐标
print(f"X-only Public Key: {taproot_pubkey}")
X-only Public Key: 35d3d9375daee64ee1d1bb578e7f16b1c21545c4fd14d0cebdcb41106fe51943
这是 Taproot 效率提升的关键,后面章节会详细讲。
地址生成:从公钥到支付目的地#
Bitcoin 地址不是公钥,而是公钥哈希后再编码的结果。多这一层有几个好处:
隐私:花费之前不暴露公钥
抗量子:哈希提供额外一层保护
防错:编码自带校验和
地址生成过程#
生成地址的流程大同小异:
哈希公钥:先 SHA256,再 RIPEMD160(合称 Hash160)
加版本字节:标明地址类型
加校验和:用于检测输入错误
编码:转成 Base58Check 或 Bech32 字符串

图 1-3:传统 Bitcoin 地址生成流程
# 示例 4: 生成不同类型的地址
# 参考: code/chapter01/04_generate_addresses.py
from bitcoinutils.setup import setup
from bitcoinutils.keys import PrivateKey
from bitcoinutils.script import Script
from bitcoinutils.keys import P2shAddress, P2wpkhAddress
# 设置主网(或使用 'testnet' 用于测试网络)
setup('mainnet')
# 生成一个新的 Bitcoin 私钥
priv = PrivateKey()
# 获取公钥
pub = priv.get_public_key()
# 从同一个密钥生成不同类型的地址
legacy_address = pub.get_address() # P2PKH
segwit_native = pub.get_segwit_address() # P2WPKH
taproot_address = pub.get_taproot_address() # P2TR
# 对于 P2SH-P2WPKH,我们需要将 P2WPKH 脚本包装在 P2SH 中
segwit_script = segwit_native.to_script_pub_key()
segwit_p2sh = P2shAddress.from_script(segwit_script) # P2SH-P2WPKH
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: {taproot_address.to_string()}")
Legacy (P2PKH): 18DGDodbW9rBzYw9xqi1iusQhsHDdVP4UN
SegWit Native: bc1qfudf8c49wx7pffkfhm672rf2cwud4rg8qyf7sx
SegWit P2SH: 3DobzZRsc211YBwvMBFvZm9Vumjxz5zJWh
Taproot: bc1pptczrjlwgwx8fx2r8hctzr25s5juj79l0m6r0cu80g92nahen80qp3d7dm
示例输出:
Legacy (P2PKH): 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
SegWit Native: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080
SegWit P2SH: 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy
Taproot: bc1plz0h3rlj2zvn88pgywqtr9k3df3p75p3ltuxh0
地址类型与编码格式#
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 编码
优势:增强隐私,脚本灵活性
示例:
bc1plz0h3rlj2zvn88pgywqtr9k3df3p75p3ltuxh0
地址格式对比#
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
from bitcoinutils.setup import setup
from bitcoinutils.keys import PrivateKey
from bitcoinutils.script import Script
from bitcoinutils.keys import P2shAddress, P2wpkhAddress
import base58
def verify_address(address_obj, address_str, address_type):
"""验证地址格式并提取信息"""
print(f"\n{address_type}:")
print(f" Address: {address_str}")
print(f" Length: {len(address_str)} characters")
# 获取 scriptPubKey 以查看底层数据
script_pubkey = address_obj.to_script_pub_key()
script_hex = script_pubkey.to_hex()
script_bytes = bytes.fromhex(script_hex)
if address_str[0] == '1' or address_str[0] == '3':
# Base58Check 编码(P2PKH 或 P2SH)
try:
decoded = base58.b58decode(address_str)
# Base58Check: 版本字节 (1) + hash (20 字节) + 校验和 (4 字节) = 25 字节
print(f" Format: Base58Check")
print(f" Decoded bytes: {len(decoded)} bytes")
print(f" Version byte: 0x{decoded[0]:02x}")
print(f" Hash160: {decoded[1:21].hex()} ({len(decoded[1:21])} bytes)")
print(f" Checksum: {decoded[21:].hex()} ({len(decoded[21:])} bytes)")
print(f" ScriptPubKey: {script_hex} ({len(script_bytes)} bytes)")
except Exception as e:
print(f" Error decoding: {e}")
elif address_str.startswith('bc1q'):
# Bech32 编码(P2WPKH)
print(f" Format: Bech32 (SegWit v0)")
print(f" ScriptPubKey: {script_hex} ({len(script_bytes)} bytes)")
# P2WPKH script: OP_0 (0x00) + pushdata (0x14 = 20) + hash160 (20 bytes) = 22 bytes
if len(script_bytes) == 22 and script_bytes[0] == 0x00 and script_bytes[1] == 0x14:
print(f" ✓ Correct format: OP_0 + pushdata(20) + 20-byte hash160")
print(f" Version: 0x00 (P2WPKH)")
print(f" Hash160: {script_bytes[2:].hex()} ({len(script_bytes[2:])} bytes)")
else:
print(f" ⚠ Unexpected script format")
elif address_str.startswith('bc1p'):
# Bech32m 编码(P2TR)
print(f" Format: Bech32m (SegWit v1 / Taproot)")
print(f" ScriptPubKey: {script_hex} ({len(script_bytes)} bytes)")
# P2TR script: OP_1 (0x51) + pushdata (0x20 = 32) + x-only pubkey (32 bytes) = 34 bytes
if len(script_bytes) == 34 and script_bytes[0] == 0x51 and script_bytes[1] == 0x20:
print(f" ✓ Correct format: OP_1 + pushdata(32) + 32-byte x-only pubkey")
print(f" Version: 0x01 (P2TR)")
print(f" X-only pubkey: {script_bytes[2:].hex()} ({len(script_bytes[2:])} bytes)")
print(f" Note: Taproot addresses are longer because:")
print(f" - They use 32-byte x-only pubkeys (vs 20-byte hashes)")
print(f" - Bech32m encoding overhead")
print(f" - But provide better privacy and script flexibility")
else:
print(f" ⚠ Unexpected script format")
# 设置主网
setup('mainnet')
# 生成一个新的 Bitcoin 私钥
priv = PrivateKey()
pub = priv.get_public_key()
# 生成不同类型的地址
legacy_address = pub.get_address()
segwit_native = pub.get_segwit_address()
taproot_address = pub.get_taproot_address()
# 对于 P2SH-P2WPKH
segwit_script = segwit_native.to_script_pub_key()
segwit_p2sh = P2shAddress.from_script(segwit_script)
print("=" * 70)
print("Bitcoin Address Format Verification")
print("=" * 70)
verify_address(legacy_address, legacy_address.to_string(), "Legacy (P2PKH)")
verify_address(segwit_native, segwit_native.to_string(), "SegWit Native (P2WPKH)")
verify_address(segwit_p2sh, segwit_p2sh.to_string(), "SegWit P2SH (P2SH-P2WPKH)")
verify_address(taproot_address, taproot_address.to_string(), "Taproot (P2TR)")
print("\n" + "=" * 70)
print("Summary:")
print("=" * 70)
print("P2PKH: ~34 chars (Base58Check, 20-byte hash160)")
print("P2WPKH: ~42-46 chars (Bech32, 20-byte hash160)")
print("P2SH-P2WPKH: ~34 chars (Base58Check, 20-byte script hash)")
print("P2TR: ~58-62 chars (Bech32m, 32-byte x-only pubkey)")
print("\nTaproot addresses are longer because:")
print(" - They use 32-byte x-only public keys (not 20-byte hashes)")
print(" - Bech32m encoding is slightly less efficient than Base58Check")
print(" - But they provide better privacy and script flexibility")
======================================================================
Bitcoin Address Format Verification
======================================================================
Legacy (P2PKH):
Address: 1Pb78PJtFVXfo32WVKmoM3DcLFRpKfuBFF
Length: 34 characters
Format: Base58Check
Decoded bytes: 25 bytes
Version byte: 0x00
Hash160: f7c6065797accfe598a6cafffc09735632db6754 (20 bytes)
Checksum: 62191622 (4 bytes)
ScriptPubKey: 76a914f7c6065797accfe598a6cafffc09735632db675488ac (25 bytes)
SegWit Native (P2WPKH):
Address: bc1q7lrqv4uh4n87tx9xetllcztn2cedke65sm7c57
Length: 42 characters
Format: Bech32 (SegWit v0)
ScriptPubKey: 0014f7c6065797accfe598a6cafffc09735632db6754 (22 bytes)
✓ Correct format: OP_0 + pushdata(20) + 20-byte hash160
Version: 0x00 (P2WPKH)
Hash160: f7c6065797accfe598a6cafffc09735632db6754 (20 bytes)
SegWit P2SH (P2SH-P2WPKH):
Address: 3B7xBqedXf4eF2PAn7nFv3qtuG76bHUyfd
Length: 34 characters
Format: Base58Check
Decoded bytes: 25 bytes
Version byte: 0x05
Hash160: 677069869dccef5ea51ea60e3fb352f05f588efb (20 bytes)
Checksum: ad6f9c58 (4 bytes)
ScriptPubKey: a914677069869dccef5ea51ea60e3fb352f05f588efb87 (23 bytes)
Taproot (P2TR):
Address: bc1p9ferd72lw0hnrvzd6javvte8yh0nhy05q4gum50yz04ktxkm2dxsz04nm9
Length: 62 characters
Format: Bech32m (SegWit v1 / Taproot)
ScriptPubKey: 51202a7236f95f73ef31b04dd4bac62f2725df3b91f40551cdd1e413eb659adb534d (34 bytes)
✓ Correct format: OP_1 + pushdata(32) + 32-byte x-only pubkey
Version: 0x01 (P2TR)
X-only pubkey: 2a7236f95f73ef31b04dd4bac62f2725df3b91f40551cdd1e413eb659adb534d (32 bytes)
Note: Taproot addresses are longer because:
- They use 32-byte x-only pubkeys (vs 20-byte hashes)
- Bech32m encoding overhead
- But provide better privacy and script flexibility
======================================================================
Summary:
======================================================================
P2PKH: ~34 chars (Base58Check, 20-byte hash160)
P2WPKH: ~42-46 chars (Bech32, 20-byte hash160)
P2SH-P2WPKH: ~34 chars (Base58Check, 20-byte script hash)
P2TR: ~58-62 chars (Bech32m, 32-byte x-only pubkey)
Taproot addresses are longer because:
- They use 32-byte x-only public keys (not 20-byte hashes)
- Bech32m encoding is slightly less efficient than Base58Check
- But they provide better privacy and script flexibility
地址编码的细节很多——版本字节、校验和、不同编码方案——但核心思路就一条:
👉 地址是给人看的。本质上只是锁定脚本(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 高级功能的基础。