用 Python 实现比特币密钥到地址的转换过程【Python、比特币】

用 Python 实现从密钥到公钥到地址的的转换过程,包括压缩公钥。

基础

比特币的椭圆曲线方程

y2 mod p = (x3 + 7) mod p

其中 p 是素数常数,值为 2256 - 232 - 29 - 28 - 27 - 26 - 24 - 1

公钥就是 K = k * G,G 是比特币中规定的一个椭圆曲线上的生成点,k 为私钥,计算出的公钥也是曲线上一个点,也就是公钥点为 (x, y) 这种一个点。

普通的公钥是直接 04xy 这样,把 ‘04’,x 的十六进制,y 的十六进制,直接拼起来的

公钥如何压缩

由于公钥是椭圆曲线上的一个点,所以只记录 x 就行了,y 可以通过方程计算得出。

在实数域中的 y2 会得到一对相反数解。在基于素数幂 p 的有限域中,y2 会得到两个奇偶不同的解,需要标识一下才知道最开始的 K 点的 y 值是哪个。

假设公钥坐标为 (x, y),普通公钥地址就是 04xy,压缩公钥地址就是 02x 或者 03x(02 代表 y 值是偶数,03 代表 y 值是奇数)。

地址

有两种公钥值,那就能生成两种地址值。

密钥的表示

同一个密钥根据是否压缩公钥的选择,能生成两组公钥和地址,这会给比特币计算带来麻烦。所以规定,同一个密钥,要么生成普通公钥,要么生成压缩公钥。

如何标识一个密钥用来生成普通公钥了,还是生成压缩公钥了呢?

规定:在密钥后面加上 ‘01’ 表示用于生成压缩公钥。

代码

这个包 bitcoin 作者已经不维护了,但现在做学习用还是没问题的。

下面这个过程中,其实只有「椭圆曲线的计算」和「公钥生成地址」这两个步骤要用这个包,其余的都是可以手动实现的。而「椭圆曲线计算」是有python-ecdsa 库可以实现的,「公钥生成地址」也只是两次单向加密,也有相应的库可以实现。
所以不使用 bitcoin 这个不再维护的库也是可以实现的,只是它进行了集合,这里只是学习用。

安装:pip install bitcoin

python=3.8

import bitcoin

msgs = [
    '所有的「压缩」,都表示公钥坐标转换为公钥值时的压缩',
    '压缩密钥不是把密钥压缩,而是指仅用来生成压缩公钥的密钥',
    '压缩地址也不是把地址压缩,而是用压缩公钥生成的地址'
]
print('='*80)
print('\n'.join(m.center(60, ' ') for m in msgs))
print('='*80)

# 生成一个随机的密钥
while True:
    # 生成一个用十六进制表示的长 256 位的私钥(str类型)
    private_key = bitcoin.random_key()
    # 解码为十进制的整形密钥
    decoded_private_key = bitcoin.decode_privkey(private_key, 'hex')
    if 0 < decoded_private_key < bitcoin.N:
        break

print(f'密钥(十六进制):{private_key} (长 256 位)')
print(f'密钥(十进制):{decoded_private_key} (0 到 1.158*10**77 之间)')

# 用 WIF 格式编码密钥
wif_encoded_private_key = bitcoin.encode_privkey(decoded_private_key, 'wif')
print(f'密钥(WIF):{wif_encoded_private_key} (5 开头,长 51 字符)')

# 用 01 标识的压缩密钥
compressed_private_key = private_key + '01'
print(f'压缩密钥(十六进制):{compressed_private_key} (01 结尾,长 264 位)')

# 生成 WIF的压缩格式
wif_compressed_private_key = bitcoin.encode_privkey(
    bitcoin.decode_privkey(compressed_private_key, 'hex'), 'wif')
print(f'压缩密钥(WIF):{wif_compressed_private_key} (L/K 开头)')

# 计算公钥坐标 K = k * G
public_key = bitcoin.fast_multiply(bitcoin.G, decoded_private_key)
print(f'公钥(坐标):{public_key}')
# 转十六也可用 bitcoin.encode(xxx, 16)
print(f'公钥(坐标的十六进制):{tuple(hex(i) for i in public_key)}')

# 计算公钥
hex_encoded_public_key = bitcoin.encode_pubkey(public_key, 'hex')
print(f'公钥(十六进制):{hex_encoded_public_key} (04 x y)')

# 计算压缩公钥
# if public_key[1] % 2 == 0:  # 两种方式均可
if public_key[1] & 1 == 0:
    compressed_prefix = '02'
else:
    compressed_prefix = '03'
# 转十六也可用 bitcoin.encode(xxx, 16)
hex_compressed_public_key = compressed_prefix + hex(public_key[0])[2:]
print(f'压缩公钥(十六进制){hex_compressed_public_key} '
      '(02 开头代表 y 是偶数,03 开头代表 y 是奇数)')

# 计算地址
# 传入公钥坐标对象/十六进制公钥值,输出同样的地址
# 传入压缩公钥值,输出与⬆️不同的地址
print(f'地址(b58check):{bitcoin.pubkey_to_address(public_key)} (1 开头)')
print(type(hex_compressed_public_key))
print('压缩地址(b58check):'
      f'{bitcoin.pubkey_to_address(hex_compressed_public_key)} (1 开头)')

运行结果

每次运行结果都不一样

================================================================================
                  所有的压缩,都表示公钥坐标转换为公钥值时的压缩                   
                压缩密钥不是把密钥压缩,而是指仅用来生成压缩公钥的密钥                 
                 压缩地址也不是把地址压缩,而是用压缩公钥生成的地址                  
================================================================================
密钥(十六进制):bc69884127d6afba047cae7ce79306f7c2971d2cea9181d07733044a0feded77 (长 256 位)
密钥(十进制):8522127486955100450095697088191569446034288320065272200560603315353770720805501.158*10**77 之间)
密钥(WIF):5KFGKTzS3zfNP6NszZZML79TAi8cL6oysN6EXb5CUsttnNgYr7f (5 开头,长 51 字符)
压缩密钥(十六进制):bc69884127d6afba047cae7ce79306f7c2971d2cea9181d07733044a0feded7701 (01 结尾,长 264 位)
压缩密钥(WIF):L3XxcW8VaDWMNWja54hnjL8JVrYawJeN1J66i1PXg3e3ZBnsrK3o (L/K 开头)
公钥(坐标):(44681669702600638617047357714142213882651896939363029749259445044549560937078, 89144939719109680695346155244111178188655845908814585456690302404355586953539)
公钥(坐标的十六进制):('0x62c8edc8d6b51b01c80ae7dc60a567ca991aba7da2e6fde9efd0b2889719aa76', '0xc5163f73167abdbce50c336b754a2febb74177bc9457855fd8f3235212d29143')
公钥(十六进制):0462c8edc8d6b51b01c80ae7dc60a567ca991aba7da2e6fde9efd0b2889719aa76c5163f73167abdbce50c336b754a2febb74177bc9457855fd8f3235212d2914304 x y)
压缩公钥(十六进制)0362c8edc8d6b51b01c80ae7dc60a567ca991aba7da2e6fde9efd0b2889719aa7602 开头代表 y 是偶数,03 开头代表 y 是奇数)
地址(b58check):18SswtpeVbc8cXWdT8yCzCAPZrcLRpDEas (1 开头)
压缩地址(b58check):1AAiMhqSkHyaKixMpFs7d4YEdUEHT4s8mK (1 开头)
发布了52 篇原创文章 · 获赞 143 · 访问量 29万+

猜你喜欢

转载自blog.csdn.net/lnotime/article/details/105511665