用 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 開頭)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章