精通比特幣(第四章)【金鑰和地址】

風靈使發表於2018-09-30

你可能聽說過比特幣是基於密碼學,這一在電腦保安中廣泛使用的數學分支。 密碼學在希臘語中是“祕密寫作”的意思,但密碼學這門科學不僅只包含被稱之為祕密寫作的加密學。 密碼學也可以用來證明祕密的知識,而不會洩露祕密(數字簽名),或證明資料的真實性(數字指紋)。 這些型別的加密證明是比特幣中關鍵的數學工具並在比特幣應用程式中被廣泛使用。具有諷刺意味的是,加密不是比特幣的重要組成部分,因為它的通訊和交易資料沒有加密,也不需要加密來保護資金。 在本章中,我們將介紹一些在比特幣中用來控制資金的所有權的密碼學,包括金鑰,地址和錢包。

4.1簡介

比特幣的所有權是通過數字金鑰、比特幣地址和數字簽名來確定的。數字金鑰實際上並不儲存在網路中,而是由使用者生成之後,儲存在一個叫做錢包的檔案或簡單的資料庫中。儲存在使用者錢包中的數字金鑰完全獨立於比特幣協議,可由使用者的錢包軟體生成並管理,而無需參照區塊鏈或訪問網路。金鑰實現了比特幣的許多有趣特性,包括去中心化信任和控制、所 有權認證和基於密碼學證明的安全模型。

大多數比特幣交易都需要一個有效的簽名才會被儲存在區塊鏈。只有有效的金鑰才能產生有效的數字簽名,因此擁有~金鑰副本就擁有了對該帳戶的比特幣的控制權。用於支出資金的數字簽名也稱為見證(witness),密碼術中使用的術語。 比特幣交易中的見證資料證明了所用資金的真正歸誰所有。

金鑰是成對出現的,由一個私鑰和一個公鑰所組成。公鑰就像銀行的帳號,而私鑰就像控制賬戶的PIN碼或支票的簽名。比特幣的使用者很少會直接看到數字金鑰。一般情況下,它們被儲存在錢包檔案內,由比特幣錢包軟體進行管理。

在比特幣交易的支付環節,收件人的公鑰是通過其數字指紋代表的,稱為比特幣地址,就像支票上的支付物件的名字 (即“收款方”)。一般情況下,比特幣地址由一個公鑰生成並對應於這個公鑰。然而,並非所有比特幣地址都是公鑰; 他們也可以代表其他支付物件,譬如指令碼,我們將在本章後面提及。這樣一來,比特幣地址把收款方抽象起來了,使得 交易的目的地更靈活,就像支票一樣:這個支付工具可支付到個人賬戶、公司賬戶,進行賬單支付或現金支付。比特幣地址是使用者經常看到的金鑰的唯一代表,他們只需要把比特幣地址告訴其他人即可。

首先,我們將介紹密碼學並解釋在比特幣中使用的數學知識。然後我們將瞭解金鑰如何被產生、儲存和管理。我們將回顧用於代表私鑰和公 鑰、地址和指令碼地址的各種編碼格式。最後,我們將講解金鑰和地址的高階用途:比特幣靚號,多重簽名以及指令碼地址和紙錢包。

4.1.1 公鑰加密和加密貨幣

公鑰加密發明於20世紀70年代。它是計算機和資訊保安的數學基礎。

自從公鑰加密被髮明之後,一些合適的數學函式被發現,譬如:素數冪和橢圓曲線乘法。這些數學函式都是不可逆的, 就是說很容易向一個方向計算,但不可以向相反方向倒推。基於這些數學函式的密碼學,使得生成數字金鑰和不可偽造 的數字簽名成為可能。比特幣正是使用橢圓曲線乘法作為其公鑰加密的基礎。

在比特幣系統中,我們用公鑰加密建立一個金鑰對,用於控制比特幣的獲取。金鑰對包括一個私鑰,和由其衍生出的唯 一的公鑰。公鑰用於接收比特幣,而私鑰用於比特幣支付時的交易簽名。

公鑰和私鑰之間的數學關係,使得私鑰可用於生成特定訊息的簽名。此簽名可以在不洩露私鑰的同時對公鑰進行驗證。

支付比特幣時,比特幣的當前所有者需要在交易中提交其公鑰和簽名(每次交易的簽名都不同,但均從同一個私鑰生 成)。比特幣網路中的所有人都可以通過所提交的公鑰和簽名進行驗證,並確認該交易是否有效,即確認支付者在該時刻對所交易的比特幣擁有所有權。

提示 大多數比特幣錢包工具為了方便會將私鑰和公鑰以金鑰對的形式儲存在一起。然而,公鑰可以由私鑰計算得到, 所以只儲存私鑰也是可以的。

4.1.2 私鑰和公鑰

一個比特幣錢包中包含一系列的金鑰對,每個金鑰對包括一個私鑰和一個公鑰。私鑰(k)是一個數字,通常是隨機選出的。有了私鑰,我們就可以使用橢圓曲線乘法這個單向加密函式產生一個公鑰(K)。有了公鑰(K),我們就可以使 用一個單向加密雜湊函式生成比特幣地址(A)。在本節中,我們將從生成私鑰開始,講述如何使用橢圓曲線運算將私 鑰生成公鑰,並最終由公鑰生成比特幣地址。私鑰、公鑰和比特幣地址之間的關係如下圖所示。

圖4-1私鑰、公鑰和比特幣地址之間的關係

為什麼使用非對稱加密(公鑰/私鑰)?

為什麼在比特幣中使用非對稱密碼術? 它不是用於“加密”(make secret)交易。 相反,非對稱密碼學的有用屬性是生成數字簽名的能力。 可以將私鑰應用於交易的數字指紋以產生數字簽名。 該簽名只能由知曉私鑰的人生成。 但是,訪問公鑰和交易指紋的任何人都可以使用它們來驗證簽名。 這種非對稱密碼學的適用性使得任何人都可以驗證每筆交易的每個簽名,同時確保只有私鑰的所有者可以產生有效的簽名。

4.1.3 私鑰

私鑰就是一個隨機選出的數字而已。一個比特幣地址中的所有資金的控制取決於相應私鑰的所有權和控制權。在比特幣交易中,私鑰用於生成支付比特幣所必需的簽名以證明對資金的所有權。私鑰必須始終保持機密,因為一旦被洩露給第三 方,相當於該私鑰保護之下的比特幣也拱手相讓了。私鑰還必須進行備份,以防意外丟失,因為私鑰一旦丟失就難以復原,其所保護的比特幣也將永遠丟失。

提示 比特幣私鑰只是一個數字。你可以用硬幣、鉛筆和紙來隨機生成你的私鑰:擲硬幣256次,用紙和筆記錄正反面並轉換為0和1,隨機得到的256位二進位制數字可作為比特幣錢包的私鑰。該私鑰可進一步生成公鑰。

從一個隨機數生成私鑰
生成金鑰的第一步也是最重要的一步,是要找到足夠安全的熵源,即隨機性來源。生成一個比特幣私鑰在本質上與“在1 到2^256之間選一個數字”無異。只要選取的結果是不可預測或不可重複的,那麼選取數字的具體方法並不重要。比特幣軟體使用作業系統底層的隨機數生成器來產生256位的熵(隨機性)。通常情況下,作業系統隨機數生成器由人工的隨機源進行初始化,這就是為什麼也可能需要不停晃動滑鼠幾秒鐘。

更準確地說,私鑰可以是1和n-1之間的任何數字,其中n是一個常數(n=1.158 * 10^77,略小於2^256),並被定義為由比特幣所使用的橢圓曲線的階(見橢圓曲線密碼學解釋)。要生成這樣的一個私鑰,我們隨機選擇一個256位的數字,並檢查它是否小於n-1。從程式設計的角度來看,一般是通過在一個密碼學安全的隨機源中取出一長串隨機位元組,對其使用SHA256雜湊演算法進行運算,這樣就可以方便地產生一個256位的數字。如果運算結果小於n-1,我們就有了一個合適的私鑰。否則,我們就用另一個隨機數再重複一次。

警告 不要自己寫程式碼或使用你的程式語言提供的簡易隨機數生成器來獲得一個隨機數。使用密碼學安全的偽隨機數生成器(CSPRNG),並且需要有一個來自具有足夠熵值的源的種子。使用隨機數發生器的程式庫時,需仔細研讀其文件,以確保它是加密安全的。正確實施CSPRNG是金鑰安全性的關鍵所在。

以下是一個隨機生成的私鑰(k),以十六進位制格式表示(256位的二進位制數,以64位十六進位制數顯示,每個十六進位制數佔4位):

1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD

提示比特幣私鑰空間的大小是2^256,這是一個非常大的數字。用十進位制表示的話,大約是10^77,而可見宇宙被估計只含有10^80個原子。

要使用比特幣核心客戶端生成一個新的金鑰,可使用 getnewaddress 命令。出於安全考慮,命令執行後只 顯示生成的公鑰,而不顯示私鑰。如果要bitcoind顯示私鑰,可以使用 dumpprivkey 命令。 dumpprivkey 命令會把私鑰以 Base58校驗和編碼格式顯示,這種私鑰格式被稱為錢包匯入格式(WIFWallet Import Format),在“私鑰的格式”一節有詳細講解。下面給出了使用這兩個命令生成和顯示私鑰的例子:

$ bitcoin-cli getnewaddress
1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
$ bitcoin-cli dumpprivkey 1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ

dumpprivkey 命令開啟錢包提取由 getnewaddress 命令生成的私鑰。除非金鑰對都儲存在錢包裡,否則bitcoind的並不能從公鑰得知私鑰。 dumpprivkey 命令才有效。

提示 dumpprivkey命令無法從公鑰得到對應的私鑰,因為這是不可能的。這個命令只是顯示錢包中已有也就是由getnewaddress命令生成的私鑰。

您還可以使用Bitcoin Explorer命令列工具(請參閱附錄中的[appdx_bx])使用命令seedec-newec-to-wif生成和顯示私鑰:

$ bx seed | bx ec-new | bx ec-to-wif
5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn

4.1.4 公鑰

通過橢圓曲線乘法可以從私鑰計算得到公鑰,這是不可逆轉的過程:K = k * G 。其中k是私鑰,G是被稱為生成點的常數點,而K是所得公鑰。其反向運算,被稱為“尋找離散對數”——已知公鑰K來求出私鑰k——是非常困難的,就像去試驗所有可能的k值,即暴力搜尋。在演示如何從私鑰生成公鑰之前,我們先稍微詳細學習下橢圓曲線密碼學。

提示 橢圓曲線乘法是密碼學家稱之為“陷阱門”功能的一種函式:在一個方向(乘法)很容易做,而不可能在相反的方向(除法)做。 私鑰的所有者可以容易地建立公鑰,然後與世界共享,知道沒有人可以從公鑰中反轉函式並計算出私鑰。 這個數學技巧成為證明比特幣資金所有權的不可偽造和安全的數字簽名的基礎。

4.1.5 橢圓曲線密碼學(Elliptic Curve Cryptography)解釋

橢圓曲線加密法是一種基於離散對數問題的非對稱加密法,可以用對橢圓曲線上的點進行加法或乘法運算來表達。
下圖是一個橢圓曲線的示例,類似於比特幣所用的曲線。

圖4-2橢圓曲線的示例
比特幣使用了secp256k1標準所定義的一種特殊的橢圓曲線和一系列數學常數。該標準由美國國家標準與技術研究院 (NIST)設立。secp256k1曲線由下述函式定義,該函式可產生一條橢圓曲線:

產生橢圓曲線的函式

上述mod p(素數p取模)表明該曲線是在素數階p的有限域內,也寫作Fp,其中p = 2^256 – 2^32 – 2^9 – 2^8 – 2^7 – 2^6 – 2^4 – 1, 這是個非常大的素數。
因為這條曲線被定義在一個素數階的有限域內,而不是定義在實數範圍,它的函式影象看起來像分散在兩個維度上的散落的點,因此很難視覺化。不過,其中的數學原理與實數範圍的橢圓曲線相似。作為一個例子,下圖顯示了在一個小了很多的素數階17的有限域內的橢圓曲線,其形式為網格上的一系列散點。而secp256k1的比特幣橢圓曲線可以被想象成一個極大的網格上一系列更為複雜的散點。

圖4-3圖為:橢圓曲線密碼學F(p)上的橢圓曲線,其中p = 17

下面舉一個例子,這是 secp256k1 曲線上的點P,其座標為(x,y)。可以使用Python對其檢驗:

P = (55066263022277343669578718895168534326250603453777594175500187360389116729240, 32670510020758816978083085130507043184471273380659243275938904335757337482424)

在橢圓曲線的數學原理中,有一個點被稱為“無窮遠點”,這大致對應於0在加法中的作用。計算機中,它有時表示為X = Y = 0(雖然這不滿足橢圓曲線方程,但可作為特殊情況進行檢驗)。

還有一個 + 運算子,被稱為“加法”,就像小學數學中的實數相加。給定橢圓曲線上的兩個點P1和P2,則橢圓曲線上必定有第三點 P3 = P1 + P2
幾何圖形中,該第三點P3可以在P1和P2之間畫一條線來確定。這條直線恰好與橢圓曲線相交於另外一個地方。此點記為 P3'= (x,y)。然後,在x軸做翻折獲得 P3=(x,-y)

下面是幾個可以解釋“窮遠點”之存在需要的特殊情況。

若 P1和 P2是同點,P1和P2間的連線則為點P1 的切線。曲線上有且只有個新的點與該切線相交。該切線的斜率可用微積分求得。即使限制曲線點為兩個整數座標也可求得斜率!

在某些情況下(即,如果P1和P2具有相同的x值,但不同的y值),則切線會完全垂直,在這種情況下,P3 = “無窮遠點”。

若P1就是“無窮遠點”,那麼其和 P1 + P2= P2。類似地,當P2是無窮遠點,則P1+ P2 = P1。這就是把無窮遠點類似於0的作用。 事實證明,在這裡 + 運算子遵守結合律,這意味著(A+B)+C = A+(B+C)。這就是說我們可以直接不加括號書寫 A + B + C,而不至於混淆。 因此,我們已經定義了橢圓加法,我們可以對乘法用擴充加法的標準方法進行定義。給定橢圓曲線上的點P,如果k是整數,則 kP = P + P + P + …+ P(k次)。注意,在這種情況下k有時被混淆而稱為“指數”。

4.1.6 生成公鑰

以一個隨機生成的私鑰k為起點,我們將其與曲線上預定的生成點G相乘以獲得曲線上的另一點,也就是相應的公鑰 K。生成點是secp256k1標準的一部分,比特幣金鑰的生成點都是相同的:

 {K = k * G} 

其中k是私鑰,G是生成點,在該曲線上所得的點K是公鑰。因為所有比特幣使用者的生成點是相同的,一個私鑰k乘以G將 得到相同的公鑰K。k和K之間的關係是固定的,但只能單向運算,即從k得到K。這就是可以把比特幣地址(K的衍生) 與任何人共享而不會洩露私鑰(k)的原因。

提示 因為其中的數學運算是單向的,所以私鑰可以轉換為公鑰,但公鑰不能轉換回私鑰。

為實現橢圓曲線乘法,我們 以之前產生的私鑰k和與生成點G相乘得到公鑰K:

K = 1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD * G

公鑰K 被定義為一個點 K = (x, y)

 K = (x, y) 

其中,

x = F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A 
y = 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB

為了展示整數點的乘法,我們將使用較為簡單的實數範圍的橢圓曲線。請記住,其中的數學原理是相同的。我們的目標是找到生成點G的倍數kG。也就是將G相加k次。在橢圓曲線中,點的相加等同於從該點畫切線找到與曲線相交的另一 點,然後翻折到x軸。

下圖顯示了在曲線上得到 G、2G、4G 的幾何操作。

圖4-4曲線上 G、2G、4G 的幾何操作

提示大多數比特幣程式使用OpenSSL加密庫進行橢圓曲線計算。例如,呼叫EC_POINT_mul() 函式,可計算得到公鑰。

4.2 比特幣地址

比特幣地址是一個由數字和字母組成的字串,可以與任何想給你比特幣的人分享。由公鑰(一個同樣由數字和字母組 成的字串)生成的比特幣地址以數字“1”開頭。下面是一個比特幣地址的例子:

1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy

在交易中,比特幣地址通常以收款方出現。如果把比特幣交易比作一張支票,比特幣地址就是收款人,也就是我們要寫入收款人一欄的內容。一張支票的 收款人可能是某個銀行賬戶,也可能是某個公司、機構,甚至是現金支票。支票不需要指定一個特定的賬戶,而是用一個抽象的名字作為收款人,這使它成為一種相當靈活的支付工具。與此類似,比特幣地址使用類似的抽象,也使比特幣交易變得很靈活。比特幣地址可以代表一對公鑰和私鑰的所有者,也可以代表其 它東西,比如會在後面的“P2SH (Pay-to-Script-Hash)”一節講到的付款指令碼。現在,讓我們來看一個簡單的例子,由公鑰生成比特幣地址。

比特幣地址可由公鑰經過單向的加密雜湊演算法得到。雜湊演算法是一種單向函式,接收任意長度的輸入產生指紋或雜湊。加密雜湊函式在比特幣中被廣泛使用 :比特幣地址、指令碼地址以及在挖礦中的工作量證明演算法。由公鑰生成比特幣地址時使用的演算法是Secure Hash Algorithm (SHA)the RACE Integ rity Primitives Evaluation Message Digest (RIPEMD),具體地說是SHA256RIPEMD160

以公鑰 K 為輸入,計算其SHA256雜湊值,並以此結果計算RIPEMD160 雜湊值,得到一個長度為160位(20位元組)的數字:

A = RIPEMD160(SHA256(K))

公式中,K是公鑰,A是生成的比特幣地址。

提示比特幣地址與公鑰不同。比特幣地址是由公鑰經過單向的雜湊函式生成的。

通常使用者見到的比特幣地址是經過“Base58Check”編碼的(參見“Base58Base58Check編碼”一節),這種編碼 使用了58個字元(一種Base58數字系統)和校驗碼,提高了可讀性、避免歧義並有效防止了在地址轉錄和輸入中產生 的錯誤。Base58Check編碼也被用於比特幣的其它地方,例如比特幣地址、私鑰、加密的金鑰和指令碼雜湊中,用來提高可讀性和錄入的正確性。下一節中我們會詳細解釋Base58Check的編碼和解碼機制,以及它產生的結果。

下圖描述瞭如何從公鑰生成比特幣地址。

圖4-5從公鑰生成比特幣地址

4.2.1 Base58Base58Check編碼

為了更簡潔方便地表示長串的數字,使用更少的符號,許多計算機系統會使用一種以數字和字母組成的大於十進位制的表示法。例如,傳統的十進位制計數系統使用0-9十個數字,而十六進位制系統使用了額外的 A-F 六個字母。一個同樣的數字,它的十六進位制表 示就會比十進位制表示更短。甚至更加簡潔,Base64使用了26個小寫字母、26個大寫字母、10個數字以及兩個符號(例 如“+”和“/”),用於在電子郵件這樣的基於文字的媒介中傳輸二進位制資料。Base64通常用於編碼郵件中的附件。Base58 是一種基於文字的二進位制編碼格式,用在比特幣和其它的加密貨幣中。這種編碼格式不僅實現了資料壓縮,保持了易讀 性,還具有錯誤診斷功能。Base58Base64編碼格式的子集,同樣使用大小寫字母和10個數字,但捨棄了一些容易錯 讀和在特定字型中容易混淆的字元。具體地,Base58不含Base64中的0(數字0)、O(大寫字母o)、l(小寫字母 L)、I(大寫字母i),以及“+”和“/”兩個字元。簡而言之,Base58就是由不包括(0,O,l,I)的大小寫字母和數字組成。

例4-1 比特幣的Base58字母表

123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

為了增加防止列印和轉錄錯誤的安全性,Base58Check是一種常用在比特幣中的Base58編碼格式,比特幣有內建的檢查錯誤的編碼。檢驗和是新增到正在編碼的資料末端的額外4個位元組。校驗和是從編碼的資料的雜湊值中得到的,所以可以用來檢測並避免轉錄和輸入中產生的錯誤。使用Base58check編碼時,解碼軟體會計算資料的校驗和並和編碼中自帶的校驗和進行對比。二者不匹配則表明有錯誤產生,那麼這個Base58Check的資料就是無效的。一個錯誤比特幣地址就不會被錢包軟體認為是有效的地址,否則這種錯誤會造成資金的丟失。

為了將資料(數字)轉換成Base58Check格式,首先我們要對資料新增一個稱作“版本位元組”的字首,這個字首用來識別編碼的資料的類 型。例如,比特幣地址的字首是0(十六進位制是0x00),而對私鑰編碼時字首是128(十六進位制是0x80)。 表4-1會列出一些常見版本的字首。

接下來,我們計算“雙雜湊”校驗和,意味著要對之前的結果(字首和資料)執行兩次SHA256雜湊演算法:

checksum = SHA256(SHA256(prefix+data))

在產生的長32個位元組的雜湊值(兩次雜湊運算)中,我們只取前4個位元組。這4個位元組就作為檢驗錯誤的程式碼或者校驗和。校驗碼會新增到資料之後。

結果由三部分組成:字首、資料和校驗和。這個結果採用之前描述的Base58字母表編碼。下圖描述了Base58Check編 碼的過程。

圖4-6Base58Check編 碼的過程

在比特幣中,大多數需要向使用者展示的資料都使用Base58Check編碼,可以實現資料壓縮,易讀而且有錯誤檢驗。 Base58Check編碼中的版本字首是用來創造易於辨別的格式,在Base58裡的格式在Base58Check編碼的有效載荷的開始包含了明確的屬性。這些屬性使使用者可以輕鬆明確被編碼的資料的型別以及如何使用它們。例如我們可以看到他們的不同,Base58Check編碼的比特幣地址是以1開頭的,而Base58Check編碼的私鑰WIF是以5開頭的。表4-1展示了一些版本字首和他們對應的Base58格式。

表4-1 Base58Check版本字首和編碼後的結果

Base58Check版本字首和編碼後的結果

我們回顧比特幣地址產生的完整過程,從私鑰、到公鑰(橢圓曲線上某個點)、再到兩次雜湊的地址,到最終的 Base58Check編碼。例4-3的C++程式碼完整詳細的展示了從私鑰到Base58Check編碼後的比特幣地址的 步驟。程式碼中使用“3.3 其他客戶端、資料庫、工具包 ”一節中介紹的libbitcoin library來實現某些輔助功能。

例4-3.從私鑰中建立Base58Check編碼的比特幣地址

code/addr.cpp[]

程式碼使用預定義的私鑰在每次執行時產生相同的比特幣地址,如下例所示

例4-3.編譯並執行addr程式碼

$ g++ -o addr addr.cpp $(pkg-config --cflags --libs libbitcoin)
Run the addr executable
$ ./addr
Public key: 0202a406624211f2abbdc68da3df929f938c3399dd79fac1b51b0e4ad1d26a47aa
Address: 1PRTTaJesdNovgne6Ehcdu1fpEdX7913CK

4.2.2 金鑰的格式

公鑰和私鑰的都可以有多種格式。一個金鑰被不同的格式編碼後,雖然結果看起來可能不同,但是金鑰所編碼數字並沒有改變。這些不同的編碼格式主要是用來方便人們無誤地使用和識別金鑰。

4.2.2.1私鑰的格式

私鑰可以以許多不同的格式表示,所有這些都對應於相同的256位的數字。表4-2展示了私鑰的三種常見格式。不同的格式用在不同的場景下。十六進位制和原始的二進位制格式用在軟體的內部,很少展示給使用者看。WIF格式用在錢包之間金鑰的輸入和輸出,也用於代表私鑰的二維碼(條形碼)。

表4-2展私鑰的三種常見格式

表4-3 示例:同樣的私鑰,不同的格式

表4-3 示例:同樣的私鑰,不同的格式

這些表示法都是用來表示相同的數字、相同的私鑰的不同方法。雖然編碼後的字串看起來不同,但不同的格式彼此之 間可以很容易地相互轉換。請注意,“raw binary”未顯示在表4-3 示例中,根據定義此處顯示的任何編碼的格式,不是raw binary資料。

我們使用Bitcoin Explorer中的wif-to-ec命令(請參閱[appdx_bx])來顯示兩個WIF鍵代表相同的私鑰:

$ bx wif-to-ec 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn

1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd

$ bx wif-to-ec KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ

1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd

4.2.3從Base58Check解碼

Bitcoin Explorer命令(參見[appdx_bx])使得編寫shell指令碼和命令列“管道”變得容易,這些方式可以處理比特幣金鑰,地址和交易。 您可以使用Bitcoin Explorer在命令列上解碼Base58Check格式。

我們使用base58check-decode命令解碼未壓縮的金鑰:

$ bx base58check-decode 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn

wrapper

{
,,,
checksum 4286807748
payload 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
version 128

,,,

}

結果包含金鑰作為有效載荷,WIF版本字首128和校驗和。

請注意,壓縮金鑰的“有效負載”附加了字尾01,表示匯出的公鑰要壓縮:

$ bx base58check-decode KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ

wrapper
{
。。。
checksum 2339607926
payload 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd01
version 128
。。。
}

將十六進位制轉換為Base58Check編碼

要轉換成Base58Check(與上一個命令相反),我們使用Bitcoin Explorer的base58check-encode命令(請參閱[appdx_bx]),並提供十六進位制私鑰,其次是WIF版本字首128:

bx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd --version 128

5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn

將十六進位制(壓縮格式金鑰)轉換為Base58Check編碼

要將壓縮格式的私鑰編碼為Base58Check(參見“壓縮格式私鑰”一節),我們需在十六進位制私鑰的後面新增字尾01,然後使用跟上面一樣的方法:

$ bx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd01 --version 128

KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ

生成的WIF壓縮格式的私鑰以字母“K”開頭,用以表明被編碼的私鑰有一個字尾“01”,且該私鑰只能被用於生成壓縮格式 的公鑰(參見“壓縮格式公鑰”一節)。

4.2.3.1公鑰的格式

公鑰也可以用多種不同格式來表示,最重要的是它們分為非壓縮格式或壓縮格式公鑰這兩種形式。

我們從前文可知,公鑰是在橢圓曲線上的一個點,由一對座標(x,y)組成。公鑰通常表示為字首04緊接著兩個256位元的數字。其中一個256位元數字是公鑰的x座標,另一個256位元數字是y座標。字首04是用來區分非壓縮格式公鑰, 壓縮格式公鑰是以02或者03開頭。

下面是由前文中的私鑰所生成的公鑰,其座標x和y如下:

x = F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A 

y = 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB

下面是同樣的公鑰以520位元的數字(130個十六進位制數字)來表達。這個520位元的數字以字首04開頭,緊接著是x及y 座標,組成格式為04 x y:

K = 04F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE 52DDFE2E505BDB

4.2.3.2壓縮格式公鑰

引入壓縮格式公鑰是為了減少比特幣交易的位元組數,從而可以節省那些執行區塊鏈資料庫的節點磁碟空間。大部分比特幣交易包含了公鑰,用於驗證使用者的憑據和支付比特幣。每個公鑰有520位元(包括字首,x座標,y座標)。如果每個區塊有數百個交易,每天有成千上萬的交易發生,區塊鏈裡就會被寫入大量的資料。

正如我們在“4.1.4 公鑰”一節所見,一個公鑰是一個橢圓曲線上的點(x, y)。而橢圓曲線實際是一個數學方程,曲線上的點實際是該方程的一個解。因此,如果我們知道了公鑰的x座標,就可以通過解方程y2 mod p = (x3 + 7) mod p得到y坐 標。這種方案可以讓我們只儲存公鑰的x座標,略去y座標,從而將公鑰的大小和儲存空間減少了256位元。每個交易所 需要的位元組數減少了近一半,隨著時間推移,就大大節省了很多資料傳輸和儲存。

未壓縮格式公鑰使用04作為字首,而壓縮格式公鑰是以02或03作為字首。需要這兩種不同字首的原因是:因為橢圓曲 線加密的公式的左邊是y2 ,也就是說y的解是來自於一個平方根,可能是正值也可能是負值。更形象地說,y座標可能在 x座標軸的上面或者下面。從圖4-2的橢圓曲線圖中可以看出,曲線是對稱的,從x軸看就像對稱的鏡子兩面。因此,如果我們略去y座標,就必須儲存y的符號(正值或者負值)。換句話說,對於給定的x值,我們需要知道y值在x軸的上面還是下面,因為它們代表橢圓曲線上不同的點,即不同的公鑰。當我們在素數p階的有限域上使用二進位制算術計算橢圓曲線的時候,y座標可能是奇數或者偶數,分別對應前面所講的y值的正負符號。因此,為了區分y座標的兩種可能值,我們在生成壓縮格式公鑰時,如果y是偶數,則使用02作為字首;如果y是奇數,則使用03作為字首。這樣就可以根據公鑰中給定的x值,正確推匯出對應的y座標,從而將公鑰解壓縮為在橢圓曲線上的完整的點座標。下圖闡釋了公鑰壓縮:

圖4-7公鑰壓縮

下面是前述章節所生成的公鑰,使用了264位元(66個十六進位制數字)的壓縮格式公鑰格式,其中字首03表示y座標是一個奇數:

K = 03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A

這個壓縮格式公鑰對應著同樣的一個私鑰,這意味它是由同樣的私鑰所生成。但是壓縮格式公鑰和非壓縮格式公鑰看起來不同。更重要的是,如果我們使用雙雜湊函式(RIPEMD160(SHA256(K)))將壓縮格式公鑰轉化成比特幣地址,得到的地址將會不同於由非壓縮格式公鑰產生的地址。這種結果會讓人迷惑,因為一個私鑰可以生成兩種不同格式的公鑰——壓縮格式和非壓縮格式,而這兩種格式的公鑰可以生成兩個不同的比特幣地址。但是,這兩個不同的比特幣地址的私鑰是一樣的。

壓縮格式公鑰漸漸成為了各種不同的比特幣客戶端的預設格式,它可以大大減少交易所需的位元組數,同時也讓儲存區塊鏈所需的磁碟空間變小。然而,並非所有的客戶端都支援壓縮格式公鑰,於是那些較新的支援壓縮格式公鑰的客戶端就不得不考慮如何處理那些來自較老的不支援壓縮格式公鑰的客戶端的交易。這在錢包應用匯入另一個錢包應用的私鑰的 時候就會變得尤其重要,因為新錢包需要掃描區塊鏈並找到所有與這些被匯入私鑰相關的交易。比特幣錢包應該掃描哪個比特幣地址呢?新客戶端不知道應該使用哪個公鑰:因為不論是通過壓縮的公鑰產生的比特幣地址,還是通過非壓縮的公鑰產生的地址,兩個都是合法的比特幣地址,都可以被私鑰簽名,但是他們是不同的比特幣地址。

為了解決這個問題,當私鑰從錢包中被匯出時,代表私鑰的WIF在較新的比特幣錢包裡被處理的方式不同,表明該私鑰已經被用來生成壓縮的公鑰和因此壓縮的比特幣地址。這個方案可以解決匯入私鑰來自於老錢包還是新錢包的問題,同時也解決了通過公鑰生成的比特幣地址是來自於壓縮格式公鑰還是非壓縮格式公鑰的問題。最後新錢包在掃描區塊鏈時,就可以使用對應的比特幣地址去查詢該比特幣地址在區塊鏈裡所發生的交易。我們將在下一節詳細解釋這種機制是如何工作的。

4.2.3.3壓縮格式私鑰

實際上“壓縮格式私鑰”是一種名稱上的誤導,因為當一個私鑰被使用WIF壓縮格式匯出時,不但沒有壓縮,而且比“非壓縮格式”私鑰長出一個位元組。這個多出來的一個位元組是私鑰被加了字尾01,用以表明該私鑰是來自於一個較新的錢包, 只能被用來生成壓縮的公鑰。私鑰是非壓縮的,也不能被壓縮。“壓縮的私鑰”實際上只是表示“用於生成壓縮格式公鑰的私鑰”,而“非壓縮格式私鑰”用來表明“用於生成非壓縮格式公鑰的私鑰”。為避免更多誤解,應該只可以說匯出格式 是“WIF壓縮格式”或者“WIF”,而不能說這個私鑰是“壓縮”的。

表4示例:相同的金鑰,不同的格式

表4-4展示了同樣的私鑰使用不同的WIF和WIF壓縮格式編碼。

請注意,十六進位制壓縮私鑰格式在末尾有一個額外的位元組(十六進位制為01)。 雖然Base58編碼版本字首對於WIF和WIF壓縮格式都是相同的(0x80),但在數字末尾新增一個位元組會導致Base58編碼的第一個字元從5變為K或 L,考慮到對於Base58這是十進位制編碼100號和99號之間的差別。對於100是一個數字長於99的數字,它有一個字首1,而不是字首9。當長度變化 ,它會影響字首。 在Base58中,字首5改變為K或L,因為數字的長度增加一個位元組。

要注意的是,這些格式並不是可互換使用的。在實現了壓縮格式公鑰的較新的錢包中,私鑰只能且永遠被匯出為WIF壓 縮格式(以K或L為字首)。對於較老的沒有實現壓縮格式公鑰的錢包,私鑰將只能被匯出為WIF格式(以5為字首)導 出。這樣做的目的就是為了給匯入這些私鑰的錢包一個訊號:是否錢包必須搜尋區塊鏈尋找壓縮或非壓縮公鑰和地址。

如果一個比特幣錢包實現了壓縮格式公鑰,那麼它將會在所有交易中使用該壓格式縮公鑰。錢包中的私鑰將會被用來在曲線上生成公鑰點,這個公鑰點將會被壓縮。壓縮格式公鑰然後被用來生成交易中使用的比特幣地址。當從一個實現了壓縮格式公鑰的新的比特幣錢包匯出私鑰時,錢包匯入格式(WIF)將會被修改為WIF壓縮格式,該格式將會在私鑰的後面附加一個位元組大小的字尾01。最 終的Base58Check編碼格式的私鑰被稱作WIF(“壓縮”)私鑰,以字母“K”或“L”開頭。而以“5”開頭的是從較老的錢包中 以WIF(非壓縮)格式匯出的私鑰。

提示 “壓縮格式私鑰”是一個不當用詞!私鑰不是壓縮的。WIF壓縮格式的私鑰只是用來表明他們只能被生成壓縮的公鑰和對應的比特幣地址。相反地,“WIF壓縮”編碼的私鑰還多出一個位元組,因為這種私鑰多了字尾“01”。該字尾是用來區分“非壓縮格式”私鑰和“壓縮格式”私鑰。

4.3 用Python實現金鑰和比特幣地址

最全面的比特幣Python庫是 Vitalik Buterin寫的 pybitcointools。在例4-5中,我們使用pybitcointools庫(匯入 為“bitcoin”)來生成和顯示不同格式的金鑰和比特幣地址。

例4-5 使用pybitcointools庫的金鑰和比特幣地址的生成和格式化過

code/key-to-address-ecc-example.py[]

例4-6 上例輸出如下:


$ python key-to-address-ecc-example.py

Private Key (hex) is:

 3aba4162c7251c891207b747840551a71939b0de081f85c4e44cf7c13e41daa6
 
Private Key (decimal) is:

 26563230048437957592232553826663696440606756685920117476832299673293013768870
 
Private Key (WIF) is:

 5JG9hT3beGTJuUAmCQEmNaxAuMacCTfXuw1R3FCXig23RQHMr4K
 
Private Key Compressed (hex) is:

 3aba4162c7251c891207b747840551a71939b0de081f85c4e44cf7c13e41daa601
 
Private Key (WIF-Compressed) is:

 KyBsPXxTuVD82av65KZkrGrWi5qLMah5SdNq6uftawDbgKa2wv6S
 
Public Key (x,y) coordinates is:

 (41637322786646325214887832269588396900663353932545912953362782457239403430124L,
 16388935128781238405526710466724741593761085120864331449066658622400339362166L)

Public Key (hex) is:

 045c0de3b9c8ab18dd04e3511243ec2952002dbfadc864b9628910169d9b9b00ec↵
243bcefdd4347074d44bd7356d6a53c495737dd96295e2a9374bf5f02ebfc176

Compressed Public Key (hex) is:

 025c0de3b9c8ab18dd04e3511243ec2952002dbfadc864b9628910169d9b9b00ec

Bitcoin Address (b58check) is:

 1thMirt546nngXqyPEz532S8fLwbozud8

Compressed Bitcoin Address (b58check) is:
 14cxpo3MBCYYWCgF74SWTdcmxipnGUsPw3

例4-7是另外一個示例,使用的是Python ECDSA庫來做橢圓曲線計算而非使用bitcoin的庫。

code/ec-math.py[]

例4-8是上述指令碼的輸出。

注意:

\  Install Python PIP package manager

$ sudo apt-get install python-pip

\Install the Python ECDSA library

$ sudo pip install ecdsa

\ Run the script

$ python ec-math.py

Secret:  38090835015954358862481132628887443905906204995912378278060168703580660294000
EC point: (70048853531867179489857750497606966272382583471322935454624595540007269312627, 105262206478686743191060800263479589329920209527285803935736021686045542353380)

BTC public key: 029ade3effb0a67d5c8609850d797366af428f4a0d5194cb221d807770a1522873

4.4 高階金鑰和地址

在以下部分中,我們將看到高階形式的金鑰和地址,諸如加密私鑰、指令碼和多重簽名地址,靚號地址,和紙錢包。

4.4.1 加密私鑰(BIP0038)

私鑰必須保密。私鑰的機密性需求情況是,在實踐中相當難以實現,因為該需求與同樣重要的安全物件可用性相互矛盾。當你需要為了避免私鑰丟失而儲存備份時,會發現維護私鑰私密性是一件相當困難的事情。通過密碼加密存有私鑰的錢包可能要安全一點,但那個錢包也需要備份。有時,例如使用者因為要升級或重灌錢包軟體,而需要把金鑰從一個錢包轉移到另一個。私鑰備份也可能需要儲存在紙張上(參見“後面紙錢包”一節)或者外部儲存介質裡,比如U盤。但如果一旦備份檔案失竊或丟失呢?這些矛盾的安全目標推進了便攜、方便、可以被眾多不同錢包和比特幣客戶端理解的加密私鑰標準BIP0038的出臺(BIP-38詳細可參見附錄部分)。

BIP0038提出了一個通用標準,使用一個口令加密私鑰並使用Base58Check對加密的私鑰進行編碼,這樣加密的私鑰就可以安全地儲存在備份介質裡,安全地在錢包間傳輸,保持金鑰在任何可能被暴露情況下的安全性。這個加密標準使 用了AES,這個標準由NIST建立,並廣泛應用於商業和軍事應用的資料加密。

BIP0038加密方案是:輸入一個比特幣私鑰,通常使用WIF編碼過,base58chek字串的字首“5”。此外BIP0038加密方案需要一個長密碼作為口令,通常由多個單詞或一段複雜的數字字母字串組成。BIP0038加密方案的結果是一個由 base58check編碼過的加密私鑰,字首為6P。如果你看到一個6P開頭的的金鑰,這就意味著該金鑰是被加密過,並需要一個口令來轉換(解碼)該金鑰回到可被用在任何錢包WIF格式的私鑰(字首為5)。許多錢包APP現在能夠識別 BIP0038加密過的私鑰,會要求使用者提供口令解碼並匯入金鑰。第三方APP,諸如非常好用基於瀏覽器的Bit Address , 可以被用來解碼BIP00038的金鑰。

最通常使用BIP0038加密的金鑰用例是紙錢包——一張紙張上備份私鑰。只要使用者選擇了強口令,使用BIP0038加密的私鑰的紙錢包就無比的安全,這也是一種很棒的比特幣離線儲存方式(也被稱作“冷儲存”)。

bitaddress.org上測試表4-5中加密金鑰,看看如何輸入密碼以得到加密金鑰。

表4-5 BIP0038加密私鑰例子

表4-5 BIP0038加密私鑰例子

4.4.2 P2SH (Pay-to-Script Hash)和多重簽名地址

正如我們所知,傳統的比特幣地址從數字1開頭,來源於公鑰,而公鑰來源於私鑰。雖然任何人都可以將比特幣傳送到 一個1開頭的地址,但比特幣只能在通過相應的私鑰簽名和公鑰雜湊值後才能消費。

以數字3開頭的比特幣地址是P2SH地址,有時被錯誤的稱謂多重簽名或多重簽名地址。他們指定比特幣交易中受益人為雜湊的指令碼,而不是公鑰的所有者。這個特性在2012年1月由BIP0016引進,目前因為BIP0016提供了增加功能到地址本身的機會而被廣泛的採納。不同於P2PKH交易傳送資金到傳統1開頭的比特幣地址,資金被髮送到3開頭的地址時,需要的不僅僅是一個公鑰的雜湊值和一個私鑰簽名作為所有者證明。在建立地址的時候,這些要求會被指定在指令碼中,所有對地址的輸入都會被這些要求阻隔。

一個P2SH地址從交易指令碼中建立,它定義誰能消耗這個交易輸出(後面“P2SH(Pay-to-Script-Hash)”一節對此有 詳細的介紹)。編碼一個P2SH地址涉及使用一個在建立比特幣地址用到過的雙重雜湊函式,並且只能應用在指令碼而不是公鑰:

 script hash = RIPEMD160(SHA256(script)) 

產生的指令碼雜湊由Base58Check編碼字首為5的版本、編碼後得到開頭為3的編碼地址。一個P2SH地址例子是 3F6i6kwkevjR7AsAd4te2YB2zZyASEm1HM。可以使用Bitcoin Explorer命令指令碼編碼獲得,比如sha256, ripemd160, and base58check-encode,舉例如下:

$ echo dup hash160 [ 89abcdefabbaabbaabbaabbaabbaabbaabbaabba ] equalverify checksig > script

$ bx script-encode < script | bx sha256 | bx ripemd160 | bx base58check-encode --version 5

3F6i6kwkevjR7AsAd4te2YB2zZyASEm1HM

提示P2SH 不一定是多重簽名的交易。雖然P2SH地址通常都是代表多重簽名,但也可能是編碼其他型別的交易指令碼。

4.4.2.1 多重簽名地址和P2SH

目前,P2SH函式最常見的實現是多重簽名地址指令碼。顧名思義,底層指令碼需要多個簽名來證明所有權,此後才能消費資金。設計比特幣多重簽名特性是需要從總共N個金鑰中需要M個簽名(也被稱為“閾值”),被稱為M-N多簽名,其 中M是等於或小於N。例如,第一章中提到的咖啡店主Bob使用多重簽名地址需要1-2簽名,一個是屬於他的金鑰和一個屬於他同伴的金鑰,以確保其中一方可以簽署消費一筆鎖定到這個地址的輸出。這類似於傳統的銀行中的一個“聯合賬戶”,其中任何一方配偶可以單獨簽單消費。或就像Bob僱傭的網頁設計師Gopesh, 創立一個網站,可能為他的業務需要一個2-3的多簽名地址,確保除非至少兩個業務合作伙伴簽署簽名交易才可以進行支付消費。

我們將會在第五章節探索如何使用P2SH地址建立交易用來消費資金。

4.4.3 比特幣靚號地址

靚號地址包含了人類可讀資訊的有效比特幣地址。例如,1LoveBPzzD72PUXLzCkYAtGFYmK5vYNR33就是包含了Base-58 字母love的。靚號地址需要生成並通過數十億的候選私鑰測試,直到一個私鑰能生成具有所需圖案的比特幣地址。雖然有一些優化過的靚號生成演算法,該方法必須涉及隨機選擇一個私鑰,生成公鑰,再生成比特幣地址,並檢查是否與所要的靚號圖案相匹配,重複數十億次,直到找到一個匹配。

一旦找到一個匹配所要圖案的靚號地址,來自這個靚號地址的私鑰可以和其他地址相同的方式被擁有者消費比特幣。靚號地址不比其他地址具有更多或更少的安全性。它們依靠和其他地址相同的ECC和SHA。你無法比任何別的地址更容易的獲得一個靚號圖案開頭的地址的私鑰。

在第一章中,我們介紹了Eugenia,一位在菲律賓工作的兒童慈善總監。我們假設Eugenia組織了一場比特幣募捐活動,並希望使用靚號比特幣地址來宣傳這個募捐活動。Eugenia將會創造一個以1Kids開頭的靚號地址來促進兒童慈善募捐的活動。讓我們看看這個靚號地址如何被建立,這個靚號地址對Eugenia慈善募捐的安全性又意味著什麼。

4.4.3.1 生成靚號地址

認識到比特幣地址不過是由Base58字母代表的一個數字是非常重要的。搜尋“1kids”開頭的圖案我們會發 現從1Kids11111111111111111111111111111到1Kidszzzzzzzzzzzzzzzzzzzzzzzzzzzzz的地址。這些以“1kid”開頭的地址範圍中大約有58的29次方地址(1.4 * 10^51))。表4-6顯示了這些有“1kids”字首的地址。

表4-6 “1Kids”靚號的範圍

表4-6 “1Kids”靚號的範圍

我們把“1Kids”這個字首當作數字,我們可以看看比特幣地址中這個字首出現的頻率。如果是一臺普通效能的桌面電腦, 沒有任何特殊的硬體,可以每秒搜尋大約10萬個金鑰。

表4-12 靚號的出現的頻率(1KidsCharity)以及生成所需時間

正如你所見,Eugenia將不會很快地建立出以“1KidsCharity”開頭的靚號地址,即使她有數千臺的電腦同時進行運算。每增加一個字元就會增加58倍的計算難度。超過七個字元的圖案通常需要專用的硬體才能被找出,譬如使用者定製的具有多個圖形處理單元(GPU)的桌上型電腦。那些通常是無法繼續在比特幣挖礦中盈利的鑽機,被重新賦予了尋找靚號地 址的任務。用GPU系統搜尋靚號的速度比用通用CPU要快很多個量級。

另一種尋找靚號地址的方法是將工作外包給一個礦池裡的靚號礦工們,如靚號礦池中的礦池。一個礦池是一種允許那些 GPU硬體通過為他人尋找靚號地址來獲得比特幣的服務。對小額的賬單,Eugenia可以將搜尋7位字元圖案的靚號地址的工作外包,在幾個小時內就可以得到結果,而不必用一個CPU搜尋上幾個月才得到結果。

生成一個靚號地址是一項通過蠻力的過程:嘗試一個隨機金鑰,檢查生成的地址是否和所需的圖案相匹配,重複這個過程直到成功找到為止。例4-9是個靚號礦工的例子,用C++程式寫的來尋找靚號地址的程式。這個例子運用到了我們在“其他替代客戶端、資料庫、工具包”一節介紹過的libbitcoin庫。

例4-9 靚號挖掘程式

code/vanity-miner.cpp[]

註釋編譯和執行虛擬礦工示例使用std :: random_device(譯者注:std :: random_device是均勻分佈的整數隨機數生成器,產生非確定性隨機數)。 根據實施情況,可能會反映底層作業系統提供的CSRNG。 在類似Unix的作業系統(如Linux)中,它來自/ dev/urandom。 這裡使用的隨機數字生成器用於演示,並不適用於生成級別的比特幣金鑰,因為它沒有以足夠的安全性。

示例程式碼需要用C編譯器連結libbitcoin庫(此庫需要提前裝入該系統)進行編譯。可以不帶引數直接執行vanity-miner的可執行檔案 (參見例4-10),它就會嘗試找到以“1kid”開頭的靚號地址。

例4-10編譯並執行vanity-miner程式示例

\Compile the code with g++
$ g++ -o vanity-miner vanity-miner.cpp 

$(pkg-config --cflags --libs libbitcoin)

\Run the example

$ ./vanity-miner

Found vanity address! 1KiDzkG4MxmovZryZRj8tK81oQRhbZ46YT
Secret: 57cc268a05f83a23ac9d930bc8565bac4e277055f4794cbd1a39e5e71c038f3f

\ Run it again for a different result

$ ./vanity-miner

Found vanity address! 1Kidxr3wsmMzzouwXibKfwTYs5Pau8TUFn
Secret: 7f65bbbbe6d8caae74a0c6a0d2d7b5c6663d71b60337299a1a2cf34c04b2a623

使用時間命令檢視需要多久才能找到結果

$ time ./vanity-miner
Found vanity address! 1KidPWhKgGRQWD5PP5TAnGfDyfWp5yceXM
Secret: 2a802e7a53d8aa237cd059377b616d2bfcfa4b0140bc85fa008f2d3d4b225349

real	0m8.868s
user	0m8.828s
sys	0m0.035s

正如我們執行Unix命令所測出的執行時間所示,示例程式碼要花幾秒鐘來找出匹配“kid”三個字元模板的結果。你可以嘗試在原始碼中改變search這一搜尋模板,看一看如果是四個字元或者五個字元的搜尋模板需要花多久時間!

4.4.3.2 靚號地址安全性

靚號地址既可以增加、也可以削弱安全措施,它們著實是一把雙刃劍。用於改善安全性時,一個獨特的地址使對手難以使用他們自己的地址替代你的地址,以欺騙你的顧客支付他們的賬單。不幸的是,靚號地址也可能使得任何人都能建立一個類似於隨機地址的地址,甚至另一個靚號地址,從而欺騙你的客戶。

Eugenia可以讓捐款人捐款到她宣佈的一個隨機生成地址(例如:1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy)。 或者她可以生成一個以“1Kids”開頭的靚號地址以顯得更獨特。

在這兩種情況下,使用單一固定地址(而不是每比捐款用一個獨立的動態地址)的風險之一是小偷有可能會黑進你的網站,用他自己的地址取代你的地址,從而將捐贈轉移給他自己。如果你在不同的地方公佈了你的捐款地址,你的使用者可以 在付款之前用自己眼睛檢查以確保這個地址跟在你的網站、郵件和傳單上看到的地址是同一個。在隨機地址 1j7mdg5rbqyuhenydx39wvwk7fslpeoxzy的情況下,普通使用者可能會只檢查頭幾個字元“1j7mdg”,就認為地址匹配。使用靚號地址生成器,那些想通過替換類似地址來盜竊的人可以快速生成與前幾個字元相匹配的地址,如表4-8所示。

表4-8 生成匹配某隨機地址的多個靚號

表4-8 生成匹配某隨機地址的多個靚號

那靚號地址會不會增加安全性?如果Eugenia生成1Kids33q44erFfpeXrmDSz7zEqG2FesZEN的靚號地址,使用者可能看到靚號圖案的字母和一些字元在上面,例如在地址部分中註明了1Kids33。這樣就會迫使攻擊者生成至少6個字母相匹 配的的靚號地址(比之前多2個字元),就要花費比Eugenia多3364倍的努力。本質上,Eugenia付出的努力(或者靚號池付出的)迫使攻擊者不得不生成更長的靚號圖案。如果Eugenia花錢請礦池生成8個字元的靚號地址,攻擊者將會被逼迫到10字元的境地,那將是個人電腦,甚至昂貴自定義靚號挖掘機或靚號池也無法生成。對Eugenia來說可承擔的起支出,對攻擊者來說則變成了無法承擔支出,特別是如果欺詐的潛在回報不足以支付生成靚號地址所需的費用。

4.4.4 紙錢包

紙錢包是列印在紙張上的比特幣私鑰。有時紙錢包為了方便起見也包括對應的比特幣地址,但這並不是必要的,因為地址可以從私鑰中匯出。紙錢包是一個非常有效的建立備份或者線下儲存比特幣(即冷儲存)的方式。作為備份機制,一個紙錢包可以提供安全性,以防在電腦硬碟損壞、失竊或意外刪除的情況下造成金鑰的的丟失。作為一個冷儲存的機制,如果紙錢包金鑰線上下生成並永久不在電腦系統中儲存,他們在應對黑客攻擊,鍵盤記錄器,或其他線上電腦威脅更有安全性。

紙錢包有許多不同的形狀,大小,和外觀設計,但非常基本的原則是一個金鑰和一個地址列印在紙張上。表4-14展現了紙錢包最基本的形式。

表4-9 比特幣紙錢包的私鑰和公鑰的列印形式

表4-9 比特幣紙錢包的私鑰和公鑰的列印形式

通過使用工具,就可以很容易地生成紙錢包,譬如使用bitaddress.org網站上的客戶端Javascript生成器。這個頁面包含所有生成金鑰和紙錢包所必須程式碼,甚至在完全失去網路連線的情況下,也可以生成金鑰和紙錢包。若要使用它,先將HTML頁面儲存在本地磁碟或外部U盤。從Internet網路斷開,從瀏覽器中開啟檔案。更方便的,使用一個原始作業系統啟動電腦,比如一 個光碟啟動的Linux系統。任何在離線情況下使用這個工具所生成的金鑰,都可以通過USB線在本地印表機上列印出 來,從而製造了金鑰只存在紙張上而從未儲存在線上系統上的紙錢包。將這些紙錢包放置在防火保險櫃內,傳送比特幣到 對應的比特幣地址上,從而實現了一個簡單但非常有效的冷儲存解決方案。圖4-8展示了通過bitaddress.org 生成的紙錢包。

圖4-8展示了通過bitaddress.org 生成的紙錢包

這個簡單的紙錢包系統的不足之處是那些被列印下來的金鑰容易被盜竊。一個能夠接近這些紙的小偷只需偷走紙或者用把拍攝紙上的金鑰,就能控制被這些金鑰鎖定的比特幣。一個更復雜的紙錢包儲存系統使用BIP0038加密的私鑰。列印在紙錢包上的這些私鑰被其所有者記住的一個口令保護起來。沒有口令,這些被加密過的金鑰也是毫無用處的。但它們仍舊優於用口令保護,因為這些金鑰從沒有線上過,並且必須從保險箱或者其他物理的安全儲存中匯出。圖4-9展示了通過bitaddress.org 生成的加密紙錢包。

圖4-9展示了通過bitaddress.org 生成的加密紙錢包,密碼:test

警告雖然你可以多次存款到紙錢包中,但是你最好一次性提取裡面所有的資金。因為如果你提取的金額少於其中的金額的話,有些錢包可能會生成一個找零地址。並且,你所用的電腦可能被病毒感染,那麼就有可能洩露私鑰。一 次性提走所有餘款可以減少私鑰洩露的風險,如果你所需的金額比較少,那麼請把餘額傳送到相同交易的一個新的紙錢包裡。

紙錢包有許多設計和大小,並有許多不同的特性。有些作為禮物送給他人,有季節性的主題,像聖誕節和新年主題。另 外一些則是設計儲存在銀行金庫或通過某種方式隱藏私鑰的保險箱內,或者用不透明的刮刮貼,或者摺疊和防篡改的鋁箔膠粘密封。圖4-10至圖4-12展示了幾個不同安全和備份功能的紙錢包的例子。

圖4-10 通過bitcoinpaperwallet.com生成的、私鑰寫在摺疊袋上的紙錢包

圖4-10 通過bitcoinpaperwallet.com生成的、私鑰寫在摺疊袋上的紙錢包

圖4-11 通過bitcoinpaperwallet.com 生成的、私鑰被密封住的紙錢包, 其他設計有金鑰和地址的額外副本,類似於票根形式的可以拆卸存根,讓你可以儲存多個副本以防火災、洪水或其他自然災害。

圖4-11 通過bitcoinpaperwallet.com 生成的、私鑰被密封住的紙錢包

圖4-12 在備份“存根”上有多個私鑰副本的紙錢包
圖4-12 在備份“存根”上有多個私鑰副本的紙錢包

相關文章