Bitcoin Keys and Addresses
Table of Contents
1. 比特币私钥
比特币采用椭圆曲线 Secp256k1, “私钥”是 \(1\) 到 \(n-1\) 之间的随机数, \(n\) 是个很大的数(256 个比特位),其值如下:
\(n\) = 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141
\(n\) 用科学计数法表示,约为 \(1.15792 \times 10^{77}\) ,这个范围是巨大的,你无法“猜测”出别人随机生成的私钥。
1.1. 私钥编码
前面介绍过,私钥就是一个随机大整数,可以用 256 比特位表示,这 256 比特位可以编码为:
- 16 进制形式;
- Wallet Import Format (WIF);
- WIF-compressed 形式。
使用 openssl rand -hex 32 可以生成一个随机的 32 字节(256 比特)数据,它可以作为 16 进制形式的比特币私钥,如:
$ openssl rand -hex 32 # 随机生成 32 字节随机数,以 16 进制形式显示 d9815f47582f890ef4f818fd5dec98a6419fda23e57f1fed62ee0b9f79b1a785 $ openssl rand -hex 32 # 再测试一次 3bd5af025525af11f4471f299d600af51a6d697374b39ad19224a91b748a17ec
假设,16 进制的私钥为 0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d ,其对应的 WIF、WIF-compressed 形式如表 1 所示。
| Format | Private Key | Note |
|---|---|---|
| Hex | 0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d |
|
| WIF | 5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ |
以 5 开头 |
| WIF-compressed | KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617 |
以 K 或 L 开头 |
注:“WIF-compressed”这个名称取得不好,实际上 WIF-compressed 形式的私钥比 WIF 形式的私钥更长。
1.1.1. WIF、WIF-compressed 编码
下面介绍 WIF、WIF-compressed 具体的编码过程(属于 Base58Check 编码):
1、在 16 进制的私钥前面增加 version 字段,Bitcoin 主网为 0x80 ,Bitcoin 测试网为 0xef ; 对于 WIF-compressed 形式,则后面还要增加 0x01 。 这个结果称为 extended key,即:
800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d # for WIF 800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d01 # for WIF-compressed
2、对 extended key 计算 SHA-256:
1ce0723b5128b03fcfd2484368f1bbc89dc7c6eddf56589c2eccb2bacbbcafc9 # for WIF b504a81b66924482a289ce571a614c37b122f40cefb0abba0227e143154c2153 # for WIF-compressed
3、再次计算上面结果的 SHA-256:
507a5b8dfed0fc6fe8801743720cedec06aa5c6fca72b07c49964492fb98a714 # for WIF a62019d20340a1de1b5f254f07f2f6c96ad5165218459ab4f3c8f5a7c0e12183 # for WIF-compressed
4、第二次 SHA256 结果的前 4 个字节作为 checksum:
507a5b8d # for WIF a62019d2 # for WIF-compressed
5、把 checksum 加到 extended key 的后面:
800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d507a5b8d # for WIF 800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d01a62019d2 # for WIF-compressed
6、对上面结果进行 Base58 编码,即得到 WIF、WIF-compressed 形式:
5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ # WIF KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617 # WIF-compressed
注:Base58 编码表只有 58 个有效字符,具体来说,就是 10 个数字加上 26 个小写字母加上 26 个大写字母,再减去 0OIl 这 4 个容易产生混淆的字符。
1.1.2. WIF、WIF-compressed 编码及解码(Python 实现)
WIF、WIF-compressed 的编码及解码过程,用 Python 实现如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import hashlib
import base58
from typing import Union
def scrub_input(hex_str_or_bytes: Union[str, bytes]) -> bytes:
if isinstance(hex_str_or_bytes, str):
hex_str_or_bytes = bytes.fromhex(hex_str_or_bytes)
return hex_str_or_bytes
# wallet import format key - base58 encoded format
# https://bitcoin.stackexchange.com/questions/9244/private-key-to-wif
def gen_wif_key(private_key: Union[str, bytes], compressed_WIF: bool = False) -> bytes:
private_key = scrub_input(private_key)
# prepended mainnet version byte to private key
mainnet_private_key = b'\x80' + private_key
if compressed_WIF:
mainnet_private_key = b'\x80' + private_key + b'\x01'
# perform SHA-256 hash on the mainnet_private_key
sha256 = hashlib.sha256()
sha256.update(mainnet_private_key)
hash = sha256.digest()
# perform SHA-256 on the previous SHA-256 hash
sha256 = hashlib.sha256()
sha256.update(hash)
hash = sha256.digest()
# create a checksum using the first 4 bytes of the previous SHA-256 hash
# append the 4 checksum bytes to the mainnet_private_key
checksum = hash[:4]
hash = mainnet_private_key + checksum
# convert mainnet_private_key + checksum into base58 encoded string
return base58.b58encode(hash)
def decode_wif(wif: str) -> bytes:
compressed = False
if wif.startswith('K') or wif.startswith('L'):
compressed = True
decoded = base58.b58decode(wif)
if compressed:
private_key = decoded[1:-5] # [80 xxx 1 checksum]
else:
private_key = decoded[1:-4] # [80 xxx checksum]
return private_key
prikey = '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d' # hex
wif = gen_wif_key(prikey)
print(wif) # 5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ
wif_compressed = gen_wif_key(prikey, True)
print(wif_compressed) # KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617
prikey = decode_wif('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ')
print(prikey.hex()) # 0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d
1.1.3. 私钥加密编码(BIP38)
WIF、WIF-compressed 形式的私钥是没有加密的,可以解码得到原始的那个“随机大整数”。
BIP38 提议用 AES 算法对私钥进行加密,这种方案得到的私钥以字符 6P 开头,这种私钥必须输入密码才能导入到各种比特币钱包中。表 2 是 BIP38 的例子。
| Item | Example |
|---|---|
| Private Key (WIF) | 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn |
| Passphrase | MyTestPassphrase |
| Encrypted Key (BIP38) | 6PRTHL6mWa48xSopbU1cKrVjpKbBZxcLRRCdctLJ3z5yxE87MobKoXdTsJ |
2. 比特币公钥
公钥 \(K\) 是椭圆曲线上的一个点,它可由私钥计算而来,其公式为:
\[K = k G\]
上式中, \(k\) 为私钥, \(G\) 为 Base Point,它是 secp256k1 的一个参数。
假设私钥 \(k\) 为 0x1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd ,那么由椭圆曲线上的计算公式 \(k G\) (具体过程略),可以得到 \(K\) 的两个坐标分别为:
2.1. 公钥两种表达形式
公钥是一个坐标,如何用一个数来表达两个坐标呢?有两种方案:“Uncompressed format”和“Compressed format”,如图 1 所示。

Figure 1: 公钥两种表达形式
Uncompressed 形式,就是把两个坐标 \(x,y\) 直接连接在一起,再在前面加个 0x04 前缀即可,如前面公钥例子的 Uncompressed 形式为:
04f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a07cf33da18bd734c600b96a72bbc4749d5141c90ec8ac328ae52ddfe2e505bdb
Compressed 形式,就是当 \(y\) 为偶数时,编码为 \(02 x\) ,当 \(y\) 为奇数时,编码为 \(03 x\) 。如前面公钥例子中,由于 \(y\) 是奇数,所以公钥的 Compressed 形式为:
03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a
为什么在 Compressed 形式中,我们可以不编码 \(y\) 值呢,因为通过椭圆曲线 Secp256k1 的公式 \(y^2 = x^3 + 7\) 可以重新计算出 \(y\) 。
2.1.1. 从私钥计算公钥(Python 实现)
下面 Python 代码可以实现从私钥计算 Uncompressed/Compressed 公钥:
import ecdsa
from ecdsa.ellipticcurve import PointJacobi
def derive_public_key(private_key: bytes, compressed: bool = False) -> bytes:
Q: PointJacobi = int.from_bytes(private_key, byteorder='big') * ecdsa.curves.SECP256k1.generator
xstr: bytes = Q.x().to_bytes(32, byteorder='big')
ystr: bytes = Q.y().to_bytes(32, byteorder='big')
if compressed:
parity: int = Q.y() & 1
return (2 + parity).to_bytes(1, byteorder='big') + xstr
else:
return b'\04' + xstr + ystr
prikey = bytearray.fromhex('1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd')
uncompressed_pubkey = derive_public_key(prikey, False)
print("uncompressed public key =", uncompressed_pubkey.hex())
compressed_pubkey = derive_public_key(prikey, True)
print("compressed public key =", compressed_pubkey.hex())
3. 比特币地址
比特币各种类型的地址如图 2 所示(摘自 https://en.bitcoin.it/wiki/Invoice_address )。

Figure 2: Bitcoin Address
3.1. P2PKH(1 开头的地址)
P2PKH(Pay-to-Public-Key-Hash)是最原始的地址,由公钥通过 Hash 计算后得到,它们都以 1 开头。
我们以前面介绍过的 Compressed 公钥(03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a)为例,介绍一下从公钥转换得到地址的步骤:
1、公钥为:
03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a
2、计算其 SHA256,得到:
448997ae8759eb23d6c6b67de6fc3419006cbf061617c3e07323aaf5bcd53476
3、对上面结果计算 RIPEMD-160 哈希,得到:
bbc1e42a39d05a4cc61752d6963b7f69d09bb27b
4、计算 checksum,规则是对上面结果前面加上版本号(主网为 0x00,测试网为 0x6f),然后计算两次 SHA256:
bbc1e42a39d05a4cc61752d6963b7f69d09bb27b
| 最前面加上版本号(主网为 00,测试网为 6f)
v
00bbc1e42a39d05a4cc61752d6963b7f69d09bb27b
| 第 1 次 sha256
v
d094b259d52d874cf084aaece4251e5d3b731fe66fe2dc21564106330d25f278
| 第 2 次 sha256
v
37fefcd01823fdbce992747702d2420b11a5d266b92d516a93aa83f336ce8241
| 取前 4 字节
v
37fefcd0
5、用格式 [version][ripemd160_hash][checksum] 构造出下面结果:
00bbc1e42a39d05a4cc61752d6963b7f69d09bb27b37fefcd0
6、对上面结果进行 Base58 编码,得到:
1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
这个地址是用 Compressed 公钥计算出来的,被称为“Compressed 地址”;如果用 Uncompressed 公钥重复进行上面过程,则会得到另外一个地址,称为“Uncompressed 地址”。在这个例子中,用 Uncompressed 公钥 04f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a07cf33da18bd734c600b96a72bbc4749d5141c90ec8ac328ae52ddfe2e505bdb 进行计算会得到地址 1424C2F4bC9JidNjjTUZCbUxv6Sa1Mt62x ,这也是一个合法的比特币地址,称为 Uncompressed 地址。
一个比特币私钥对应两个地址(Compressed/Uncompressed 地址),它们都是合法的。
参考:https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
3.1.1. 从公钥计算 P2PKH 地址(Python 实现)
下面 Python 代码可以实现从公钥计算 P2PKH 地址:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import hashlib
import base58
def sha256(inputs: bytes) -> bytes:
""" Computes sha256 """
sha = hashlib.sha256()
sha.update(inputs)
return sha.digest()
def ripemd160(inputs: bytes) -> bytes:
""" Computes ripemd160 """
rip = hashlib.new('ripemd160')
rip.update(inputs)
return rip.digest()
def base58_cksum(inputs: bytes) -> bytes:
""" Computes base 58 four bytes check sum """
s1 = sha256(inputs)
s2 = sha256(s1)
checksum = s2[0:4]
return checksum
def pubkey_compressed_to_uncompressed(compressed_pubkey: bytes) -> bytes:
""" Converts compressed pubkey to uncompressed format """
assert len(compressed_pubkey) == 33
# modulo p which is defined by secp256k1's spec
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
x = int.from_bytes(compressed_pubkey[1:33], byteorder='big')
y_sq = (pow(x, 3, p) + 7) % p
y = pow(y_sq, (p + 1) // 4, p)
if compressed_pubkey[0] % 2 != y % 2:
y = p - y
y_bytes = y.to_bytes(32, byteorder='big')
return b'\04' + compressed_pubkey[1:33] + y_bytes # x + y
def pubkey_to_p2pkh_addr(pubkey: bytes, version: bytes) -> bytes:
""" Derives legacy (p2pkh) address from pubkey """
out1 = sha256(pubkey)
out2 = ripemd160(out1)
# Base-58 encoding with a checksum
checksum = base58_cksum(version + out2)
address = base58.b58encode(version + out2 + checksum)
return address
pubkey = '03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a'
pubkey_uncompressed = b''
pubkey_compressed = b''
if pubkey.startswith('04'): # uncompressed
pubkey_uncompressed = bytes.fromhex(pubkey)
if ord(bytearray.fromhex(pubkey[-2:])) % 2 == 0:
pubkey_compressed_hex_str = '02' + pubkey[2:66]
else:
pubkey_compressed_hex_str = '03' + pubkey[2:66]
pubkey_compressed = bytes.fromhex(pubkey_compressed_hex_str)
else: # compressed
pubkey_uncompressed = pubkey_compressed_to_uncompressed(bytes.fromhex(pubkey))
pubkey_compressed = bytes.fromhex(pubkey)
print("compressed public key =", pubkey_compressed.hex())
print("uncompressed public key =", pubkey_uncompressed.hex())
version = b'\x00' # 0x00 for mainnet, 0x6f for testnet
addr_compressed = pubkey_to_p2pkh_addr(pubkey_compressed, version)
addr_uncompressed = pubkey_to_p2pkh_addr(pubkey_uncompressed, version)
print("address (uncompressed) = ", addr_uncompressed) # 1424C2F4bC9JidNjjTUZCbUxv6Sa1Mt62x
print("address (compressed) = ", addr_compressed) # 1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
3.2. P2SH(3 开头的地址)
Pay-to-Script Hash (P2SH) 地址由 Script Hash 的 Base58Check 编码得到。由于进行 Base58Check 编码时,对 P2SH 地址指定的版本前缀为 5,这导致这类地址以“3”开头。
3.2.1. P2SH-P2WPKH(隔离见证兼容地址,3 开头)
在原生的隔离见证地址(参考节 3.3)提出之前,生成隔离见证地址的方式是在 P2SH 中嵌入 Pay-to-Witness-Public-Key-Hash(P2WPKH),细节可参考:https://bitcointalk.org/index.php?topic=5229211.0
3.2.2. 从公钥计算 P2SH-P2WPKH 地址(Python 实现)
下面 Python 代码可以实现从公钥计算 P2SH-P2WPKH 地址:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import hashlib
import base58
def sha256(inputs: bytes) -> bytes:
""" Computes sha256 """
sha = hashlib.sha256()
sha.update(inputs)
return sha.digest()
def ripemd160(inputs: bytes) -> bytes:
""" Computes ripemd160 """
rip = hashlib.new('ripemd160')
rip.update(inputs)
return rip.digest()
def base58_cksum(inputs: bytes) -> bytes:
""" Computes base 58 four bytes check sum """
s1 = sha256(inputs)
s2 = sha256(s1)
checksum = s2[0:4]
return checksum
def pubkey_to_p2sh_p2wpkh_addr(pubkey_compressed: bytes) -> bytes:
""" Derives p2sh-segwit (p2sh p2wpkh) address from pubkey """
pubkey_hash = sha256(pubkey_compressed)
rip = ripemd160(pubkey_hash)
redeem_script = b'\x00\x14' + rip # 0x00: OP_0, 0x14: PushData
redeem_hash = sha256(redeem_script)
redeem_rip = ripemd160(redeem_hash)
# Base-58 encoding with a checksum
version = b'\x05' # 0x05 for mainnet, 0xc4 for testnet
checksum = base58_cksum(version + redeem_rip)
address = base58.b58encode(version + redeem_rip + checksum)
return address
pubkey = '03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a'
addr_p2sh_segwit = pubkey_to_p2sh_p2wpkh_addr(bytes.fromhex(pubkey))
print("p2sh-segwit address", addr_p2sh_segwit) # 3FyC6EYuxW22uj4CaEGjNCjxeg7gHyFeVv
3.3. Bech32(bc1q 开头的地址)
在 BIP0173 中提出了 bc1 开头的地址,它们是原生的隔离见证地址(注:对于版本 0 的隔离见证地址,它们总是以 bc1q 开头)。
Bech32 编码由 3 部分组成:
- human-readable part,比特币主网固定为 bc;
- separator,固定为 1;
- data part,由数字和小写字母组成,但排除这 4 个:
1,b,i,o(注:10 个数字加上 26 个小写字母,再减去这 4 个排除的字符,可得 32 个字符)。
后文仅介绍 Pay-to-Witness-Public-Key-Hash(P2WPKH)地址的生成, P2WPKH 地址长度固定为 42 字符。 这里不介绍 Pay-to-Witness-Script-Hash(P2WSH)地址, P2WSH 地址长度固定为 62 字符。
3.3.1. 从公钥计算 P2WPKH 地址(Python 实现)
下面 Python 代码可以实现从公钥计算 P2WPKH 地址:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import hashlib
from typing import Optional, Union, List, Tuple
from enum import Enum
# From https://github.com/sipa/bech32/blob/master/ref/python/segwit_addr.py
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
BECH32_CONST = 1
BECH32M_CONST = 0x2bc830a3
class Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
BECH32 = 1
BECH32M = 2
def sha256(inputs: bytes) -> bytes:
""" Computes sha256 """
sha = hashlib.sha256()
sha.update(inputs)
return sha.digest()
def ripemd160(inputs: bytes) -> bytes:
""" Computes ripemd160 """
rip = hashlib.new('ripemd160')
rip.update(inputs)
return rip.digest()
def bech32_polymod(values: List[int]) -> int:
"""Internal function that computes the Bech32 checksum."""
generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
chk = 1
for value in values:
top = chk >> 25
chk = (chk & 0x1ffffff) << 5 ^ value
for i in range(5):
chk ^= generator[i] if ((top >> i) & 1) else 0
return chk
def bech32_hrp_expand(hrp: str) -> List[int]:
"""Expand the HRP into values for checksum computation."""
return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
def bech32_verify_checksum(hrp: str, data: List[int]) -> Union[Encoding, None]:
"""Verify a checksum given HRP and converted data characters."""
const = bech32_polymod(bech32_hrp_expand(hrp) + data)
if const == BECH32_CONST:
return Encoding.BECH32
if const == BECH32M_CONST:
return Encoding.BECH32M
return None
def bech32_create_checksum(hrp: str, data: List[int], spec: Encoding) -> List[int]:
"""Compute the checksum values given HRP and data."""
values = bech32_hrp_expand(hrp) + data
const = BECH32M_CONST if spec == Encoding.BECH32M else BECH32_CONST
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
def bech32_encode(hrp: str, data: List[int], spec: Encoding) -> str:
"""Compute a Bech32 string given HRP and data values."""
combined = data + bech32_create_checksum(hrp, data, spec)
return hrp + '1' + ''.join([CHARSET[d] for d in combined])
def bech32_decode(bech: str) -> Union[Tuple[None, None, None], Tuple[str, List[int], Encoding]]:
"""Validate a Bech32/Bech32m string, and determine HRP and data."""
if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
(bech.lower() != bech and bech.upper() != bech)):
return None, None, None
bech = bech.lower()
pos = bech.rfind('1')
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
return None, None, None
if not all(x in CHARSET for x in bech[pos+1:]):
return None, None, None
hrp = bech[:pos]
data = [CHARSET.find(x) for x in bech[pos+1:]]
spec = bech32_verify_checksum(hrp, data)
if spec is None:
return None, None, None
return hrp, data[:-6], spec
def convertbits(data: bytes, frombits: int, tobits: int, pad: bool = True) -> Optional[List[int]]:
"""General power-of-2 base conversion."""
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
if value < 0 or (value >> frombits):
return None
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
return None
return ret
def decode(hrp: str, addr: str) -> Union[Tuple[None, None], Tuple[int, List[int]]]:
"""Decode a segwit address."""
hrpgot, data, spec = bech32_decode(addr)
if hrpgot != hrp:
return None, None
decoded = convertbits(data[1:], 5, 8, False)
# Witness programs are between 2 and 40 bytes in length.
if decoded is None or len(decoded) < 2 or len(decoded) > 40:
return None, None
# Witness versions are in range 0..16.
if data[0] > 16:
return None, None
# Witness v0 programs must be exactly length 20 or 32.
if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
return None, None
# Witness v0 uses Bech32; v1 through v16 use Bech32m.
if data[0] == 0 and spec != Encoding.BECH32 or data[0] != 0 and spec != Encoding.BECH32M:
return None, None
# Success.
return data[0], decoded
def encode(hrp: str, witver: int, witprog: bytes) -> Optional[str]:
"""Encode a segwit address."""
spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec)
if decode(hrp, ret) == (None, None):
return None
return ret
def pubkey_to_segwit_addr(pubkey: bytes) -> Optional[str]:
hrp = "bc" # "bc" for mainnet, "tb" for testnet
witver = 0
witprog = ripemd160(sha256(pubkey))
addr = encode(hrp, witver, witprog)
return addr
pubkey_hex = '0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798'
addr = pubkey_to_segwit_addr(bytearray.fromhex(pubkey_hex))
print(addr) # bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
3.4. Bech32m(bc1p 开头的地址)
Bech32 有个缺点:如果地址的最后一个字符是 p,则在紧接着 p 之前的位置插入或者删除任意数量的字符 q 都不会使其 checksum 失效。
为了缓解 Bech32 的上述缺点,在 BIP0350 中提出了 Bech32m 地址:
- 对于版本为 0 的原生隔离见证地址,使用以前的 Bech32;
- 对于版本为 1(或者更高)的原生隔离见证地址,则使用新的 Bech32m。
对于 Bech32m 地址,当版本为 1 时,它们总是以 bc1p 开头(即 Taproot 地址)。
3.4.1. 计算 P2TR 地址
P2TR 是 Pay-to-Taproot 的缩写,这种 Output 有两种花费路径:Key Path 和 Script Path。我们这里只考虑生成 Key Path 花费路径的 P2TR 地址。
怎么从公钥推导出 P2TR 地址呢?
P2PKH(公钥的 Hash 的编码): public key -> ripemd160(sha256(public key)) ---base58---> 1... P2WPKH(公钥的 Hash 的编码): public key -> ripemd160(sha256(public key)) ---bech32---> bc1q... P2TR(不再是公钥的 Hash 的编码了,而是对 tweaked public key 的直接编码): public key -> tweaked public key ---bech32m---> bc1p...
关于怎样从 Public Key 得到 Tweaked Public Key,这里不介绍,可以参考 bip86 或者 bip341 。
3.5. 不同地址对应的不同 scriptPubKey
表 3 总结了不同地址对应的不同 scriptPubKey 等信息。
| scriptPubKey (Locking Script) | scriptSig (Unlocking Script) | Witness | |
|---|---|---|---|
| P2PK | <pubKey> OP_CHECKSIG | <sig> | |
| P2PKH | OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG | <sig> <pubKey> | |
| P2MS | OP_<M> <pubKey>...<pubKey> OP_<N> OP_CHECKMULTISIG | OP_0 <signature>... | |
| P2SH | OP_HASH160 <scriptHash> OP_EQUAL | <scriptParam>... <redeemScript> | |
| P2SH (2-of-3 signatures) | OP_HASH160 <multisig_scriptHash> OP_EQUAL | 0 <sig1> <sig2> <OP_3 <pubKey1> <pubKey2> <pubKey3> OP_2 OP_CHECKMULTISIG> | |
| P2SH-P2WPKH | OP_HASH160 <scriptHash> OP_EQUAL | 0 <pubKeyHash> | <sig> <pubKey> |
| P2SH-P2WSH | OP_HASH160 <scriptHash> OP_EQUAL | 0 <redeemScriptHash> | <scriptParam>... <redeemScript> |
| P2SH-P2WSH (2-of-3 signatures) | OP_HASH160 <scriptHash> OP_EQUAL | 0 <multisig_scriptHash> | 0 <sig1> <sig2> <OP_3 <pubKey1> <pubKey2> <pubKey3> OP_2 OP_CHECKMULTISIG> |
| Native P2WPKH | OP_0 <pubKeyHash> | -empty- | <sig> <pubKey> |
| Native P2WSH | OP_0 <scriptHash> | -empty- | <scriptParam>... <redeemScript> |
| Native P2WSH (2-of-3 signatures) | OP_0 <multisig_scriptHash> | -empty- | 0 <sig1> <sig2> <OP_3 <pubKey1> <pubKey2> <pubKey3> OP_2 OP_CHECKMULTISIG> |
| P2TR (Public Key Path Spending) | OP_1 <pubKey> | -empty- | <schnorr-sig> |
| P2TR (Script Path Spending) | OP_1 <pubKey> | -empty- | <script> <controlBlock> |
3.5.1. 从锁定脚本(scriptPubKey)可推测出相应地址类型
表 4 介绍了不同地址的 scriptPubKey 的具体实例。这样,我们从 scriptPubKey 可推测出相应地址类型。
| scriptPubKey (Locking Script) | scriptPubKey 长度 | 说明 | |
|---|---|---|---|
| P2PK (Uncompressed) | 0x41{65-byte pubkey}ac | 67 bytes | 地址以 1 开头 |
| P2PK (Compressed) | 0x21{33-byte pubkey}ac | 35 bytes | 地址以 1 开头 |
| P2PKH | 0x76a914{20-byte keyhash}88ac | 25 bytes | 地址以 1 开头,keyhash=RIPEMD160(SHA256(pubKey)),pubKey 可以是 Uncompressed 或者 Compressed |
| P2MS (2-of-3 signatures) | 0x5241{65-byte pubkey}41{65-byte pubkey}41{65-byte pubkey}53ae | 201 bytes | P2MS 的 scriptPubKey 长度不固定,和签名方案有关。无地址 |
| P2SH | 0xa914{20-byte scripthash}87 | 23 bytes | 地址以 3 开头,通过地址不可区分 P2SH-P2WPKH/P2SH-P2WSH 等。scripthash=RIPEMD160(redeemScript) |
| Native P2WPKH | 0x0014{20-byte keyhash} | 22 bytes | 地址以 bc1q 开头,比 Native P2WSH 地址要短。keyhash=RIPEMD160(SHA256(compressedPubKey)) |
| Native P2WSH | 0x0020{32-byte scripthash} | 34 bytes | 地址以 bc1q 开头,比 Native P2WPKH 地址要长。scripthash=SHA256(witnessScript) |
| P2TR (Public Key Path Spending) | 0x5120{32-byte-tweaked-public-key} | 34 bytes | 地址以 bc1p 开头,通过地址不可区分 Key Path/Script Path |
| P2TR (Script Path Spending) | 0x5120{32-byte-tweaked-public-key} | 34 bytes | 地址以 bc1p 开头,通过地址不可区分 Key Path/Script Path |
尽管 P2PK 的 scriptPubKey 和 P2PKH 的 scriptPubKey 形式不一样,但它们计算地址的方式是一样的,都是公钥进行哈希后编码得到地址(节 3.1 介绍了具体的推导步骤)。换句话说,如果公钥相同,则由 P2PK 的 scriptPubKey 推导出来的地址和由 P2PKH 的 scriptPubKey 推导出来的地址是相同的。比如,交易 0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9 的 Output 的 scriptPubKey(410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac)是 P2PK 类型,而交易 ad38beebfb3f9ecc759787a69ada97c561a11e1a22355a052e5ab750774e5f8b 的 Output 的 scriptPubKe(76a91411b366edfc0a8b66feebae5c2e25a7b6a5d1cf3188ac)是 P2PKH 类型,由它们推导出来的地址是相同,都是 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S。
参考:
P2PK (Uncompressed) 交易实例:
https://mempool.space/tx/f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16
P2PK (Compressed) 交易实例:
https://mempool.space/tx/aa2cb91301d684c443f81db2a623baed5ea657ea33c5fc4d8f995033292ebbd6
P2MS 1-of-2 交易实例:
https://mempool.space/tx/78b28d3c2324da8c2f01840021addbcabb68f7ce1d4da870cabe5e9df6afe63d
P2MS 2-of-3 交易实例:
https://mempool.space/tx/581d30e2a73a2db683ac2f15d53590bd0cd72de52555c2722d9d6a78e9fea510
3.6. 地址安全性
比特币地址(这里是指 P2PKH 地址)是公钥的 Hash,如图 3 所示。

Figure 3: Private key, public key, and bitcoin address
假设某一天椭圆公钥密码体系被攻破(即由公钥可以计算出私钥),你的比特币就马上不安全了吗?不一定,因为钱包地址是公钥的 Hash, 如果某个钱包地址只收比特币,而不转出比特币,那么公钥是不会暴露的 ,只有当你转出比特币时,你才要需要提供签名及公钥。如果你确实需要转出比特币,那么你可以在转出比特币的同时把余额全部转入到另外一个全新的地址(这个地址还没有过转出记录)。
4. 附录
4.1. Bech32 和 Bech32m 编码
Bech32 是一种带有 Checksum 的 Base32 编码。不过,Bech32 采用了和 Base32 不一样的字符集,Bech32 的字符集如表 5 所示。
| Value | Symbol | Value | Symbol | Value | Symbol | Value | Symbol |
|---|---|---|---|---|---|---|---|
| 0 | q | 8 | g | 16 | s | 24 | c |
| 1 | p | 9 | f | 17 | 3 | 25 | e |
| 2 | z | 10 | 2 | 18 | j | 26 | 6 |
| 3 | r | 11 | t | 19 | n | 27 | m |
| 4 | y | 12 | v | 20 | 5 | 28 | u |
| 5 | 9 | 13 | d | 21 | 4 | 29 | a |
| 6 | x | 14 | w | 22 | k | 30 | 7 |
| 7 | 8 | 15 | 0 | 23 | h | 31 | l |
Bech32 编码由 3 部分组成:
- human-readable part,必需是 ASCII printable characters(即编码范围为 33-126 的 ASCII 字符);
- separator,固定为 1;
- data part,数据的 Base32 编码(采用表 5 所示字符集),再在最后加上 6 个 Checksum。所以 data part 至少是 6 个字符长。
4.1.1. Bech32 编码计算实例
假设 human-readable part 为 abc,需要编码的数据的十进制形式为 0xd4ec8962,下面介绍如何计算它的 Bech32 编码。
第一步,用 Base32 方式计算数据的编码(不包含 Checksum):
Input data: 0xd4ec8962
Hex: d 4 e c 8 9 6 2
8-bit: 11010100 11101100 10001001 01100010
5-bit: 11010 10011 10110 01000 10010 11000 10000 [the last three zeros '000' are the pading]
Decimal: 26 19 22 8 18 24 16
Output: 6 n k g j c s
所以,不包含 Checksum 时的编码为 6nkgjcs。
第二步,计算 6 字符的 Checksum。它借鉴了 BCH 编码的思想,可以看做是多项式除法运算的余数。节 4.1.1.1 有具体的代码实例,这里直接给出 Checksum 结果:c4rhar。
第三步:合并结果,得到完整 Bech32 编码:
abc 1 6nkgjcs c4rhar ---- bech32 --> abc16nkgjcsc4rhar
^ ^ ^ ^
| | | |
hrp sep | checksum
base32
4.1.1.1. Checksum 计算实例
下面介绍节 4.1.1 中例子的 Bech32 Checksum 的计算过程:
from typing import Optional, Union, List, Tuple
from enum import Enum
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
BECH32_CONST = 1
BECH32M_CONST = 0x2bc830a3 # Bech32m 和 Bech32 的唯一区别就是这个 CONST 值不一样
class Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
BECH32 = 1
BECH32M = 2
def bech32_polymod(values: List[int]) -> int:
"""Internal function that computes the Bech32 checksum."""
generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
chk = 1
for value in values:
top = chk >> 25
chk = (chk & 0x1ffffff) << 5 ^ value
for i in range(5):
chk ^= generator[i] if ((top >> i) & 1) else 0
return chk
def bech32_hrp_expand(hrp: str) -> List[int]:
"""Expand the HRP into values for checksum computation."""
return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
def bech32_verify_checksum(hrp: str, data: List[int]) -> Union[Encoding, None]:
"""Verify a checksum given HRP and converted data characters."""
const = bech32_polymod(bech32_hrp_expand(hrp) + data)
if const == BECH32_CONST:
return Encoding.BECH32
if const == BECH32M_CONST:
return Encoding.BECH32M
return None
def bech32_create_checksum(hrp: str, data: List[int], spec: Encoding) -> List[int]:
"""Compute the checksum values given HRP and data."""
values = bech32_hrp_expand(hrp) + data
const = BECH32M_CONST if spec == Encoding.BECH32M else BECH32_CONST
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
def convertbits(data: bytes, frombits: int, tobits: int, pad: bool = True) -> Optional[List[int]]:
"""General power-of-2 base conversion."""
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
if value < 0 or (value >> frombits):
return None
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
return None
return ret
hrp = "abc"
input = bytes.fromhex("d4ec8962")
input_five_bit_groups = convertbits(input, 8, 5)
checksum_bech32 = bech32_create_checksum(hrp, input_five_bit_groups, Encoding.BECH32)
print(checksum_bech32) # [24, 21, 3, 23, 29, 3]
print(''.join([CHARSET[d] for d in checksum_bech32])) # c4rhar
checksum_bech32m = bech32_create_checksum(hrp, input_five_bit_groups, Encoding.BECH32M)
print(checksum_bech32m) # [13, 9, 19, 27, 24, 1]
print(''.join([CHARSET[d] for d in checksum_bech32m])) # dfnmcp
4.1.2. Bech32m
Bech32m 和 Bech32 基本相同,它们的区别仅仅在于计算 Checksum 时内部的一个 CONST 值不一样。
4.1.3. 开源实现
在 BIP0173 中定义 Segwit address 编码格式时,约定了待编码数据的前 5 bits 是 witness 版本号。这导致 Base32 编码过程中“8 bits 数组转换为 5 bits 数组”的这个操作不能封装在 encode 函数的内部处理了,所以很多时候我们看到的 Bech32 相关库都会提供一个 ConvertBits 函数(注:如果约定 8 bits 是 witness 版本号就不用向外暴露 ConvertBits 函数了):
// 风格一:encode 函数接受的数据是 5 bits 数组(往往通过另一个函数 ConvertBits 来得到)
// 优点:方便用于编码比特币地址。
// 缺点:当编码任意数据时对使用者不友好。调用 encode 函数前需要先调用 ConvertBits 把待编码的 8 bits 数组转换为 5 bits 数组
// 下面代码来源:https://pkg.go.dev/github.com/btcsuite/btcutil/bech32#example-Encode
data := []byte("Test data")
// Convert test data to base32:
conv, err := bech32.ConvertBits(data, 8, 5, true)
if err != nil {
fmt.Println("Error:", err)
}
encoded, err := bech32.Encode("abc", conv) // 期望的输入是 5 bits 数组,进行 Base32 时直接使用,内部不会再转换为 5 bits 数组了
if err != nil {
fmt.Println("Error:", err)
}
如果用户想用 Bech32 编码任意数据时,采用上面这种方式定义 encode 是不友好的,因为它和现有的 base58/base64 等编码函数的使用习惯不相符合。
也有一些开源实现把“8 bits 数组转换为 5 bits 数组”这个子操作封装在 encode 函数内部:
// 风格二:encode 函数接受的数据就是待编码数组
// 优点:当编码任意数据时非常简单。直接调用 encode 函数即可,这和 base58/base64 等用法很像
// 缺点:不能用 encode 函数编码比特币地址。库需要提供另外一个单独函数来编码比特币地址
// 下面代码来源:https://docs.rs/bech32/latest/bech32/
const DATA: [u8; 20] = [0xab; 20]; // Arbitrary data to be encoded.
const STRING: &str = "abc14w46h2at4w46h2at4w46h2at4w46h2at958ngu";
// Encode arbitrary data using "abc" as the human-readable part and append a bech32m checksum.
let hrp = Hrp::parse("abc").expect("valid hrp");
let string = bech32::encode::<Bech32m>(hrp, &DATA).expect("failed to encode string"); // 期望的输入是 8 bits 数组,内部会转换为 5 bits 数组,以方便进行 Base32 编码
assert_eq!(string, STRING);
// Encode arbitrary data as a Bitcoin taproot address.
let taproot_address = segwit::encode(hrp::BC, segwit::VERSION_1, &DATA).expect("valid witness version and program");
assert_eq!(taproot_address, TAP_ADDR);
5. 参考
Mastering Bitcoin, 2nd Edition