# 比特币地址详解 **Published by:** [heqing](https://paragraph.com/@vilink/) **Published on:** 2022-04-30 **URL:** https://paragraph.com/@vilink/rUlprbfgPZYeqjqHJ2Gb ## Content 参考文档 比特币地址说明:https://zh.thedev.id/mastering-bitcoin-cash/3-keys-addresses-wallets.html csdn: https://blog.csdn.net/BitTribeLab/article/details/102834123 比特币地址维基百科:https://en.bitcoin.it/wiki/List_of_address_prefixes secpk256k1: https://en.bitcoin.it/wiki/Secp256k1 比特币地址到私钥:http://aandds.com/blog/bitcoin-key-addr.html 私钥 私钥本质就是一个随机数,一个长度为256位的数值 私钥的编码 为了标识或者存储这个随机大整数,可以用 256 比特位表示,这 256 比特位可以编码为: 16 进制形式; Wallet Import Format (WIF); WIF-compressed 形式。 随机生成一个32自己数据 $ openssl rand -hex 32 # 随机生成 32 字节随机数,以 16 进制形式显示 d9815f47582f890ef4f818fd5dec98a6419fda23e57f1fed62ee0b9f79b1a785 $ openssl rand -hex 32 # 再测试一次 3bd5af025525af11f4471f299d600af51a6d697374b39ad19224a91b748a17ec # 或者自己记一个数字,12345678,然后转成16进制即可 0000000000000000000000000000000000000000000000000000000000bc614e WIF、WIF-compressed 的编码及解码过程 #!/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 比特币地址 比特币是建立在密码学基础上的,主要涉及到哈希函数、公钥密码学和数学签名,其贯穿始终的密码学在比特币中并不是为了加密,而是为了证明所有权。比特币交易和节点数据并没有被加密,反而是公开。 比特币公钥 公钥K是椭圆曲线上的一个点,它可以由私钥计算出来,定义的公式为 K = kG.其中k为私钥,G为base point是椭圆曲线secp256k1的一个参数。 如果私钥k="0x1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd",那么通过计算可以得到K的两个坐标为: x="f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a" y="07cf33da18bd734c600b96a72bbc4749d5141c90ec8ac328ae52ddfe2e505bdb" 公钥是一个曲线上的坐标,要表达这个坐标,又产生了两种存储方式: Uncompressed 形式:就是把两个坐标(x, y)直接连接在一起,再在前面加个 0x04 前缀即可; Compressed 形式,就是当y为偶数时,编码为02x,当y为奇数时,编码为 03x; 基本原理:通过椭圆曲线 $y^2=x^3 + 7$ 和编码的x值,我们可以计算得出y,然后完整拼装的出坐标点原始公钥数据。 下面是通过私钥计算公钥的代码 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()) 公钥和地址 由于比特币UTXO的账户模型,用户的可用余额,实际上是别人转入的未使用的交易输出。所以比特币的地址可以抽象定义为,锁定某一个未使用的交易输出的hash数据;而比特币锁定某个交易输出的数据就根据交易的类型不同,有了很多形式。 P2PKH支付公钥哈希:(Pay-to-Pubkey Hash) !Unsupported embed 下面是python的示例形式: #!/usr/bin/env python3 # -*- coding: utf-8 -*- import hashlib import base58 """ P2PKH(Pay-to-Public-Key-Hash) 是最原始的地址,由公钥通过 Hash 计算后得到,它们都以 1 开头。 我们以前面介绍过的 Compressed 公钥(03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a)为例,介绍一下从公钥转换得到地址的步骤: 1. 计算其 SHA256 2. 对上面结果计算 RIPEMD-160 哈希 3. 计算 checksum,规则是对上面结果前面加上版本号(主网为 0x00,测试网为 0x6f),然后计算两次 SHA256 4. 用格式 [version][ripemd160_hash][checksum] 构造出结果 5. 对上面结果进行 Base58Check 编码 用 Compressed 公钥计算出来的,被称为“Compressed 地址”; 如果用 Uncompressed 公钥重复进行上面过程,则会得到另外一个地址,称为“Uncompressed 地址”。 一个比特币私钥对应两个地址(Compressed/Uncompressed 地址),它们都是合法的。 """ 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()) main_version = b'\x00' # 0x00 for mainnet, test_version = b'\x6f' # 0x6f for testnet addr_compressed = pubkey_to_p2pkh_addr(pubkey_compressed, main_version) addr_uncompressed = pubkey_to_p2pkh_addr(pubkey_uncompressed, main_version) test_addr_compressed = pubkey_to_p2pkh_addr(pubkey_compressed, test_version) test_addr_uncompressed = pubkey_to_p2pkh_addr(pubkey_uncompressed, test_version) print("mainnet address (uncompressed) = ", addr_uncompressed) # 1424C2F4bC9JidNjjTUZCbUxv6Sa1Mt62x print("mainnet address (compressed) = ", addr_compressed) # 1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy print("testnet address (uncompressed) = ", test_addr_compressed) # mxdivjAqQSQj4LrAMX1XLQidyfU3pCWeS7 print("testnet address (compressed) = ", test_addr_uncompressed) # miY1V5L3QDaZVjrMT2Sw2WhHn63GzsNFQB P2SH支付脚本地址:(Pay-to-Script-Hash) !Unsupported embed 示例 from bitcoinutils.setup import setup from bitcoinutils.transactions import Transaction, TxInput, TxOutput, Sequence from bitcoinutils.keys import P2pkhAddress, P2shAddress, PrivateKey from bitcoinutils.script import Script from bitcoinutils.constants import TYPE_RELATIVE_TIMELOCK def main(): # always remember to setup the network setup('testnet') # # This script creates a P2SH address containing a CHECKSEQUENCEVERIFY plus # a P2PKH locking funds with a key as well as for 20 blocks # # set values relative_blocks = 20 seq = Sequence(TYPE_RELATIVE_TIMELOCK, relative_blocks) # secret key corresponding to the pubkey needed for the P2SH (P2PKH) transaction p2pkh_sk = PrivateKey('cRvyLwCPLU88jsyj94L7iJjQX5C2f8koG4G2gevN4BeSGcEvfKe9') # get the address (from the public key) p2pkh_addr = p2pkh_sk.get_public_key().get_address() # create the redeem script redeem_script = Script([seq.for_script(), 'OP_CHECKSEQUENCEVERIFY', 'OP_DROP', 'OP_DUP', 'OP_HASH160', p2pkh_addr.to_hash160(), 'OP_EQUALVERIFY', 'OP_CHECKSIG']) # create a P2SH address from a redeem script addr = P2shAddress.from_script(redeem_script) print(addr.to_string()) if __name__ == "__main__": main() # 2N48oA8YokPa4uumpAgiuKfPwxXy2wRxrbk 隔离见证地址 隔离见证地址是什么? "隔离见证"(Segregated Witness,或缩写为 segwit)是比特币的一次大的升级,于 2017 年在主网上激活。 具体指的是把见证数据(即 scriptSig 中的内容)从 Transaction 信息里抽离出来,单独存放。 解决了什么问题? 隔离见证的最大好处之一是消除了"Transaction Malleability", 一般翻译成"交易延展性"; 减少了手续费,见证数据通常在交易的总大小中占了很大比重,通过将见证数据从交易中移出,隔离见证提高了比特币的可扩展性。 隔离见证兼容地址(Nested Segwit Address) [ P2SH-P2WPKH] 在原生的隔离见证地址提出之前,原有的地址基础上,实现隔离见证的方式是在P2SH中嵌入 Pay-to-Witness-Public-Key-Hash(P2WPKH)的方式来实现的。这种形式即为兼容的隔离见证地址,和 P2SH的格式是一样的,节点不升级也能正常使用隔离见证。 例子: #!/usr/bin/env python3 # -*- coding: utf-8 -*- import hashlib import base58 """ Pay-to-Script Hash (P2SH) 地址由 Script Hash 的 Base58Check 编码得到。由于进行 Base58Check 编码时,对 P2SH 地址指定的版本前缀为 5,这导致这类地址以“3”开头。 在原生的隔离见证地址提出之前,生成隔离见证地址的方式是在 P2SH 中嵌入 Pay-to-Witness-Public-Key-Hash(P2WPKH) 细节可参考:https://bitcointalk.org/index.php?topic=5229211.0 """ 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 def pubkey_to_p2sh_p2wpkh_addr_test(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'\xc4' # 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)) addr_p2sh_segwit_test = pubkey_to_p2sh_p2wpkh_addr_test(bytes.fromhex(pubkey)) print("p2sh-segwit address", addr_p2sh_segwit) # 3FyC6EYuxW22uj4CaEGjNCjxeg7gHyFeVv print("p2sh-segwit test address", addr_p2sh_segwit_test) # 2N7XQ9yUwZxXP7WgkFMtbz9jDs2Kr2njYRy 原生隔离见证地址 (Native Segwit Address)[ P2WPKH 或 P2WSH ] bc1 开头的地址,是由新的隔离见证脚本生成的地址(P2WPKH 或 P2WSH),是纯正的隔离见证地址。 Bech32 编码由 3 部分组成: human-readable part,比特币主网固定为 bc; separator,固定为 1; data part,由数字和小写字母组成,但排除这 4 个: 1, b, i, o (注:10 个数字加上 26 个小写字母,再减去这 4 个排除的字符,可得 32 个字符)。 有个缺点:如果地址的最后一个字符是 p,则在紧接着 p 之前的位置插入或者删除任意数量的字符 q 都不会使其 checksum 失效。为了缓解 Bech32 的上述缺点,在 BIP0350 中提出了 Bech32m 地址: 对于版本为 0 的原生隔离见证地址,使用以前的 Bech32; 对于版本为 1(或者更高)的原生隔离见证地址,则使用新的 Bech32m。 对于 Bech32m 地址,当版本为 1 时,它们总是以 bc1p 开头(即 Taproot 地址) 例子P2WPKH(其实就是原来P2PKH的隔离见证格式) #!/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 例子P2WSH(其实就是原来P2SH的隔离见证格式) from bitcoinutils.setup import setup from bitcoinutils.script import Script from bitcoinutils.keys import P2wshAddress, PrivateKey def main(): # always remember to setup the network setup('testnet') # # P2WSH # p2wpkh_key = PrivateKey.from_wif('cNn8itYxAng4xR4eMtrPsrPpDpTdVNuw7Jb6kfhFYZ8DLSZBCg37') script = Script(['OP_1', p2wpkh_key.get_public_key().to_hex(), 'OP_1', 'OP_CHECKMULTISIG']) p2wsh_addr = P2wshAddress.from_script(script) print("P2WSH of P2PK:", p2wsh_addr.to_string() ) if __name__ == "__main__": main() # tb1qy4kdfavhluvnhpwcqmqrd8x0ge2ynnsl7mv2mdmdskx4g3fc6ckq8f44jg ## Publication Information - [heqing](https://paragraph.com/@vilink/): Publication homepage - [All Posts](https://paragraph.com/@vilink/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@vilink): Subscribe to updates - [Twitter](https://twitter.com/HaggaiQin): Follow on Twitter