比特币私鑰公鑰位址生成算法
原理
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNCM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0zYU9UNFR1Y4x2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZwpmLzgTN3MzN1ATM0ETNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
實作
#!coding:utf8
#author:yqq
#date:2019/3/4 0004 14:35
#description: 比特币位址生成算法
import hashlib
import ecdsa
import os
#2019-05-15 添加私鑰限制範圍
g_b58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
#g_nMaxPrivKey = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140 - 0x423423843 #私鑰最大值 (內插補點是自定義的)
#g_nMinPrivKey = 0x0000000000000000000000000000000000000000000000000000000000000001 + 0x324389329 #私鑰最小值 (增值是自定義的)
#2019-11-12 根據官方定義修改 有限域
# http://www.secg.org/sec2-v2.pdf#page=9&zoom=100,0,249
# 關于 有限域的定義 請參考
# 0xEFFFFFC2F = 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1
g_nFactor = 0xEFFFFFC2F + 0x23492397 #增值自定義
g_nMaxPrivKey = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140 - g_nFactor #私鑰最大值 (內插補點是自定義的)
g_nMinPrivKey = 0x0000000000000000000000000000000000000000000000000000000000000001 + g_nFactor #私鑰最小值 (增值是自定義的)
def Base58encode(n):
'''
base58編碼
:param n: 需要編碼的數
:return: 編碼後的
'''
result = ''
while n > 0:
result = g_b58[n % 58] + result
n /= 58
return result
def Base256decode(s):
'''
base256編碼
:param s:
:return:
'''
result = 0
for c in s:
result = result * 256 + ord(c)
return result
def CountLeadingChars(s, ch):
'''
計算一個字元串開頭的字元的次數
比如: CountLeadingChars('000001234', '0') 結果是5
:param s:字元串
:param ch:字元
:return:次數
'''
count = 0
for c in s:
if c == ch:
count += 1
else:
break
return count
def Base58CheckEncode(version, payload):
'''
:param version: 版本字首 , 用于區分主網 和 測試網絡
:param payload:
:return:
'''
s = chr(version) + payload
checksum = hashlib.sha256(hashlib.sha256(s).digest()).digest()[0:4] #兩次sha256, 區前4位元組作為校驗和
result = s + checksum
leadingZeros = CountLeadingChars(result, '\0')
return '1' * leadingZeros + Base58encode(Base256decode(result))
def PrivKeyToPubKey(privKey):
'''
私鑰-->公鑰
:param privKey: 共65個位元組: 0x04 + x的坐标 + y的坐标
:return:
'''
sk = ecdsa.SigningKey.from_string(privKey.decode('hex'), curve=ecdsa.SECP256k1)
# vk = sk.verifying_key
return ('\04' + sk.verifying_key.to_string()).encode('hex')
def PrivKeyToPubKeyCompress(privKey):
'''
私鑰-->公鑰 壓縮格式公鑰
:param privKey: ( 如果是奇數,字首是 03; 如果是偶數, 字首是 02) + x軸坐标
:return:
'''
sk = ecdsa.SigningKey.from_string(privKey.decode('hex'), curve=ecdsa.SECP256k1)
# vk = sk.verifying_key
try:
# print(sk.verifying_key.to_string().encode('hex'))
point_x = sk.verifying_key.to_string().encode('hex')[ : 32*2] #擷取點的 x 軸坐标
point_y = sk.verifying_key.to_string().encode('hex')[32*2 : ] #擷取點的 y 軸坐标
# print("point_x:", point_x)
if (long(point_y, 16) & 1) == 1: # 如果是奇數,字首是 03; 如果是偶數, 字首是 02
prefix = '03'
else:
prefix = '02'
return prefix + point_x
except:
raise("array overindex")
pass
#https://en.bitcoin.it/wiki/List_of_address_prefixes
def PubKeyToAddr(privKey, isTestnet = False):
'''
公鑰-->位址
:param privKey:私鑰
:param isTestnet:是否是測試網絡
:return:位址
'''
ripemd160 = hashlib.new('ripemd160')
ripemd160.update(hashlib.sha256(privKey.decode('hex')).digest())
if isTestnet:
return Base58CheckEncode(0x6F, ripemd160.digest()) #0x6F p2pkh testnet
# return base58CheckEncode(0x05, ripemd160.digest()) #05 p2sh mainnet
return Base58CheckEncode(0x00, ripemd160.digest()) #00 p2pkh mainnet
def PrivKeyToWIF(privKey, isTestnet = False):
'''
将私鑰轉為 WIF格式 , 用于比特币錢包導入
:param privKey: 私鑰(16進制字元串)
:return: WIF格式的私鑰
'''
if isTestnet:
# return Base58CheckEncode(0xEF, privKey.decode('hex') + '\01') #0xEF 測試網絡 fix bug: 2019-04-03 yqq 01是多餘的, 隻有是壓縮的格式的時候,才需要加
return Base58CheckEncode(0xEF, privKey.decode('hex') ) #0xEF 測試網絡
# return Base58CheckEncode(0x80, privKey.decode('hex') + '\01') #0x80 主網
return Base58CheckEncode(0x80, privKey.decode('hex') ) #0x80 主網
def PrivKeyToWIFCompress(privKey, isTestnet = False):
'''
壓縮格式
将私鑰轉為 WIF格式 , 用于比特币錢包導入
:param privKey: 私鑰(16進制字元串)
:return: WIF格式的私鑰
'''
if isTestnet:
return Base58CheckEncode(0xEF, privKey.decode('hex') + '\01') #0xEF 測試網絡
return Base58CheckEncode(0x80, privKey.decode('hex') + '\01') #0x80 主網
def GenPrivKey():
'''
生成私鑰, 使用 os.urandom (底層使用了作業系統的随機函數接口, 取決于CPU的性能,各種的硬體的資料名額)
:return:私鑰(16進制編碼)
'''
#2019-05-15 添加私鑰範圍限制
while True:
privKey = os.urandom(32).encode('hex') #生成 256位 私鑰
if g_nMinPrivKey < int(privKey, 16) < g_nMaxPrivKey:
return privKey
def GenAddr(isTestnet=False):
'''
此函數用于C++調用,
:param isTestnet: 是否是測試網絡
:return: (私鑰, 公鑰, 位址)
'''
privKey = GenPrivKey()
# print("privkey : " + privKey)
privKeyWIF = PrivKeyToWIF(privKey, isTestnet)
# print("privkey WIF:" + PrivKeyToWIF(privKey, isTestnet))
pubKey = PrivKeyToPubKey(privKey)
# print("pubkey : " + pubKey)
addr = PubKeyToAddr(pubKey, isTestnet)
# print("addr : " + addr)
return str(privKeyWIF), str(pubKey), str(addr)
def GenAddrCompress(isTestnet=False):
'''
此函數用于C++調用,
:param isTestnet: 是否是測試網絡
:param isCompress: 是否壓縮
:return: (私鑰, 公鑰, 位址)
'''
privKey = GenPrivKey()
# print("privkey : " + privKey)
privKeyWIF = PrivKeyToWIFCompress(privKey, isTestnet)
# print("privkey WIF:" + PrivKeyToWIF(privKey, isTestnet))
pubKey = PrivKeyToPubKeyCompress(privKey)
# print("pubkey : " + pubKey)
addr = PubKeyToAddr(pubKey, isTestnet)
# print("addr : " + addr)
return str(privKeyWIF), str(pubKey), str(addr)
def GenMultiAddr(nAddrCount = 1, isTestnet=True):
'''
生成多個位址
:param nAddrCount:
:param isTestnet:
:return:
'''
# return [("1111", "2222", "3333"), ("4444", "55555", "66666")]
# return [1, 2, 3, 4]
# return ["1111", "2222", "3333", "4444"]
lstRet = []
for i in range(nAddrCount):
lstRet.append(GenAddrCompress(isTestnet))
return lstRet
#
def good():
isTestnet = True
# private_key = GenPrivKey()
private_key = '95b51ad564bd26811aeafc06ebe64643d2a50f82aa4901e714ba4be635ed9a57'
print("privkey : " + private_key)
print("privkey WIF:" + PrivKeyToWIF(private_key, isTestnet))
pubKey = PrivKeyToPubKey(private_key)
print("pubkey : " + pubKey)
addr = PubKeyToAddr( pubKey , isTestnet)
print("addr : " + addr)
print("-----------------------------")
print("privkey WIF compress:" + PrivKeyToWIFCompress(private_key, isTestnet))
pubKey = PrivKeyToPubKeyCompress(private_key)
print("pubkey compress : " + pubKey)
addr = PubKeyToAddr( pubKey , isTestnet)
print("addr compress: " + addr)
#
#
# def main():
# good()
# for i in range(1):
# print(GenAddr(True))
# if __name__ == '__main__':
#
# main()
關于位址壓縮
參考連結:
https://bitcoin.stackexchange.com/questions/3059/what-is-a-compressed-bitcoin-key