zoukankan      html  css  js  c++  java
  • 比特币地址生成算法详解

    1 生成过程

    比特币地址生成流程如下图所示:

     

    第一步,随机选取一个32字节的数,大小介于1~0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141之间,作为私钥

    18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725

    第二步,使用椭圆曲线加密算法(ECDSA-SECP256k1)计算私钥所对应的非压缩公钥(共65字节,1字节0x04,32字节为x坐标,32字节为y坐标)。

    0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6

    第三步,计算公钥的SHA-256哈希值

    600FFE422B4E00731A59557A5CCA46CC183944191006324A447BDB2D98D4B408

    第四步,计算上一步哈希值的RIPEMD-160哈希值

    010966776006953D5567439E5E39F86A0D273BEE

    第五步,在上一步结果之间加入地址版本号(如比特币主网版本号"0x00")

    00010966776006953D5567439E5E39F86A0D273BEE

    第六步,计算上一步结果的SHA-256哈希值

    445C7A8007A93D8733188288BB320A8FE2DEBD2AE1B47F0F50BC10BAE845C094

    第七步,再次计算上一步结果的SHA-256哈希值

    D61967F63C7DD183914A4AE452C9F6AD5D462CE3D277798075B107615C1A8A30

    第八步,取上一步结果的前4个字节(8位十六进制数)D61967F6,把这4个字节加在第五步结果的后面,作为校验(这就是比特币地址的16进制形态)

    00010966776006953D5567439E5E39F86A0D273BEED61967F6

    第九步,用base58表示法变换一下地址(这就是最常见的比特币地址形态)

    16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM

    下面给出比特币地址生成的python源码

     1 import hashlib
     2 from ecdsa import SECP256k1, SigningKey
     3 import sys
     4 import binascii
     5 
     6 # 58 character alphabet used
     7 BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
     8 
     9 def from_bytes (data, big_endian = False):
    10     if isinstance(data, str):
    11         data = bytearray(data)
    12     if big_endian:
    13         data = reversed(data)
    14     num = 0
    15     for offset, byte in enumerate(data):
    16         num += byte << (offset * 8)
    17     return num
    18     
    19 def base58_encode(version, public_address):
    20     """
    21     Gets a Base58Check string
    22     See https://en.bitcoin.it/wiki/Base58Check_encoding
    23     """
    24     if sys.version_info.major > 2:
    25         version = bytes.fromhex(version)
    26     else:
    27         version = bytearray.fromhex(version)
    28     firstSHA256 = hashlib.sha256(version + public_address)
    29     print("first sha256: %s"%firstSHA256.hexdigest().upper())
    30     secondSHA256 = hashlib.sha256(firstSHA256.digest())
    31     print("second sha256: %s"%secondSHA256.hexdigest().upper())
    32     checksum = secondSHA256.digest()[:4]
    33     payload = version + public_address + checksum
    34     print("Hex address: %s"%binascii.hexlify(payload).decode().upper())
    35     if sys.version_info.major > 2:
    36         result = int.from_bytes(payload, byteorder="big")
    37     else:
    38         result = from_bytes(payload, True)
    39     # count the leading 0s
    40     padding = len(payload) - len(payload.lstrip(b''))
    41     encoded = []
    42 
    43     while result != 0:
    44         result, remainder = divmod(result, 58)
    45         encoded.append(BASE58_ALPHABET[remainder])
    46 
    47     return padding*"1" + "".join(encoded)[::-1]
    48 
    49 def get_private_key(hex_string):
    50     if sys.version_info.major > 2:
    51         return bytes.fromhex(hex_string.zfill(64))
    52     else:
    53         return bytearray.fromhex(hex_string.zfill(64))
    54 
    55 def get_public_key(private_key):
    56     # this returns the concatenated x and y coordinates for the supplied private address
    57     # the prepended 04 is used to signify that it's uncompressed
    58     if sys.version_info.major > 2:
    59         return (bytes.fromhex("04") + SigningKey.from_string(private_key, curve=SECP256k1).verifying_key.to_string())
    60     else:
    61         return (bytearray.fromhex("04") + SigningKey.from_string(private_key, curve=SECP256k1).verifying_key.to_string())
    62 
    63 def get_public_address(public_key):
    64     address = hashlib.sha256(public_key).digest()
    65     print("public key hash256: %s"%hashlib.sha256(public_key).hexdigest().upper())
    66     h = hashlib.new('ripemd160')
    67     h.update(address)
    68     address = h.digest()
    69     print("RIPEMD-160: %s"%h.hexdigest().upper())
    70     return address
    71 
    72 if __name__ == "__main__":
    73     #private_key = get_private_key("FEEDB0BDEADBEEF")
    74     private_key = get_private_key("18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725")
    75     print("private key: %s"%binascii.hexlify(private_key).decode().upper())
    76     public_key = get_public_key(private_key)
    77     print("public_key: %s"%binascii.hexlify(public_key).decode().upper())
    78     public_address = get_public_address(public_key)
    79     bitcoin_address = base58_encode("00", public_address)
    80     print("Final address %s"%bitcoin_address)

    执行过程如下:

     2 关键问题

    2.1 base58编码

    Base58编码是一种二进制转可视字符串的算法,主要用来转换大整数,将整数字节流转换为58编码流,实际上它就是整数的58进制,和2进制、8进制、16进制是一样的道理,只是用58作为进制的单位了,正好和58个不容易混淆的字符对应,比特币所用的字符表如下:

    123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

    该表去除了几个看起来会产生歧义的字符,如0(零)和O(大写字母O),I(大写的字母i)和l(小写的字母L)等。另外,比特币在实现base58编码时,开头的0做了特殊处理,所以可以将输入流开头的0直接填充到结果前边。以00000000000000000000000000000000000000000094a00911为例,最后非零整数为0x94a00911(2493516049),2493516049除以58商是42991656余数是1,1对应的base58编码是2,42991656除以58商是741235余数是26,26对应的base58编码是T,741235除以58商是12779余数是53,53对应的base58编码是v,12779除以58商是220余数是19,19对应的base58编码是L,220除以58商是3余数是46,46对应的base58编码是o,3对应的base58编码是4,000000000000000000000000000000000000000000对应着21字节的0,所以最终的base48编码为1111111111111111111114oLvT2。

    2.2 比特币地址个数

    而从比特币地址的生成过程可知,第四步RIPEMD-160算法的结果是20字节(160位)的数,该步是比特币地址的最小限制,所以理论上来说比特币合法地址总是应该是2^160个。另外比特币私钥是32字节(256位)的随机数,并且有一定大小范围的限制,所以合法的比特币私钥个数介于2^255~2^256之间,从而可以看出私钥个数远远大于比特币地址数,所以理论上应该存在多个私钥对应同一地址的情况。

    2.3 比特币地址长度

    一般BTC地址的长度是34位,也有33位,其实可以通过推理可知(当地址版本为00时),BTC最短长度是26位,最长长度是34位。由第1节第八步可知比特币地址的16进制形态有25个字节,所以理论上最短的地址应该是16进制地址00000000000000000000000000000000000000000000000000对应的base58编码地址1111111111111111111111111,但是由第八步可知以上所说的16进制地址的最后4个字节对应的是0000000000000000000000000000000000000000的两次哈希的最后4个字节即94a00911,所以对应的地址是00000000000000000000000000000000000000000094a00911对应的base58编码地址1111111111111111111114oLvT2,其长度是27位,该地址也是所谓的“燃烧地址”(burn address),BTC地址的正常推导过程是:私钥==>公钥==>Hash160<==>地址,Hash160是没有规则的字符串,我们可以随便提供个Hash160值,从半截腰上直接推导地址:Hash160<==>地址,这时候要想花费该地址上对应的比特币,必须知道Hash160串对应的私钥,而这近乎是一个无解的难题,没有私钥该地址就只能进不能出,进入的币再也不可用了,就像燃料被烧掉了一样, 非常形象。

    从上图可以看出,仅仅从2017年9月21日到2018年9月21日,就有9424位“土豪”向该地址发送9424次比特币,如果从2010年8月10日的第一笔交易算起,一共向该地址转入了53439次比特币,总量达66.68个比特币,以当前比特币价格这些币价值人民币300万左右。接着上面比特币地址长度的问题进行讨论,排除掉25字节16进制全零地址以后,后一个有可能是最短地址应该是16进制地址00000000000000000000000000000000000000000100000000对应的58编码地址,但是0000000000000000000000000000000000000001的二次哈希最后4个字节是9d35b5b9,所以相应的实际BTC地址是0000000000000000000000000000000000000000019d35b5b9对应的base58编码地址11111111111111111111BZbvjr,其长度是26位,当前该地址里也有0.01028个被“燃烧”掉的比特币,如下图(BTC.com竟然查不到该地址的余额):

     接下来继续分析比特币地址最长可能,应该是00fffffffffffffffffffffffffffffffffffffffffa06820b,最后4个字节是fffffffffffffffffffffffffffffffffffffffff两次哈希结果的后4个字节,对应的base58编码地址为1QLbz7JHiBTspS962RLKV8GndWFwi5j6Qr,该地址同样是“燃烧地址”,共转入51次比特币,总数是0.012495个,如下图:

     

    3 源码分析

     BTC源码中由PubKeyToAddress将比特币公钥转换为base58编码的字符串地址。

    1 inline string PubKeyToAddress(const vector<unsigned char>& vchPubKey)
    2 {
    3     return Hash160ToAddress(Hash160(vchPubKey));
    4 }

    在PubKeyToAddress函数中,会先调用Hash160对公钥进行SHA256和RIPEMD160哈希运算,即对应生成过程的第三、四步。

    1 inline uint160 Hash160(const vector<unsigned char>& vch)
    2 {
    3     uint256 hash1;
    4     SHA256(&vch[0], vch.size(), (unsigned char*)&hash1);
    5     uint160 hash2;
    6     RIPEMD160((unsigned char*)&hash1, sizeof(hash1), (unsigned char*)&hash2);
    7     return hash2;
    8 }

    之后调用Hash160ToAddress来处理生成的哈希结果,在Hash160ToAddress函数中首先会把版本加到哈希结果的最前面(第五步),之后调用EncodeBase58Check,该函数中13行会调用Hash函数来进行两次哈希运行(第六、七步)并附加两次哈希结果的最后四个字节(校验用),最后调用EncodeBase58做最终的base58编码转换,主要代码如下:

     1 inline string Hash160ToAddress(uint160 hash160)
     2 {
     3     // add 1-byte version number to the front
     4     vector<unsigned char> vch(1, ADDRESSVERSION);
     5     vch.insert(vch.end(), UBEGIN(hash160), UEND(hash160));
     6     return EncodeBase58Check(vch);
     7 }
     8 
     9 inline string EncodeBase58Check(const vector<unsigned char>& vchIn)
    10 {
    11     // add 4-byte hash check to the end
    12     vector<unsigned char> vch(vchIn);
    13     uint256 hash = Hash(vch.begin(), vch.end());
    14     vch.insert(vch.end(), (unsigned char*)&hash, (unsigned char*)&hash + 4);
    15     return EncodeBase58(vch);
    16 }
    17 
    18 inline string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend)
    19 {
    20     CAutoBN_CTX pctx;
    21     CBigNum bn58 = 58;
    22     CBigNum bn0 = 0;
    23 
    24     // Convert big endian data to little endian
    25     // Extra zero at the end make sure bignum will interpret as a positive number
    26     vector<unsigned char> vchTmp(pend-pbegin+1, 0);
    27     reverse_copy(pbegin, pend, vchTmp.begin());
    28 
    29     // Convert little endian data to bignum
    30     CBigNum bn;
    31     bn.setvch(vchTmp);
    32 
    33     // Convert bignum to string
    34     string str;
    35     str.reserve((pend - pbegin) * 138 / 100 + 1);
    36     CBigNum dv;
    37     CBigNum rem;
    38     while (bn > bn0)
    39     {
    40         if (!BN_div(&dv, &rem, &bn, &bn58, pctx))
    41             throw bignum_error("EncodeBase58 : BN_div failed");
    42         bn = dv;
    43         unsigned int c = rem.getulong();
    44         str += pszBase58[c];
    45     }
    46 
    47     // Leading zeroes encoded as base58 zeros
    48     for (const unsigned char* p = pbegin; p < pend && *p == 0; p++)
    49         str += pszBase58[0];
    50 
    51     // Convert little endian string to big endian
    52     reverse(str.begin(), str.end());
    53     return str;
    54 }
    Hash160ToAddress

    参考网址:

    http://8btc.com/article-1929-1.html

  • 相关阅读:
    数据库
    Xpath表达式
    错误:向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们
    xmlhttp status各类Http请求状态(status)及其含义
    配置ckeditor3.0 和ckfinder
    程序默认用管理员身份打开(vs2010)
    (转)CKFinder破解的方法,去掉提示
    无法嵌入互操作类型“SHDocVw.ShellWindowsClass”。请改用适用的接口
    ASP.NET页面执行顺序
    回调函数
  • 原文地址:https://www.cnblogs.com/zhaoweiwei/p/address.html
Copyright © 2011-2022 走看看