比特币地址详解

参考文档

比特币地址说明: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 比特位可以编码为:

  1. 16 进制形式;

  2. Wallet Import Format (WIF);

  3. 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

比特币地址

比特币是建立在密码学基础上的,主要涉及到哈希函数、公钥密码学和数学签名,其贯穿始终的密码学在比特币中并不是为了加密,而是为了证明所有权。比特币交易和节点数据并没有被加密,反而是公开。

比特币公钥

  1. 公钥K是椭圆曲线上的一个点,它可以由私钥计算出来,定义的公式为 K = kG.其中k为私钥,G为base point是椭圆曲线secp256k1的一个参数。 如果私钥k="0x1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd",那么通过计算可以得到K的两个坐标为: x="f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a" y="07cf33da18bd734c600b96a72bbc4749d5141c90ec8ac328ae52ddfe2e505bdb"

  2. 公钥是一个曲线上的坐标,要表达这个坐标,又产生了两种存储方式:

  • Uncompressed 形式:就是把两个坐标(x, y)直接连接在一起,再在前面加个 0x04 前缀即可;

  • Compressed 形式,就是当y为偶数时,编码为02x,当y为奇数时,编码为 03x;

  1. 基本原理:通过椭圆曲线 $y^2=x^3 + 7$ 和编码的x值,我们可以计算得出y,然后完整拼装的出坐标点原始公钥数据。

  2. 下面是通过私钥计算公钥的代码

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

隔离见证地址

  1. 隔离见证地址是什么? "隔离见证"(Segregated Witness,或缩写为 segwit)是比特币的一次大的升级,于 2017 年在主网上激活。 具体指的是把见证数据(即 scriptSig 中的内容)从 Transaction 信息里抽离出来,单独存放。

  2. 解决了什么问题?

  • 隔离见证的最大好处之一是消除了"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),是纯正的隔离见证地址。

  1. Bech32 编码由 3 部分组成:

  • human-readable part,比特币主网固定为 bc;

  • separator,固定为 1;

  • data part,由数字和小写字母组成,但排除这 4 个: 1, b, i, o (注:10 个数字加上 26 个小写字母,再减去这 4 个排除的字符,可得 32 个字符)。

  1. 有个缺点:如果地址的最后一个字符是 p,则在紧接着 p 之前的位置插入或者删除任意数量的字符 q 都不会使其 checksum 失效。为了缓解 Bech32 的上述缺点,在 BIP0350 中提出了 Bech32m 地址:

  • 对于版本为 0 的原生隔离见证地址,使用以前的 Bech32;

  • 对于版本为 1(或者更高)的原生隔离见证地址,则使用新的 Bech32m。

  • 对于 Bech32m 地址,当版本为 1 时,它们总是以 bc1p 开头(即 Taproot 地址)

  1. 例子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
  1. 例子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