Bitcoin Keys and Addresses
Table of Contents
1. 比特币私钥
比特币采用椭圆曲线 Secp256k1, “私钥”是
0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141
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. 比特币公钥
公钥
上式中,
假设私钥 0x1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
,那么由椭圆曲线上的计算公式
2.1. 公钥两种表达形式
公钥是一个坐标,如何用一个数来表达两个坐标呢?有两种方案:“Uncompressed format”和“Compressed format”,如图 1 所示。
Figure 1: 公钥两种表达形式
Uncompressed 形式,就是把两个坐标
04f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a07cf33da18bd734c600b96a72bbc4749d5141c90ec8ac328ae52ddfe2e505bdb
Compressed 形式,就是当
03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a
为什么在 Compressed 形式中,我们可以不编码
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 -*- import hashlib from typing import Optional 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() # From https://github.com/sipa/bech32/blob/master/ref/python/segwit_addr.py """Reference implementation for Bech32 and segwit addresses.""" CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" def bech32_polymod(values): """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): """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, data): """Verify a checksum given HRP and converted data characters.""" return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1 def bech32_create_checksum(hrp, data): """Compute the checksum values given HRP and data.""" values = bech32_hrp_expand(hrp) + data polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1 return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] def bech32_encode(hrp, data): """Compute a Bech32 string given HRP and data values.""" combined = data + bech32_create_checksum(hrp, data) return hrp + '1' + ''.join([CHARSET[d] for d in combined]) def bech32_decode(bech): """Validate a Bech32 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) bech = bech.lower() pos = bech.rfind('1') if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: return (None, None) if not all(x in CHARSET for x in bech[pos+1:]): return (None, None) hrp = bech[:pos] data = [CHARSET.find(x) for x in bech[pos+1:]] if not bech32_verify_checksum(hrp, data): return (None, None) return (hrp, data[:-6]) def convertbits(data, frombits, tobits, pad=True): """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, addr): """Decode a segwit address.""" hrpgot, data = bech32_decode(addr) if hrpgot != hrp: return (None, None) decoded = convertbits(data[1:], 5, 8, False) if decoded is None or len(decoded) < 2 or len(decoded) > 40: return (None, None) if data[0] > 16: return (None, None) if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: return (None, None) return (data[0], decoded) def encode(hrp: str, witver: int, witprog: bytes) -> Optional[str]: """Encode a segwit address.""" ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5)) 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://www.blockchain.com/btc/tx/f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16
P2PK (Compressed) 交易实例:
https://www.blockchain.com/btc/tx/aa2cb91301d684c443f81db2a623baed5ea657ea33c5fc4d8f995033292ebbd6
P2MS 1-of-2 交易实例:
https://www.blockchain.com/btc/tx/78b28d3c2324da8c2f01840021addbcabb68f7ce1d4da870cabe5e9df6afe63d
P2MS 2-of-3 交易实例:
https://www.blockchain.com/btc/tx/581d30e2a73a2db683ac2f15d53590bd0cd72de52555c2722d9d6a78e9fea510
3.6. 地址安全性
比特币地址(这里是指 P2PKH 地址)是公钥的 Hash,如图 3 所示。
Figure 3: Private key, public key, and bitcoin address
假设某一天椭圆公钥密码体系被攻破(即由公钥可以计算出私钥),你的比特币就马上不安全了吗?不一定,因为钱包地址是公钥的 Hash, 如果某个钱包地址只收比特币,而不转出比特币,那么公钥是不会暴露的 ,只有当你转出比特币时,你才要需要提供签名及公钥。如果你确实需要转出比特币,那么你可以在转出比特币的同时把余额全部转入到另外一个全新的地址(这个地址还没有过转出记录)。
4. 参考
Mastering Bitcoin, 2nd Edition