第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 编码过程如下:

  1. 添加版本前缀:主网使用 0x80,测试网使用 0xEF

  2. (可选)添加压缩标志:如果对应的公钥将被压缩,则在有效载荷后追加 0x01,此步骤会改变 WIF 的最终 Base58 前缀

  3. 计算校验和:应用 SHA256(SHA256(data)) 并取前 4 字节

  4. 应用 Base58 编码:转成易读的字符串

WIF 编码流程

图 1-1:WIF 编码将 32 字节私钥转换为 Base58Check 编码字符串

生成的 WIF 字符串具有独特的前缀:

  • LK:主网私钥

  • c:测试网私钥

公钥:密码学验证点#

公钥是 secp256k1 椭圆曲线上的一个点,由私钥通过椭圆曲线乘法算出。数学原理比较复杂,但代码写起来很简单。

ECDSA 与 secp256k1#

Bitcoin 使用 secp256k1 曲线实现椭圆曲线数字签名算法(ECDSA)。secp256k1 曲线由以下方程定义:

y² = x³ + 7

secp256k1 椭圆曲线

图 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 地址不是公钥,而是公钥哈希后再编码的结果。多这一层有几个好处:

  • 隐私:花费之前不暴露公钥

  • 抗量子:哈希提供额外一层保护

  • 防错:编码自带校验和

地址生成过程#

生成地址的流程大同小异:

  1. 哈希公钥:先 SHA256,再 RIPEMD160(合称 Hash160)

  2. 加版本字节:标明地址类型

  3. 加校验和:用于检测输入错误

  4. 编码:转成 Base58Check 或 Bech32 字符串

传统 Bitcoin 地址流程

图 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

1...

Legacy payments

P2SH

Base58Check

25 bytes

~34 chars

3...

Multi-sig, wrapped SegWit

P2WPKH

Bech32

21 bytes

42-46 chars

bc1q...

SegWit payments

P2TR

Bech32m

33 bytes

58-62 chars

bc1p...

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 高级功能的基础。