比特币地址说明: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
#!/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数据;而比特币锁定某个交易输出的数据就根据交易的类型不同,有了很多形式。
!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
!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", 一般翻译成"交易延展性";
减少了手续费,见证数据通常在交易的总大小中占了很大比重,通过将见证数据从交易中移出,隔离见证提高了比特币的可扩展性。
在原生的隔离见证地址提出之前,原有的地址基础上,实现隔离见证的方式是在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
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
