用 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 位)
密钥(十进制):85221274869551004500956970881915694460342883200652722005606033153537707208055 (0 到 1.158*10**77 之间)
密钥(WIF):5KFGKTzS3zfNP6NszZZML79TAi8cL6oysN6EXb5CUsttnNgYr7f (5 开头,长 51 字符)
压缩密钥(十六进制):bc69884127d6afba047cae7ce79306f7c2971d2cea9181d07733044a0feded7701 (01 结尾,长 264 位)
压缩密钥(WIF):L3XxcW8VaDWMNWja54hnjL8JVrYawJeN1J66i1PXg3e3ZBnsrK3o (L/K 开头)
公钥(坐标):(44681669702600638617047357714142213882651896939363029749259445044549560937078, 89144939719109680695346155244111178188655845908814585456690302404355586953539)
公钥(坐标的十六进制):('0x62c8edc8d6b51b01c80ae7dc60a567ca991aba7da2e6fde9efd0b2889719aa76', '0xc5163f73167abdbce50c336b754a2febb74177bc9457855fd8f3235212d29143')
公钥(十六进制):0462c8edc8d6b51b01c80ae7dc60a567ca991aba7da2e6fde9efd0b2889719aa76c5163f73167abdbce50c336b754a2febb74177bc9457855fd8f3235212d29143 (04 x y)
压缩公钥(十六进制)0362c8edc8d6b51b01c80ae7dc60a567ca991aba7da2e6fde9efd0b2889719aa76 (02 开头代表 y 是偶数,03 开头代表 y 是奇数)
地址(b58check):18SswtpeVbc8cXWdT8yCzCAPZrcLRpDEas (1 开头)
压缩地址(b58check):1AAiMhqSkHyaKixMpFs7d4YEdUEHT4s8mK (1 开头)