天天看點

4-比特币私鑰公鑰位址生成

比特币私鑰公鑰位址生成算法

原理

4-比特币私鑰公鑰位址生成
4-比特币私鑰公鑰位址生成

實作

#!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