1. 程式人生 > >比特幣地址生成演算法詳解

比特幣地址生成演算法詳解

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'\0')) 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

參考網址: