比特幣的HD錢包演化-2
好了,有了上一篇文章的基礎,我們可以從零開始完全探究數字貨幣的地址生成、管理方法;下面的程式碼均使用Linux Bash shell和Python3來處理;另外需要安裝 ofollow,noindex" target="_blank">pycoin 這個庫。
生成私鑰
一般來說,私鑰是個256bit的隨機字元。為了演示方便,我們用一個人民大眾喜聞樂見的地址生成為例子,私鑰選取為 sha256(“satoshi”)
>printf "satoshi"|sha256sum da2876b3eb31edb4436fa4650673fc6f01f90de2f1793c4ec332b2387b09726f-
得到私鑰為 da2876b3eb31edb4436fa4650673fc6f01f90de2f1793c4ec332b2387b09726f
用WIF表示私鑰
我們看到私鑰本質上是256bit的數字,他可以用二進位制表示,也可以用16進位制字串表示,也可以用Base58Check來表示;為了在不同的錢包中方便的匯入匯出私鑰,也為了方便二維碼的生成,比特幣採用了名為 WIF
的表示方法,下面列一個表格來說明:
Type | Prefix | Description | Private key |
---|---|---|---|
Raw | None | 32 bytes binary | 1101101000101000011101101011001111101011001100011110110110110100010000110110111110100100011001010000011001110011111111000110111100000001111110010000110111100010111100010111100100111100010011101100001100110010101100100011100001111011000010010111001001101111 |
HEX | None | 64 hexadecimal digits | da2876b3eb31edb4436fa4650673fc6f01f90de2f1793c4ec332b2387b09726f |
WIF | 5 | Base58Check encoding | 5KUN8s42BCTkQVMTy3oFfqeXE8awVskbDi6XbDMpRnFvHJW9fgk |
WIF-compressed | K or L | Base58Check encoding | L4XnHhvLC1b4ag9L2PM9kRicQxUoYT1Q36PQ21YtLNkrAdWZNos6 |
得到WIF 程式碼示例:
def gen_pubk_from_privk(private_key, compressed=True): # private_key = codecs.encode(os.urandom(32), 'hex').decode() secret_exponent = int('0x' + private_key, 0) print('WIF: ' + encoding.secret_exponent_to_wif(secret_exponent, compressed=compressed)) public_pair = ecdsa.public_pair_for_secret_exponent(ecdsa.secp256k1.generator_secp256k1, secret_exponent) print('public pair:', public_pair) return public_pair
WIF格式分為非壓縮和壓縮格式,壓縮私鑰其實是對非壓縮私鑰字尾追加了01之後的Base58Check編碼,具體生成過程為:
- 壓縮私鑰: 私鑰字首80+私鑰本體+壓縮私鑰字尾01 + 校驗
- 非壓縮私鑰: 私鑰字首80+私鑰本體+校驗
和字面意思相相反的是,壓縮私鑰比非壓縮私鑰還長。為啥這麼折騰呢?這個我們在公鑰生成的部分說明。
生成公鑰
我們之前的文章介紹了,公鑰是在橢圓曲線上的一個點,由一對座標(x,y)組成。公鑰通常表示為字首04緊接著兩個256位元的數字。其中一個256位元數字是公鑰的x座標,另一個256位元數字是y座標。字首04是用來區分非壓縮格式公鑰, 壓縮格式公鑰是以02或者03開頭。
下面是由前文中的私鑰所生成的公鑰,其座標x和y如下:
-
public pair:
- x = 89077434373547985693783396961781741114890330080946587550950125758215996319671
- y = 114001858762817543140175961139571810325965930451644331549950109688554928624341
加上字首04,完整的公鑰為:
K = 0489077434373547985693783396961781741114890330080946587550950125758215996319671114001858762817543140175961139571810325965930451644331549950109688554928624341
為什麼要區分壓縮格式和非壓縮格式
這是一個歷史問題,初版比特幣執行時,中本聰沒有考慮到一個問題:
一個公鑰是一個橢圓曲線上的點(x,y)。而橢圓曲線實際是一個數學方程,曲線上的點實際是該方程的一個解。因此,如果我們知道了公鑰的x座標,就可以通過解方程 y^2 % p = (x^3 + 7) % p
得到y坐 標。這種方案可以讓我們只儲存公鑰的x座標,略去y座標,從而將公鑰的大小和儲存空間減少了256 bits。如果每筆交易所 需要的位元組數減少了近一半,隨著時間推移,節省的資料傳輸和儲存空間還是很客觀的。
所以後來開發團隊推出了壓縮公鑰,為了跟之前老版本的非壓縮公鑰相區分,就加上了02和03作為字首。
那麼為什麼要加兩個字首(02,03)呢?
因為橢圓曲線加密的公式的左邊是y2 ,也就是說y的解是來自於一個平方根,可能是正值也可能是負值。更形象地說,y座標可能在 x座標軸的上面或者下面。橢圓曲線圖中曲線是對稱的,從x軸看就像對稱的鏡子兩面。因此,如果我們略去y座標,就必須儲存y的符號(正值或者負值)。換句話說,對於給定的x值,我們需要知道y值在x軸的上面還是下面,因為它們代表橢圓曲線上不同的點,即不同的公鑰。當我們在素數p階的有限域上使用二進位制算術計算橢圓曲線的時候,y座標可能是奇數或者偶數,分別對應前面所講的y值的正負符號。因此,為了區分y座標的兩種可能值,我們在生成壓縮格式公鑰時,如果y是偶數,則使用02作為字首;如果y是奇數,則使用03作為字首。這樣就可以根據公鑰中給定的x值,正確推匯出對應的y座標,從而將公鑰解壓縮為在橢圓曲線上的完整的點座標。
總結出來,一個公鑰的表現形式可以又兩種:
- 04開頭的非壓縮公鑰: (130位十六進位制 2+64+64)
- 02或03開頭的壓縮公鑰:(66位十六進位制 2+64)
這樣繼續推導,兩種表現形式可以推匯出兩個地址,也就是手握一個私鑰,可以推匯出兩個合法的比特幣地址。
這樣又間接解釋了為什麼會有壓縮私鑰和非壓縮私鑰兩種表現:
- 當中本聰實現第一版比特幣客戶端錢包的時候,沒有考慮到公鑰可以壓縮,所以採用了最原始直接的辦法儲存公鑰和私鑰
- 後來人們發現公鑰可以簡化儲存來節省一部分空間,於是加入了壓縮公鑰格式,為了跟之前的非壓縮公鑰區分,引入了字首
- 同樣,使用壓縮公鑰格式的錢包匯入匯出私鑰時,為了區分,也必須為私鑰標明它對應的公鑰是否壓縮格式,所以也為私鑰的表示引入了字尾
- 壓縮私鑰的意思是,由這個私鑰匯出的公鑰表示方法是壓縮的,私鑰本身還需要引入一個01作為字尾,長度反而多了一個位元組
從公鑰到比特幣地址
得出公鑰之後,地址的生成還要經過三重變換, 公鑰為K,變換過程如下:
- 首先計算 A = SHA256(K)
- 計算 B = RIPEMD160(A)
- Addr = Base58Check(prefix + B)
為什麼要有RIPEMD160(SHA256(K)) 的過程
因為中本聰設計之初充分考慮到了安全性方面的問題,一筆交易廣播後,並不是直接把公鑰K暴漏在外,如果你不花費這個UTXO,暴漏的只有 RIPEMD160(SHA256(K))
這個值。假如將來有一種計算機的計算能力得到集合級別的提升,有一定可能暴力破解橢圓曲線演算法。解決方案就是引入 RIPEMD160(SHA256(K))
的過程,這樣要破解一個未花費的UTXO,需要逆向RIPEMD160,SHA256,secp256k1三種不同的演算法,即使將來量子計算髮展到實用階段,也很難做到吧。
但是根據比特幣交易的設計,一個地址重複使用就會暴露公鑰K,所以我們推薦的安全做法就是一筆UTXO花費後就更換地址。這也是所有安全錢包的預設實現方法。
這個設計的唯一的瑕疵,在我看來,就是RIPEMD160將公鑰的碰撞空間減小了,由 2 256 減小到了 2 160 ,當然 2 160 的碰撞空間對於現有計算能力也是個天文數字,我想中本聰沒有選擇SHA3等演算法的原因,應該是充分考慮了雜湊演算法的複雜度和差異度,最後選擇的RIPEMD160吧。
Base58Check編碼
WIF格式和比特幣地址都是用Base58Check編碼表示的,Base58是Base64基礎上發展來的,它具有以下功能:
- 一個任意大小的payload。
- 一組58個字母數字符號,由易於區分的大小寫字母組成(不使用0OIl)
- 一個位元組的版本/應用程式資訊。比特幣地址為這個位元組使用0x00
- 四個位元組(32位)基於SHA256的錯誤檢查程式碼。此程式碼可用於自動檢測並可能糾正排版錯誤。
- 保留資料中零開頭的額外步驟
建立過程:
-
獲取版本位元組和payload位元組,並將它們連線在一起(按位元組順序)。
-
取SHA256(SHA256(步驟1的結果))的前四個位元組
-
將步驟1的結果和步驟2的結果連在一起(按位元組順序)。
-
處理步驟3的結果 – 一系列位元組 – 作為單個大端序號,使用正常的數學步驟(bignumber division)和下面描述的base-58字母表轉換為base-58。結果應該被標準化為沒有任何前導零(字元’1’)的base-58。
-
在base58中,值為零的前導字元’1’被保留用於表示整個前導零位元組,就像它處於前導位置時一樣,沒有值作為base-58符號。必要時可以有一個或多個前導’1’來表示一個或多個前導零位元組。計算步驟3結果中的前導零位元組數(對於舊的比特幣地址,至少有一個用於版本/應用程式位元組;對於新地址,將永遠不會有)。每個前導零位元組在最終結果中應由其自己的字元’1’表示。
-
將步驟5中的1與步驟4 的結果連線起來。這是Base58Check的結果。
最後綜合起來,從公鑰K到比特幣地址完整的示意圖如下:
引入Python程式碼計算一下 satoshi
作為seed的地址
def genaddress_from_pubk(compressed=True) # 首先計算 RIPEMD160(SHA256(K)) ripemd160 = encoding.public_pair_to_hash160_sec(public_pair, compressed=compressed) # 再用Base58Check計算最終地址 addr = encoding.hash160_sec_to_bitcoin_address(ripemd160) return addr
因為公鑰由壓縮形式和非壓縮兩種形式,所以完整的結果是:
seed: satoshi sha256: da2876b3eb31edb4436fa4650673fc6f01f90de2f1793c4ec332b2387b09726f compress address WIF: L4XnHhvLC1b4ag9L2PM9kRicQxUoYT1Q36PQ21YtLNkrAdWZNos6 hash160: 0a8ba9e453383d4561cbcdda36e5789c2870dd41 Bitcoin address:1xm4vFerV3pSgvBFkyzLgT1Ew3HQYrS1V uncompress address WIF: 5KUN8s42BCTkQVMTy3oFfqeXE8awVskbDi6XbDMpRnFvHJW9fgk hash160: 650d0497e014e60d4680fce6997d405de264f042 Bitcoin address:1ADJqstUMBB5zFquWg19UqZ7Zc6ePCpzLE
satoshi
作為seed生成了兩個地址:
1xm4vFerV3pSgvBFkyzLgT1Ew3HQYrS1V
和 1ADJqstUMBB5zFquWg19UqZ7Zc6ePCpzLE
,這都是兩個正在使用的地址哦,到今天為止還有熱心人源源不斷的為 1ADJqstUMBB5zFquWg19UqZ7Zc6ePCpzLE
轉賬一些零錢作為中本聰的紀念。你可以將WIF匯入錢包參與這兩個地址的抽獎哦。
一些Base58Check版本字首和編碼後的結果
看到這裡,我們發現Base58Check 編碼的過程種,最後一步會引入一個字首。而在比特幣中,除了WIF私鑰和地址,大多數需要向用戶展示的資料都使用Base58Check編碼,理所當然的,引入了不同的字首來區分不同的資訊,下面展示了一些版本字首和他們對應的Base58格式:
Type | Version prefix | Base58 result prefix |
---|---|---|
Bitcoin Address | 0x00 | 1 |
Pay to Script Hash Address | 0x05 | 3 |
Bitcoin Testnet Address | 0x5F | m or n |
Private Key WIF | 0x80 | 5, K or L |
BIP-38 Encrypted Private Key | 0x0142 | 6P |
BIP-32 Encrypted public Key | 0x0488B21E | xpub |
除了我們已經提到的WIF和Bitcoin Address,我們還發現了奇怪的BIP-38和BIP-32,這個需要到解釋比特幣原始交易編碼的時候來講解。
Brain Wallet
好啦,我們上面已經完整的再現了由一個 seed單詞 satoshi
,匯出兩個比特幣地址的過程;你只要記好 satoshi
這個單詞,就可以在世界上的任何地方,任何時間,掌管傳送給 1xm4vFerV3pSgvBFkyzLgT1Ew3HQYrS1V
和 1ADJqstUMBB5zFquWg19UqZ7Zc6ePCpzLE
這兩個地址的比特幣了。
你不需要銀行賬號,不需要密保卡,只需要有個可以聯網的地方,就能祕密傳送交易啦。將來有了免費的衛星網路的話,我想你都不用登陸網際網路,可能在家裡屋頂上樹個鍋,買個專用裝置,要轉帳的時候,只要輸入 satoshi
就可以祕密完成數億美元的匯款,而且絲毫不用擔心這個賬戶被政府查封,世界上也只有你一個人能動用這個賬戶,真是完美的洗錢逃匯工具!也難怪有人說,比特幣是人類歷史上第一次用技術手段保證了財產的完全私有權。
這個 satoshi
的seed,就是所謂腦錢包的口令,相對於天書一般的256bits私鑰,早期這樣的工具非常受歡迎,到現在為止你也可以到這個 線上工具 去重複我們以上推導的所有過程。
但是這個方法有個致命的弱點,他的安全性完全取決於seed這個單詞的複雜度,像 satoshi
這樣的seed,就像 123456
的密碼一樣,不用說大家都知道安全度為0啊。
而且你自以為選取生日啊、姓名縮寫啊、戀人海誓山盟的話語啊,這些東西作為seed,其實也是非常脆弱的。總有人孜孜不倦的遍歷所有可能的seed。這在後期導致了非常多的hack事件。
截至2018-10,我檢索區塊鏈,統計公開的比特幣地址已經超過了3億個,如果有萬分之一的地址是由腦錢包生成的話,不安全的地址也超過了3w個,所以後來腦錢包這種方式就不被推薦了。
下面可以列舉一些已經公開的seed,這都是我用一些公開語料庫隨意碰撞出來的,你就知道這種方法的危險性啦:
FINAL_CRACK_ADDRESS: hash160:sha256:seed:address:wif-priv FINAL_CRACK_ADDRESS:0a8ba9e453383d4561cbcdda36e5789c2870dd41:c:sha256:satoshi:1xm4vFerV3pSgvBFkyzLgT1Ew3HQYrS1V:L4XnHhvLC1b4ag9L2PM9kRicQxUoYT1Q36PQ21YtLNkrAdWZNos6 FINAL_CRACK_ADDRESS:650d0497e014e60d4680fce6997d405de264f042:u:sha256:satoshi:1ADJqstUMBB5zFquWg19UqZ7Zc6ePCpzLE:5KUN8s42BCTkQVMTy3oFfqeXE8awVskbDi6XbDMpRnFvHJW9fgk FINAL_CRACK_ADDRESS:c71e3a0989754d4ffae45a1c6ef8e348539cd83c:u:sha256:satoshinakamoto:1K9qgN3H2wB2v3LwJEBDbRRJ3znHXEQP4Y:5HqE1vZMMLc7jZRF5wZb79QexyCguNeNdaHLdKTGndvLBrCHD31 FINAL_CRACK_ADDRESS:ec42ad7fd54f931274b83f6137379206e458b106:u:sha256:1satoshi:1NYEM85RpgkSofLqDfwjb21o3MD4ibSo49:5JSGPQ2Jw1P5cVi2L8LeuWnMF5H8rLGrPPgVM2XE1cahG1BQDzY FINAL_CRACK_ADDRESS:fd8d22e02b3a41bc38f69516c43f7ebd6268e16b:u:sha256:satoshi nakamoto:1Q7f2rL2irjpvsKVys5W2cmKJYss82rNCy:5K7EWwEuJu9wPi4q7HmWQ7xgv8GxZ2KqkFbjYMGvTCXmY22oCbr FINAL_CRACK_ADDRESS:0000d85a71f305a1c907cdc7437c43b2eecc35e5:u:sha256:CHARINA143:11121ioKu4MCB1LLzPF98AVtzFsEg7UYKm:5JhayTrDhzDHqCg2v16Y2gZWi4kWF6BFoZR3MyaaxWtyKHzKJ8d FINAL_CRACK_ADDRESS:0002439f087ffefb973c5b9bbd52f509984d3cbe:c:sha256:purple99:113iKJcZeRxEYRVcWNgVxmjodPAisDZ45:KxFLoyseSyVtKGXQNkYBajT8EqviQSuH64Y6GE1g6KXuvcFaSrqc FINAL_CRACK_ADDRESS:00027a0ef2b9295011b10b089c8caf2e69f6b6f7:u:sha256:bridesmaid:113y6FCRm2WHh5Aru2R7ot37wkNDtxqf3:5JsYc8oXiHBJm9rzRPJHaCSYBBx7KCTx5L81rqRMZBArsdZTsan FINAL_CRACK_ADDRESS:00030fa763130b5310afe68b12204009e60e935c:u:sha256:masturbating:114fh9qiRhubJJDV5rCthFxmGyYeLQ2B6:5J8TgeF9jU3BPRrsCD75Ks4a6aWxgzLKENDSbsLufDTWq7evCQa FINAL_CRACK_ADDRESS:0005736b486f87e5823909a89eb48dda185d3956:u:sha256:nitro:117XjTn3UdBNjVo3KsB17WRFDDmcW2pPa:5JkUWsPzNEZEvMFwWQjoXCeem5LL6LDcC7K6H1LWiwXFAA9LFo1 FINAL_CRACK_ADDRESS:00080beae5c3a433fbae8ff0b69e705ac3ce5464:u:sha256:resultantly:11Ae5tbiSZ7QJWh4okWJhDKuPfAvk3a13:5KftjbtsSmhy5Y42AYpfEAm4U4vQKP9VhjbS6xtFqBDhUfAGaFj FINAL_CRACK_ADDRESS:00083c18c738e883ebc1b5ca270569ee8f9f790e:u:sha256:suggestively:11AsAKWdMZj5ScSYRoAQ8xG4J7Y6s7cW9:5JvrusiSgSmCTV7x7f5vgdWUMLjwk153j9UXmNHvRAJ8m7rtsJD FINAL_CRACK_ADDRESS:000e4d1774a3fc0251e9e6caf0a8617639e80093:u:sha256:dezoxiribonukleinsav:11J8fK9qhgxdQ96ZvvXExkfFvQSPQiPKN:5KSkRxaAbk94wzzzynDNj2bzRFmJZXCrhtYj3iGU9JjUxnRXTYM FINAL_CRACK_ADDRESS:00135d1c8f99cc657ad1f246bc5051ad03f95d32:u:sha256:Mussolini:11QCR3sk9r4jeyMqCKGEYabGTfjzhgGdZ:5K2x6UanMSevNX7f19oB4to46C3zXoGTUBGVqv8WNtCRwJNGBGC FINAL_CRACK_ADDRESS:001ed6fae0af0b37126004029defcc4521b300dd:u:sha256:meagerness:11dwnVzCyGoMZcGDndQteWgR9b7FKsJMu:5JKVJhbZWXmHxj2MuttZCDaFk7TC9KBVYjbPRjztP63mmAUV6Vm FINAL_CRACK_ADDRESS:002607c11a2311825a087f37c95d7816e0491a9d:u:sha256:vertebrate:11nZPfxYPeDm4d4fd93BaFa1BezFRTP6F:5HsBbgzEXZEaeLRCZHs66ho2ekpFEqeAJyyBPe8YyMkCqCHWv6j ...
不過我們現在已經掌握到了比特幣地址的生成原理,所以如何提高安全性就不用我再囉嗦了,相信你心中已有答案。
那些山寨幣們
比特幣專案是2019-01-03正式開始執行的,之後簡單的複製一下比特幣的程式碼,稍作修改就推出的山寨幣們數不勝數;這些山寨幣第一個要修改的,就是地址格式,以免和比特幣地址混淆;
怎麼修改呢?注意到前面Base58Check Encode的最後一步了嗎?那個時候我們需要引入一個字首作為地址的區分;得益於比特幣這種前瞻性的設計,山寨幣們只要改動一下這個字首就可以了,下面列舉一下我所知道的山寨幣的實現:
引用自: https://github.com/walletgeneratornet/WalletGenerator.net
name, networkVersion, privateKeyPrefix, WIF_Start, CWIF_Start "2GIVE",0x27, 0xa7, "6","R" "42coin",0x08, 0x88, "5","M" "Acoin",0x17, 0xe6, "8","b" "AGAcoin",0x53, 0xd3, "8","Y" "Alphacoin",0x52, 0xd2, "8","Y" "Alqo",0x17, 0xc1, "7","V" "Animecoin",0x17, 0x97, "6","P" "Anoncoin",0x17, 0x97, "6","P" "Apexcoin",0x17, 0x97, "6","P" "Auroracoin",0x17, 0x97, "6","T" "Aquariuscoin",0x17, 0x97, "6","P" "Axe",0x4B, 0xCB, "7","X" "BBQcoin",0x55, 0xd5, "6","T" "Biblepay",0x19, 0xb6, "7","[TU]" "Bitcoin",0x00, 0x80, "5","[LK]" "BitcoinCash",0x00, 0x80, "5","[LK]" "BitcoinDark",0x3c, 0xbc, "7","U" "Bitcore",0x00, 0x80, "5","[LK]" "BitcoinGold",0x26, 0x80, "5","[LK]" "Bitconnect",0x12, 0x92, "5","N" "Birdcoin",0x2f, 0xaf, "6","[ST]" "BitSynq",0x3f, 0xbf, "7","V" "BitZeny",0x51, 0x80, "5","[LK]" "Blackcoin",0x19, 0x99, "6","P" "BlackJack",0x15, 0x95, "[56]", "P" "BlockNet",0x1a, 0x9a, "6","P" "BolivarCoin",0x55, 0xd5, "8","Y" "BoxyCoin",0x4b, 0xcb, "7","X" "BunnyCoin",0x1a, 0x9a, "6","P" "Cagecoin",0x1f, 0x9f, "6","Q" "CampusCoin",0x1c, 0x9c, "6","Q" "CanadaeCoin",0x1c, 0x9c, "6","Q" "CannabisCoin",0x1c, 0x9c, "6","Q" "Capricoin",0x1c, 0x9c, "6","Q" "CassubianDetk",0x1e, 0x9e, "6","Q" "CashCoin",0x22, 0xa2, "6","[QR]" "Catcoin",0x15, 0x95, "[56]", "P" "ChainCoin",0x1c, 0x9c, "6","Q" "ColossusCoinXT",0x1e, 0xd4, "5","[LK]" "Condensate",0x3c, 0xbc, "7","U" "Copico",0x1c, 0x90, "5","N" "CopperCoin",0x1c, 0x9c, "6","Q" "Corgicoin",0x1c, 0x9c, "6","Q" "CryptoBullion",0x0b, 0x8b, "5","M" "CryptoClub",0x23, 0xa3, "6","R" "Cryptoescudo",0x1c, 0x9c, "6","Q" "Cryptonite",0x1c, 0x80, "5","[LK]" "CryptoWisdomCoin",0x49, 0x87, "5","[LM]" "C2coin",0x1c, 0x9c, "6","Q" "Dash",0x4c, 0xcc, "7","X" "DeafDollars",0x30, 0xb0, "6","T" "DeepOnion",0x1f, 0x9f, "6","Q" "Deutsche eMark",0x35, 0xb5, "7","T" "Devcoin",0x00, 0x80, "5","[LK]" "DigiByte",0x1e, 0x9e, "6","Q" "Digitalcoin",0x1e, 0x9e, "6","Q" "Dimecoin",0x0f, 0x8f, "5","N" "DNotes",0x1f, 0x9f, "6","Q" "Dogecoin",0x1e, 0x9e, "6","Q" "DogecoinDark",0x1e, 0x9e, "6","Q" "eGulden",0x30, 0xb0, "6","T" "eKrona",0x2d, 0xad, "6","S" "ELECTRA",0x21, 0xa1, "6","Q" "Ember",0x5c, 0x32, "2","8" "Emerald",0x22, 0xa2, "6","[QR]" "Emercoin",0x21, 0x80, "5","[LK]" "EnergyCoin",0x5c, 0xdc, "8","Z" "Espers",0x21, 0x90, "5","N" "Fastcoin",0x60, 0xe0, "8","a" "Feathercoin",0x0e, 0x8e, "5","N" "Fedoracoin",0x21, 0x80, "5","[KL]" "Fibre",0x23, 0xa3, "6","R" "Florincoin",0x23, 0xb0, "6","T" "Flurbo",0x23, 0x30, "6","8" "Fluttercoin",0x23, 0xa3, "6","R" "FrazCoin",0x23, 0xA3, "6","R" "Freicoin",0x00, 0x80, "5","[LK]" "FUDcoin",0x23, 0xa3, "6","R" "Fuelcoin",0x24, 0x80, "5","[KL]" "Fujicoin",0x24, 0xa4, "6","R" "GabenCoin",0x10, 0x90, "5","N" "Garlicoin",0x26, 0xb0, "6","T" "GlobalBoost",0x26, 0xa6, "6","R" "Goodcoin",0x26, 0xa6, "6","R" "GridcoinResearch",0x3e, 0xbe, "7","V" "Gulden",0x26, 0xa6, "6","R" "Guncoin",0x27, 0xa7, "6","R" "HamRadioCoin",0x00, 0x80, "5","LK" "HFRcoin",0x10, 0x90, "5","N" "HOdlcoin",0x28, 0xa8, "5","[LK]" "HTMLCoin",0x29, 0xa9, "6","S" "HyperStake",0x75, 0xf5, "9","d" "ImperiumCoin",0x30, 0xb0, "6","T" "IncaKoin",0x35, 0xb5, "7","T" "IncognitoCoin",0x00, 0x80, "5","LK" "Influxcoin",0x66, 0xe6, "8","b" "Innox",0x4b, 0xcb, "7","X" "IridiumCoin",0x30, 0xb0, "6","T" "iCash",0x66, 0xcc, "7","X" "iXcoin",0x8a, 0x80, "5","[LK]" "Judgecoin",0x2b, 0xab, "6","S" "Jumbucks",0x2b, 0xab, "6","S" "KHcoin",0x30, 0xb0, "6","T" "KittehCoin",0x2d, 0xad, "6","S" "Lanacoin",0x30, 0xb0, "6","T" "Latium",0x17, 0x80, "5","[LK]" "LBRY Credits",0x55, 0x80, "5","[LK]" "Litecoin",0x30, 0xb0, "6","T" "LiteDoge",0x5a, 0xab, "6","S" "LoMoCoin",0x30, 0xb0, "6","T" "MadbyteCoin",0x32, 0x6e, "4","H" "MagicInternetMoney",0x30, 0xb0, "6","T" "Magicoin",0x14, 0x94, "5","[NP]" "Marscoin",0x32, 0xb2, "6","T" "MarteXcoin",0x32, 0xb2, "6","T" "MasterDoge",0x33, 0x8b, "5","M" "Mazacoin",0x32, 0xe0, "8","a" "Megacoin",0x32, 0xb2, "6","T" "MintCoin",0x33, 0xb3, "[67]", "T" "MobiusCoin",0x00, 0x80, "5","[LK]" "MonetaryUnit",0x10, 0x7e, "5","K" "Monocle",0x32, 0xb2, "6","T" "MoonCoin",0x03, 0x83, "5","L" "Myriadcoin",0x32, 0xb2, "6","T" "NameCoin",0x34, 0x80, "5","[LK]" "Navcoin",0x35, 0x96, "6","P" "NeedleCoin",0x35, 0xb5, "7","T" "NEETCOIN",0x35, 0xb5, "7","T" "NYC",0x3c, 0xbc, "7","U" "Neoscoin",0x35, 0xb1, "6","T" "Nevacoin",0x35, 0xb1, "6","T" "Novacoin",0x08, 0x88, "5","M" "Nubits",0x19, 0xbf, "7","V" "Nyancoin",0x2d, 0xad, "6","S" "Ocupy",0x73, 0xf3, "9","[cd]" "Omnicoin",0x73, 0xf3, "9","[cd]" "Onyxcoin",0x73, 0xf3, "9","[cd]" "PacCoin",0x18, 0x98, "6","P" "Particl",0x38, 0x6c, "4","[HG]" "Paycoin",0x37, 0xb7, "7","U" "Pandacoin",0x37, 0xb7, "7","U" "ParkByte",0x37, 0xb7, "7","U" "Peercoin",0x37, 0xb7, "7","U" "Pesetacoin",0x2f, 0xaf, "6","[ST]" "PHCoin",0x37, 0xb7, "7","U" "PhoenixCoin",0x38, 0xb8, "7","U" "PiggyCoin",0x76, 0xf6, "9","d" "Pinkcoin",0x3,0x83, "[RQP]","L" "PIVX",0x1e, 0xd4, "8","Y" "Peercoin",0x37, 0xb7, "7","U" "Potcoin",0x37, 0xb7, "7","U" "Primecoin",0x17, 0x97, "6","P" "ProsperCoinClassic",0x3a, 0xba, "7","Q" "Quark",0x3a, 0xba, "7","U" "Qubitcoin",0x26, 0xe0, "8","a" "Reddcoin",0x3d, 0xbd, "7","[UV]" "Riecoin",0x3c, 0x80, "5","[LK]" "Rimbit",0x3c, 0xbc, "7","U" "ROIcoin",0x3c, 0x80, "5","[LK]" "Rubycoin",0x3c, 0xbc, "7","U" "Rupaya",0x3c, 0xbc, "7","U" "Sambacoin",0x3e, 0xbe, "7","V" "SecKCoin",0x3f, 0xbf, "7","V" "SibCoin",0x3f, 0x80, "5","[LK]" "SixEleven",0x34, 0x80, "5","[LK]" "SmileyCoin",0x19, 0x99, "6","P" "SongCoin",0x3f, 0xbf, "7","V" "SpreadCoin",0x3f, 0xbf, "7","V" "StealthCoin",0x3e, 0xbe, "7","V" "Stratis",0x3f, 0xbf, "7","V" "SwagBucks",0x3f, 0x99, "6","P" "Syscoin",0x00, 0x80, "5","[LK]" "Tajcoin",0x41, 0x6f, "6","H" "Terracoin",0x00, 0x80, "5","[LK]" "Titcoin",0x00, 0x80, "5","[LK]" "TittieCoin",0x41, 0xc1, "7","V" "Topcoin",0x42, 0xc2, "7","V" "TransferCoin",0x42, 0x99, "6","P" "TreasureHuntCoin",0x32, 0xb2, "6","T" "TrezarCoin",0x42, 0xC2, "7","V" "Unobtanium",0x82, 0xe0, "8","a" "USDe",0x26, 0xa6, "6","R" "Vcash",0x47, 0xc7, "7","W" "Versioncoin",0x46, 0xc6, "7","W" "VergeCoin",0x1e, 0x9e, "6","Q" "Vertcoin",0x47, 0x80, "5","[LK]" "Viacoin",0x47, 0xc7, "7","W" "VikingCoin",0x46, 0x56, "3","D" "W2Coin",0x49, 0xc9, "7","W" "WACoins",0x49, 0xc9, "7","W" "WankCoin",0x00, 0x80, "5","[LK]" "WeAreSatoshiCoin",0x87, 0x97, "6","P" "WorldCoin",0x49, 0xc9, "7","W" "XP",0x4b, 0xcb, "7","X" "Yenten",0x4e, 0x7b, "5","K" "Zcash",[0x1c,0xb8], 0x80, "5","[LK]" "Zetacoin",0x50, 0xE0, "8","a" "Testnet Bitcoin",0x6f, 0xef, "9","c" "Testnet Dogecoin",0x71, 0xf1, "9","c" "Testnet MonetaryUnit",0x26, 0x40, "3","A" "Testnet PIVX",0x8b, 0xef, "9","c" "Testnet WACoins",0x51, 0xd1, "8","[XY]"
哈哈,洋洋大觀啊。這也說明了folk一個山寨幣的成本是如何的低;然後有了以太坊的ERC20之後,發一個新幣的成本簡直低到令人髮指,也無怪乎場子裡面騙子橫行了。
以太坊的地址生成
Ethereum專案是不走尋常路的,他作為比特幣之後最具創新性的後輩,地址設計反而簡單的多。
直到生成公鑰這一步,以太坊和比特幣都是一致的,採用了secp256k1演算法,只是最後的地址生成以太坊很簡潔,直接Keccak256 hash,然後取最後的40位16進位制字元得到的。
為什麼比特幣實現複雜呢?這是因為比特幣的交易是以UTXO為核心的,每個UTXO包含其所有者及價值資訊,系統中的每一筆的交易由若干UTXO輸入和若干UTXO輸出組成。UTXO無法只提取部分,每次必須完整的使用,這有點像我們生活現實中的現金。比特幣系統中,一個使用者的“餘額”是該使用者的私鑰能夠有效簽名的所有UTXO的總和。要深刻的理解這一點,還需要我們瞭解了比特幣的交易資料構成之後才能探討。我們後面會寫文章解釋這一點啦。
而以太坊採用了與比特幣不同的實現方式——賬戶,類似我們生活中的銀行卡。以賬戶為核心的設計比較節省空間,而且以太坊的block組織更為精巧。另外,以太坊的設計目標和比特幣是不同的:
- 首先以太坊的賬戶除了普通的收發幣的賬戶(俗稱外部賬戶EOA),還有合約賬戶,合約賬戶需要一個固定的地址,不然每次呼叫合約都會很麻煩;這樣就要求以太坊的合約賬戶不像比特幣交易那樣頻繁的更換地址;
- 他並不執著於強迫使用者去注意隱私問題,以太坊的態度是,如果使用者注重隱私問題,你就自己搞定;你需要通過合約中的簽名資料包協議來建立一個加密“混合器”進行加密。
- 總之以太坊因為要實現的目標更為巨集大,他的設計理念是根據最初的使用者都是一群Geek們來建立的;Geek們最喜歡啥?就是不要過度設計,讓我來自己搞定
所以在以太坊系統中,賬戶是一個20位元組的地址,包含四個部分:
-
隨機數,用於確定每筆交易只能被處理一次的計數器
-
賬戶目前的以太幣餘額
-
賬戶的合約程式碼,如果有的話
-
賬戶的儲存(預設為空)
可以採用 pyethereum 這個庫,用以下程式碼模擬以太坊地址的生成:
# -*- coding: utf-8 -*- """doctopt ethereum address generate tools Usage: genaddr.py p2addr<private> genaddr.py word2addr<word> Options: -h --helpShow this screen. --versionShow version. Example: genaddr.py p2addr 6bd3b27c591# gen address from private 0x6bd3b27c591=>1PiFuqGpG8yGM5v6rNHWS3TjsG6awgEGA1 genaddr.py word2addr 'Money is the root of all evil.'# gen address from private wordlist """ from docopt import docopt import os import sys from ethereum import utils if __name__ == '__main__': arguments = docopt(__doc__, version='1.0') if arguments['p2addr']: private_key = bytes.fromhex(arguments['<private>']) passpharse = b'unknown' elif arguments['word2addr']: passpharse = arguments['<word>'].encode('utf-8') private_key = utils.sha3(passpharse) raw_address = utils.privtoaddr(private_key) account_address = utils.checksum_encode(raw_address) print("word:{}:private:{}:address:{}".format(passpharse.decode('utf-8'), private_key.hex(), account_address))
> python genaddr.py word2addr 'hello' word:hello:private:1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8:address:0x5ccfa55C29F0522f062E3C15004E35a69dD45F6B
以太坊賬戶方式的一個弱點是:為了阻止重放攻擊,每筆交易必須有nonce。這就使得賬戶需要跟蹤nonce的使用情況。而且,不再使用的賬戶,無法從賬戶狀態中移除。
關於重放攻擊,我們會在後面說明。
Bitcoin Cash地址生成
待續
重放攻擊
待續
總結
以上長篇大論了比特幣系統的地址是如何生成的,當然我們也略過了許多細節,比如錢包如何加salt,如何加passphrase等等,這些直接去讀BIP 規範更為精確;但是一個完整的錢包,可不僅僅是要解決地址生成這個問題,還要能方便的管理私鑰。
在比特幣早期,私鑰的管理是非常粗暴的,就是每次建立新錢包時,系統自動隨機生成100個私鑰,然後隨著使用者交易增多用光之後,再生成100個私鑰;錢包檔案就是一個二進位制檔案,即使加了密碼保護,也很容易暴力破解洩密;
這導致了無窮多的hack事件;不管你信不信,初期很多從事比特幣交易的網站,其wallet.dat檔案就明晃晃的放在伺服器上,管理員粗心大意,完全可以形容為 沒頭腦+不高興
,很多人連個最基本的密碼保護也不設定;另外也發生過很多悲劇的 rm -rf
事件,我認為由於這樣的失誤導致的比特幣丟失至少在100w+ 幣的級別;換算今天的匯率,你能相信有個銀行將數億美元現金的保險櫃不加鎖,明晃晃的擺在大堂上擺闊嗎?
在一連串的悲劇事件中(具體是哪些悲劇可以寫一本書哦),作為登峰造極者,mtgox當之無愧! 80多萬個比特幣的丟失,史上獨一份。這個交易所的老闆也是心大,80w+幣的錢包密碼也不設定一個,就在那裡任由黑客予取予求;還不是一天兩天哦,是持續好幾周的hack事件!
mtgox是比特幣歷史上巨大的迷霧,他不光牽扯到許多比特幣的早期玩家,還有BTC-E, FBI牽涉其中,我認為這是僅次於 中本聰到底何方神聖
的謎題。所幸法胖還活著,我希望有生之年能讀到這個事件的完整披露。
好了,假如你是一個交易所的老闆,你會很快發現自己面臨著以下問題:
- 不需要給每個使用者的賬戶都建立一個錢包檔案,我希望能有一個總的賬戶管理方案
- 可能交易所有1000個大戶,你希望他們的錢包是冷儲存的,提幣的時候他們可以耐心等一段時間,但是剩下的100000個普通使用者的賬戶就要存放到一個熱錢包上,只留有部分資產來應付流動性
- 有很多部門需要批准獲取一些資金,比如研發要用啦做測試,市場部門要用來搞活動等等
- 最後,私鑰最好只能由少數人,最好只有我本人來掌握,不然私鑰的傳播過程中,隨便一個人就能讓你萬劫不復
- 我如果有一些合夥人的話,肯定也希望能掌管一部分資金
- 最後,如果有突發情況,我能迅速把公司賬上所有的幣都轉移到另外一個安全的賬戶上,這有可能是要迅速完成上萬筆的交易轉移
好啦,假如我們現在只有前面那種一個wallet.dat錢包的管理方案,要怎麼做呢?
很明顯的,這種管理太粗糙了。社群們經過不斷的探索,提出了BIP-32,BIP-39,BIP-44等規範,以絕妙的辦法解決了這些問題。這就是比特幣HD錢包的由來。同時這些規範不僅僅適用於比特幣系統,還適用於所有的電子貨幣方案,也許今後,你可以同時在一個錢包裡管理你的ETH, BTC, 支付寶餘額等等~~~
那到底要怎麼做呢?
我們已經探索了這麼遠,估計你也不耐煩了,但是我們還要說,這還早的遠呢!那麼下次文章再見。