# 第4章 密鑰和地址
你可能聽說過比特幣基于*加密技術*的,加密技術是由數學分支出來的,在計算機安全領域被廣泛使用。 密碼術在希臘語中是“秘密寫作”的意思,但密碼學這門學科不僅僅只有秘密寫作,還包括加密技術。 密碼學可以用于不泄露秘密(數字簽名),就能證明知曉加密的內容,或證明數據的真實性(數字指紋)。 這些類型的加密證明是比特幣中至關重要的數學工具,并被廣泛用于比特幣應用。有趣的是,加密不是比特幣的重要組成部分,因為它的通信和交易數據沒有加密,也不需要加密來保護資金。 在本章中,我們將介紹一些在比特幣中用來控制資金所有權的加密技術,包括密鑰,地址和錢包。
## 4.1簡介
比特幣的所有權是通過*數字密鑰、比特幣地址和數字簽名*來確定的。數字密鑰實際上并不存儲在網絡中,而是由用戶生成之后,存儲在一個叫做*錢包*的文件或簡單的數據庫中。用戶錢包中的數字密鑰完全獨立于比特幣協議,可由用戶的錢包軟件生成并管理,而無需參照區塊鏈或訪問互聯網。密鑰實現了比特幣的許多有趣特性,包括去中心化信任和控制、所有權認證和基于密碼學證明的安全模型。
大多數比特幣交易都需要在區塊鏈中存儲一個有效的數字簽名。該數字簽名只能由密鑰產生,因此擁有密鑰副本就等于擁有了該帳戶中比特幣的控制權。用于支出資金的數字簽名也稱為*見證(witness)*,這是加密技術中的術語。 比特幣交易中的見證數據證明了資金的真正所有權。
密鑰是成對出現的,由私鑰和公鑰所組成。公鑰就像銀行的帳號,而私鑰就像PIN碼或支票的簽名。比特幣的用戶很少會直接看到數字密鑰。一般情況下,它們存儲在錢包文件內,由比特幣錢包軟件進行管理。
在比特幣交易的支付環節,收款人的公鑰由數字指紋表示,稱為*比特幣地址*,就像支票上收款人名稱 (即“付給誰的賬戶”)。一般情況下,比特幣地址由公鑰生成并與之對應。然而,并非所有比特幣地址都代表公鑰; 也可以代表其他支付對象,譬如腳本,我們將在本章后面提及。這樣一來,比特幣地址就可以抽象成資金接收者,使得交易更靈活,就像紙質支票:可以支付到個人賬戶、公司賬戶,支付賬單和現金。比特幣地址是密鑰被用戶能夠看到的唯一形式,因為這個地址就是需要告訴別人的。
首先,我們將介紹密碼學,并解釋在比特幣中使用的數學知識。然后我們將了解密鑰的產生、存儲和管理方式。我們將檢查私鑰和公鑰、地址和腳本地址的各種編碼格式。最后,我們將講解密鑰和地址的高級用途:比特幣靚號地址,多重簽名以及腳本地址和紙錢包。
### 4.1.1 公鑰加密和加密貨幣
公鑰加密發明于20世紀70年代,它是計算機和信息安全的數學基礎。
公鑰加密被發明之后,一些合適的數學函數被發現,譬如:素數冪運算和橢圓曲線乘法。這些數學函數都是不可逆的, 就是說很容易向一個方向計算,但不可以向相反方向倒推。基于這些數學函數的密碼學,使得生成數字密鑰和不可偽造的數字簽名成為可能。比特幣正是使用橢圓曲線乘法作為其公鑰加密的基礎。
在比特幣系統中,我們用公鑰加密創建一個密鑰對,用于控制對比特幣的訪問。密鑰對包括一個私鑰,和由其衍生出的唯一的公鑰。公鑰用于接收比特幣,而私鑰用于支付時進行交易簽名。
公鑰和私鑰之間的數學關系,使得私鑰可用于生成特定消息的簽名。公鑰則可以在不顯示私鑰的情況下驗證簽名。
支付比特幣時,比特幣的當前所有者需要在交易中提交其公鑰和簽名(每次交易的簽名都不同,但都由同一個私鑰生成)。針對展示的公鑰和簽名,比特幣網絡中的所有人都可以驗證該交易有效并予以接受,從而確認支付者對該交易中的比特幣的所有權。
>**提示** 大多數比特幣錢包工為了方便會將私鑰和公鑰以*密鑰對*的形式存儲在一起。然而,公鑰可以由私鑰計算得到, 所以只存儲私鑰也是可以的。
### 4.1.2 私鑰和公鑰
一個比特幣錢包中包含一系列的密鑰對,每個密鑰對包括一個私鑰和一個公鑰。私鑰(*k*)是一個數字,通常是隨機選出的。基于私鑰,我們就可以使用橢圓曲線乘法這個單向加密函數產生一個公鑰(*K*)。基于公鑰(*K*),我們就可以使用一個單向加密哈希函數生成比特幣地址(*A*)。在本節中,我們將從生成私鑰開始,講述如何使用橢圓曲線運算將私鑰生成公鑰,并最終由公鑰生成比特幣地址。私鑰、公鑰和比特幣地址之間的關系如下圖所示。

圖4-1:私鑰、公鑰和比特幣地址之間的關系
**為什么使用非對稱加密(公鑰/私鑰)?**
為什么在比特幣中使用非對稱加密技術? 它不是用于對交易進行“加密”(保密)的。 相反,非對稱加密技術的最有用特性是生成數字簽名。 可以將私鑰用作交易的數字指紋來產生數字簽名。 該簽名只能由知曉私鑰的人生成。 但是,任何訪問公鑰和交易指紋的人都可以驗證簽名。 這種非對稱密碼學的適用性使得任何人都可以驗證每筆交易的每個簽名,并且確保只有私鑰的所有者可以生成有效的簽名。
### 4.1.3 私鑰
私鑰就是一個隨機選出的數字而已。擁有和控制了私鑰,就相當于控制了該私鑰對應的比特幣地址中的所有資金。通過證明比特幣交易中資金的所有權,私鑰可以生成花費該筆資金的簽名。私鑰任何情況下都必須保密,因為一旦被泄露給第三方,相當于該私鑰保護之下的比特幣也拱手相讓了。私鑰還必須進行備份,以防意外丟失,因為私鑰一旦丟失就無法恢復,其所保護的比特幣也將永遠丟失。
>**提示** 比特幣私鑰只是一個數字。你可以用硬幣、鉛筆和紙來隨機生成你的私鑰:擲硬幣256次,用紙和筆記錄正反面并轉換為0和1,隨機得到的256位二進制數字可作為比特幣錢包的私鑰。該私鑰可進一步生成公鑰。
***從一個隨機數生成私鑰***
生成密鑰的第一步也是最重要的一步,是要找到足夠安全的熵源,即隨機性來源。生成一個比特幣私鑰在本質上與“在1到2<sup>256</sup>之間選一個數字”無異。只要選取的結果是不可預測或不可重復的,那么選取數字的具體方法并不重要。比特幣軟件使用操作系統底層的隨機數生成器來產生256位的熵(隨機性)。通常情況下,操作系統隨機數生成器由人工的隨機源進行初始化,這就是為什么也可能需要不停晃動鼠標幾秒鐘。
更準確地說,私鑰可以是1和n-1之間的任何數字,其中n是一個常數(n=1.158 * 10<sup>77</sup>,略小于2<sup>256</sup>),并被定義為由比特幣所使用的橢圓曲線的階(見下面的橢圓曲線密碼學解釋這一節)。要生成這樣的一個私鑰,我們隨機選擇一個256位的數字,并檢查它是否小于n-1。從編程的角度來看,一般是通過在一個密碼學安全的隨機源中取出一長串隨機字節,對其使用SHA256哈希算法進行運算,這樣就可以方便地產生一個256位的數字。如果運算結果小于n,我們就有了一個合適的私鑰。否則,我們就用另一個隨機數再重復一次。
>**警告** 不要自己寫代碼來生成隨機數,也不要使用編程語言提供的簡易隨機數生成器來獲得一個隨機數。使用密碼學安全的偽隨機數生成器(CSPRNG),并且需要有一個熵源值足夠的的種子。使用隨機數發生器的程序庫時,需仔細研讀其文檔,以確保它是密碼學安全的。正確實施CSPRNG是密鑰安全性的關鍵所在。
以下是一個隨機生成的私鑰(k),以十六進制格式表示(256位的二進制數,轉變為十六進制是64位,每個十六進制數占4位):
1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD
>**提示** 比特幣私鑰空間的大小是2<sup>256</sup>,這是一個非常大的數字。用十進制表示的話,大約是10<sup>77</sup>,而可見宇宙被估計只含有10<sup>80</sup>個原子。
要使用Bitcoin Core客戶端生成一個新的密鑰(參見第三章內容),可使用 getnewaddress 命令。出于安全考慮,命令運行后只顯示生成的公鑰,而不顯示私鑰。如果要bitcoind顯示私鑰,可以使用 dumpprivkey 命令。 dumpprivkey 命令會把私鑰以 Base58校驗和編碼格式顯示,這種私鑰格式被稱為錢包導入格式(WIF,Wallet Import Format),在“私鑰的格式”一節有詳細講解。下面給出了使用這兩個命令生成和顯示私鑰的例子:
```
$ bitcoin-cli getnewaddress
1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
$ bitcoin-cli dumpprivkey 1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
```
dumpprivkey 命令打開錢包提取由 getnewaddress 命令生成的私鑰。除非密鑰對都存儲在錢包里,否則bitcoind并不能從公鑰得知私鑰。
>**提示** dumpprivkey命令無法從公鑰生成私鑰,因為這是不可能的。這個命令只是顯示錢包中已有也就是由getnewaddress命令生成的私鑰。
還可以使用Bitcoin Explorer命令行工具(請參閱附錄中的[appdx_bx])使用命令seed,ec-new和ec-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<sup>256</sup> – 2<sup>32</sup> – 2<sup>9</sup> – 2<sup>8</sup> – 2<sup>7</sup> – 2<sup>6</sup> – 2<sup>4</sup> – 1, 這是個非常大的素數。
因為這條曲線被定義在一個素數階的有限域內,而不是定義在實數范圍,它的函數圖像看起來像二維的離散的點,因此很難可視化。不過,該公式與實數的橢圓曲線數學公式是相似的。為了舉例,下圖顯示了在一個小了很多的素數階17的有限域內的橢圓曲線,其形式為網格上的一系列散點。而secp256k1的比特幣橢圓曲線可以被想象成一個更大的網格上一系列更為復雜的散點。

圖4-3:橢圓曲線密碼學F(p)上的橢圓曲線,其中p = 17
比如,下面是 secp256k1 曲線上的點P,其坐標為(x,y)。
```
`P = (55066263022277343669578718895168534326250603453777594175500187360389116729240, 32670510020758816978083085130507043184471273380659243275938904335757337482424)`
```
下面的例1顯示了如何使用Python對其檢驗:
例1:使用pyhton確認這個點在橢圓曲線上
```
Python 3.4.0 (default, Mar 30 2014, 19:23:13)
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> p = 115792089237316195423570985008687907853269984665640564039457584007908834671663
>>> x = 55066263022277343669578718895168534326250603453777594175500187360389116729240
>>> y = 32670510020758816978083085130507043184471273380659243275938904335757337482424
>>> (x ** 3 + 7 - y**2) % p
0
```
在橢圓曲線數學中,有一個點被稱為“無窮遠點”,這大致相當于0的作用。計算機中,它有時表示為X = Y = 0(雖然這不滿足橢圓曲線方程,但這是一個可以單獨檢查的例子)。
還有一個 + 運算符,被稱為“加法”,就像小學數學中的實數相加。給定橢圓曲線上的兩個點P<sub>1</sub>和P<sub>2</sub>,則橢圓曲線上必定有第三點 P<sub>3</sub> = P<sub>1</sub> + P<sub>2</sub>。
從幾何學上說,該第三點P<sub>3</sub>可以在P<sub>1</sub>和P<sub>2</sub>之間畫一條線來計算出來的。這條直線恰好與橢圓曲線相交于另外一個地方。此點記為 P<sub>3</sub>'= (x,y)。然后,基于x軸的對稱點就是 P<sub>3</sub>=(x,-y)。
下面是幾個解釋為何需要“無窮遠點”的特殊例子。
若 P<sub>1</sub>和 P<sub>2</sub>是同一點,那么P<sub>1</sub>和P<sub>2</sub>之間的連線延長就會與曲線相切于p<sub>1</sub>處。該切線將會與曲線相交于一個新的點。該切線的斜率可用微積分求得。盡管我們只局限在曲線上兩個整數坐標的那個點,但這個機制是沒問題的。
在某些情況下(即,如果P<sub>1</sub>和P<sub>2</sub>具有相同的x值,不同的y值),則切線會完全垂直,在這種情況下,P<sub>3</sub> = “無窮遠點”。
若P<sub>1</sub>就是“無窮遠點”,那么其和 P<sub>1</sub> + P<sub>2</sub>= P<sub>2</sub>。類似地,當P<sub>2</sub>是無窮遠點,則P<sub>1</sub>+ P<sub>2</sub> = P<sub>1</sub>。這表明無窮遠點類似于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加密庫】](http://bit.ly/1ql7bn8)進行橢圓曲線計算。例如,調用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),具體來說是SHA256和RIPEMD160。
以公鑰 *K* 為輸入,計算其SHA256哈希值,并以此結果計算RIPEMD160 哈希值,得到一個長度為160位(20字節)的數字:
A = RIPEMD160(SHA256(K))
公式中,*K*是公鑰,*A*是生成的比特幣地址。
>**提示** 比特幣地址與公鑰并不不同。比特幣地址是公鑰經過單向的哈希函數生成的。
通常用戶見到的比特幣地址是經過“Base58Check”編碼的(參見下面的“Base58和Base58Check編碼”一節),這種編碼使用了58個字符(Base58數字系統)和校驗碼,提高了可讀性、避免歧義并有效防止了在地址轉錄和輸入中產生的錯誤。Base58Check編碼也被用于比特幣的其它地方,例如比特幣地址、私鑰、加密的密鑰和腳本哈希中,用來提高可讀性和錄入的正確性。下一節中我們會詳細解釋Base58Check的編碼和解碼機制,以及它產生的結果。
下圖描述了如何從公鑰生成比特幣地址。

圖4-5從公鑰生成比特幣地址]
### 4.2.1 Base58和Base58Check編碼
為了更簡潔方便地表示長串的數字,使用更少的符號,許多計算機系統在表示大于十進制時,會使用數字和字母混合組成。例如,傳統的十進制計數系統使用0-9十個數字,而十六進制系統使用了16個,增加了 A-F 六個字母來表示0-9額外的符號。一個同樣的數字,它的十六進制表示就會比等值得十進制表示更短。更為簡潔的是,Base64使用了26個小寫字母、26個大寫字母、10個數字以及兩個符號(例 如“+”和“/”),用于在像電子郵件這樣的文本媒介中傳輸二進制數據。Base64通常用于郵件中添加二進制附件。Base58 是一種基于文本的二進制編碼格式,用在比特幣和其它的加密貨幣中。這種編碼格式提供了緊湊表示,易讀性,錯誤檢測預防這幾方面彼此之間的平衡。Base58是Base64編碼格式的子集,同樣使用大小寫字母和10個數字,但舍棄了一些容易讀錯和在特定字體中外觀容易混淆的字符。具體地,Base58不含Base64中的0(數字0)、O(大寫字母o)、l(小寫字母 L)、I(大寫字母i),以及“+”和“/”兩個字符。簡而言之,Base58就是由不包括(0,O,l,I)的大小寫字母和數字組成。下面的例4-2是完整的Base58字母表。
例4-2 比特幣的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-6 Base58Check編碼:Base58、版本化和校驗和格式,用于對比特幣數據進行明確編碼
在比特幣中,大多數需要向用戶展示的數據都使用Base58Check編碼,因為它緊湊,易讀而且有錯誤檢驗。 Base58Check編碼中的版本前綴是用來創造易于辨別的格式Base58編碼時,該格式在base58check編碼的負載的開頭包含特定字符。這些字符使人們很容易了解別被編碼的數據的類型和使用方法。例如我們很容易看到,Base58Check編碼的比特幣地址是以1開頭的,而Base58Check編碼的私鑰WIF是以5開頭的。表4-1展示了一些版本前綴和他們對應的Base58格式。
表4-1 Base58Check版本前綴和編碼后的結果
|Type|Version prefix (hex)|Base58 result prefix|
| ---- | ---- | ---- |
|Bitcoin Address|0x00|1
|Pay-to-Script-Hash Address|0x05|3|
|Bitcoin Testnet Address|0x6F|m or n|
|Private Key WIF|0x80|5, K, or L|
|BIP-38 Encrypted Private Key|0x0142|6P|
|BIP-32 Extended Public Key|0x0488B21E|xpub
### 4.2.2 密鑰的格式
公鑰和私鑰都可以有多種格式。雖然看起來可能不同,但是所編碼的是同樣的數字。這些不同的編碼格式主要是用來方便人們無誤地閱讀和抄寫。
#### 4.2.2.1私鑰的格式
私鑰的格式有許多,所有這些都對應于相同的256位的數字。表4-2展示了私鑰的三種常見格式。不同的格式用在不同的場景。十六進制和原始的二進制格式用在軟件的內部,很少展示給用戶看。WIF格式用在錢包之間密鑰的輸入和輸出,也用于代表私鑰的二維碼(條形碼)。
表4-2 私鑰形式(編碼格式)
|Type |Prefix |Description|
| ---- | ---- |---- |
|Raw|None|32 bytes
|Hex|None|64 hexadecimal digits
|WIF|5|Base58Check encoding: Base58 with version prefix of 128- and 32-bit checksum
|WIF-compressed|K or L|As above, with added suffix 0x01 before encoding
下表表4-3顯示了三種格式的私鑰。
表4-3 示例:相同的私鑰,不同的格式
|Format |Private key
| ---- | ----
|Hex|1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
|WIF|5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
|WIF-compressed|KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
這些格式都表示相同的數字,相同的私鑰。雖然編碼后的字符串看起來不同,但任何一種格式都能很容易轉換為其他格式。請注意,“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.2.2 從Base58Check解碼
Bitcoin Explorer命令(參見本書附錄[appdx_bx])使我們很容易編寫shell腳本和命令行“管道”,處理比特幣密鑰,地址和交易。 Bitcoin Explorer命令行可以解碼Base58Check格式。
我們使用base58check-decode命令解碼未壓縮的密鑰:
```
$ bx base58check-decode 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
wrapper
{
checksum 4286807748
payload 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
version 128
}
```
結果包含密鑰有效內容(payload),WIF版本前綴128和校驗和。
請注意,壓縮密鑰的“有效內容”附加了后綴01,表示派生的公鑰要被壓縮:
```
$ bx base58check-decode KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
wrapper
{
checksum 2339607926
payload 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd01
version 128
}
```
#### 4.2.2.3 將十六進制轉換為Base58Check編碼
要轉換成Base58Check(與上一個命令相反),使用Bitcoin Explorer的base58check-encode命令(請參閱本書附錄[appdx_bx]),需要十六進制私鑰,后面跟WIF版本前綴128:
```
bx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd --version 128
5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
```
#### 4.2.2.4 將十六進制(壓縮格式密鑰)轉換為Base58Check編碼
要將壓縮格式的私鑰(參見“壓縮格式私鑰”一節)編碼為Base58Check,需要在十六進制私鑰的后面添加后綴01,然后使用跟上面一樣的方法:
```
$ bx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd01 --version 128
KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
```
生成的WIF壓縮格式的私鑰以字母“K”開頭,表明被編碼的私鑰中有一個后綴“01”,且該私鑰只能被用于生成壓縮格式的公鑰(參見“壓縮格式公鑰”一節)。
#### 4.2.2.5 公鑰的格式
公鑰也可以用多種不同格式來表示,通常分為非壓縮格式或壓縮格式公鑰這兩種形式。
我們從前文可知,公鑰是在橢圓曲線上的一個點,由一對坐標(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.2.6 壓縮格式公鑰
比特幣引入壓縮格式公鑰是為了減少交易的大小,從而節省運行區塊鏈數據庫的節點磁盤空間。大部分比特幣交易包含了公鑰,用于驗證用戶的憑證和支付比特幣。每個公鑰有520位(包括前綴,x坐標,y坐標)。如果每個區塊有數百個交易,每天有成千上萬的交易發生,區塊鏈里就會被寫入大量的數據。
正如“4.1.4 公鑰”一節所述,一個公鑰是一個橢圓曲線上的點(x, y)。而橢圓曲線實際是一個數學方程,曲線上的點實際是該方程的一個解。因此,如果我們知道了公鑰的x坐標,就可以通過解方程y<sup>2</sup> mod p = (x<sup>3</sup> + 7) mod p得到y坐標。這可以讓我們只存儲公鑰的x坐標,略去y坐標,從而將公鑰的大小和存儲空間減少了256位。這樣每筆交易需要的字節數減少了近一半,隨著時間推移,就能保存更多的交易數據。
未壓縮格式公鑰使用04作為前綴,而壓縮格式公鑰是以02或03作為前綴。為什么會有兩個前綴:因為橢圓曲線加密的公式的左邊是y<sup>2</sup> ,也就是說y的解是來自于一個平方根,可能是正值也可能是負值。更形象地說,y坐標可能在x坐標軸的上面或者下面。就像圖4-2的橢圓曲線圖中可以看出,曲線是對稱的,就像是x軸的鏡像。因此,如果我們略去y坐標,就必須儲存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.2.7 壓縮格式私鑰
實際上“壓縮格式私鑰”是一種名稱上的誤導,因為當私鑰使用WIF壓縮格式導出時,不但沒有壓縮,反而比“非壓縮格式”私鑰長出一個字節。這個多出來的一個字節是私鑰被加了后綴01,用以表明該私鑰是來自于一個較新的錢包,只能被用來生成壓縮公鑰。私鑰并沒有壓縮的,也不能被壓縮。“壓縮私鑰”實際上表示“只能生成壓縮公鑰的私鑰”,而“非壓縮私鑰”用來表明“只能生成非壓縮公鑰的私鑰”。為避免更多誤解,應該只可以說導出格式 是“WIF壓縮格式”或者“WIF”,而不能說這個私鑰是“壓縮”的。
表4-4展示了同樣的私鑰的WIF和WIF壓縮格式編碼。
表4示例:相同的密鑰,不同的格式
|Format |Private key
| ---- | ----
|Hex|1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD
|WIF|5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
|Hex-compressed|1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD01
|WIF-compressed|xFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
請注意,十六進制壓縮私鑰在末尾有一個額外的字節(十六進制為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 用C++實現密鑰和地址
我們回顧比特幣地址產生的完整過程,從私鑰、到公鑰(橢圓曲線上某個點)、再到雙重哈希地址,到最終的 Base58Check編碼。例4-3的C++代碼完整詳細的展示了從私鑰到Base58Check編碼后的比特幣地址的步驟。代碼中使用“3.3 其他客戶端、資料庫、工具包 ”一節中介紹的libbitcoin庫中的助手函數。
例4-3.從私鑰中創建Base58Check編碼的比特幣地址
link:code/addr.cpp[]
上述代碼使用預定義的私鑰在每次運行時產生相同的比特幣地址,如下例所示
例4-4 編譯并運行addr代碼
```
Compile the addr.cpp code
$ g++ -o addr addr.cpp $(pkg-config --cflags --libs libbitcoin)
Run the addr executable
$ ./addr
Public key: 0202a406624211f2abbdc68da3df929f938c3399dd79fac1b51b0e4ad1d26a47aa
Address: 1PRTTaJesdNovgne6Ehcdu1fpEdX7913CK
```
>**提示** 例4-4中的代碼從壓縮公鑰(參見上面的“壓縮公鑰”一節)生成了一個比特幣地址(1PRTT...)。如果使用了未壓縮公鑰,就會生成另外一個地址(14K1y...)。
## 4.4 用Python實現密鑰和比特幣地址
最全面的比特幣Python庫是 Vitalik Buterin寫的 pybitcointools。在例4-5中,我們使用pybitcointools庫(導入 為“bitcoin”)來生成和顯示不同格式的密鑰和比特幣地址。
例4-5 使用pybitcointools庫的密鑰和比特幣地址的生成和格式化
link:code/key-to-address-ecc-example.py[]
例4-6是上例代碼運行輸出的內容。
例4-6 運行key-to-address-ecc-example.py
```
$ 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的庫。
例4-7 用于比特幣密鑰的橢圓曲線算法演示腳本
link:code/ec-math.py[]
例4-8是上述腳本的輸出。
警告:例4-7中使用的os.urandom,表明的是底層操作系統的加密安全隨機數生成器(cryptographically secure random number generator (CSRNG) )。注意:根據操作系統的不同,os.urandom的實現缺乏足夠的安全性,也可能沒有正確的種子,并且不適合產生高質量的比特幣密鑰。
例4-8 安裝Python ECDSA 庫,運行腳本running the ec_math.py
```
$ # 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.5 高級密鑰和地址
在以下部分中,我們將看到密鑰和地址的高級形式,諸如加密私鑰、腳本和多重簽名地址,靚號地址,和紙錢包。
### 4.5.1 加密私鑰(BIP0038)
私鑰必須保密。私鑰的*保密性*需求的真實情況是,在實踐中相當難以實現,因為該需求與同樣重要的*可用性*安全目標相矛盾。當你為了避免私鑰丟失而存儲備份時,會發現維護私鑰私密性相當困難。通過密碼加密保存私鑰的錢包可能要安全一點,但那個錢包也需要備份。有時,例如用戶因為要升級或重裝錢包軟件,而需要把密鑰從一個錢包轉移到另一個。私鑰備份也可能需要存儲在紙張上(參見“紙錢包”一節)或者外部存儲介質里,比如U盤。但如果一旦備份文件失竊或丟失呢?這些相互矛盾的安全目標推進了便攜、方便、可以被眾多不同錢包和比特幣客戶端理解的加密私鑰標準BIP-38的出臺(BIP-38詳細可參見附錄部分)。
BIP-38提出了一個通用標準,使用一個口令加密私鑰并使用Base58Check對加密的私鑰進行編碼,這樣加密的私鑰就可以安全地保存在備份介質里,安全地在錢包間傳輸,保持密鑰在任何可能被暴露情況下的安全性。這個加密標準使用的是Advanced Encryption Standard(AES),該標準由NIST建立,并廣泛應用于商業和軍事應用的數據加密。
BIP-38加密方案是:使用一個比特幣私鑰作為輸入,該私鑰通常使用WIF編碼過,base58chek字符串的前綴“5”。BIP-38加密方案還需要一個長密碼作為口令,通常由多個單詞或一段復雜的數字字母字符串組成。BIP-38加密方案的結果是一個由 base58check編碼過的加密私鑰,前綴為6P。如果你看到一個6P開頭的的密鑰,這就意味著該密鑰是被加密過,并需要一個口令來轉換(解密)該密鑰,成為任何錢包可用的WIF格式的私鑰(前綴為5)。許多錢包應用現在能夠識別 BIP-38加密過的私鑰,會提示用戶輸入口令解碼導入密鑰。第三方應用,諸如非常好用基于瀏覽器的[Bit Address](http://bitaddress.org/) 的Wallet Details標簽,也能用來解碼BIP-38的密鑰。
BIP-38加密密鑰的最常見使用例子是紙錢包:在一張紙上備份私鑰。只要用戶選擇了強口令,使用BIP-38加密私鑰的紙錢包就非常安全,這也是一種很棒的比特幣離線存儲方式(也被稱作“冷存儲”)。
在bitaddress.org上測試表4-5中加密密鑰,看看輸入密碼如何得到加密密鑰。
表4-5 BIP-38加密私鑰例子
|Private Key (WIF)|5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
| ---- | ----
|Passphrase|MyTestPassphrase
|Encrypted Key (BIP-38)|6PRTHL6mWa48xSopbU1cKrVjpKbBZxcLRRCdctLJ3z5yxE87MobKoXdTsJ
### 4.5.2 P2SH (Pay-to-Script Hash)支付腳本哈希和多重簽名地址
正如我們所知,傳統的比特幣地址從數字1開頭,來源于公鑰,而公鑰來源于私鑰。雖然任何人都可以將比特幣發送到 一個1開頭的地址,但比特幣只有提供相應的私鑰簽名和公鑰哈希值后才能花費。
以數字3開頭的比特幣地址是P2SH地址,有時被錯誤的稱謂多重簽名或多重簽名地址。它們指定比特幣交易中受益人為腳本哈希,而不是公鑰的所有者。這個特性在2012年1月由BIP-16(參見附錄appdxbitcoinimpproposals)引進,正是因為BIP-16提供了向地址本身添加功能的機會而被廣泛地采納。不同于P2PKH(pay-to-public-key-hash,支付公鑰哈希),交易發送資金到傳統1開頭的比特幣地址,資金被發送到3開頭的地址時,要求的不僅僅是公鑰的哈希值和私鑰簽名來證明所有權。這些要求是在創建地址時在腳本中指定的,對這個地址的所有輸入都將使用相同的要求。
P2SH地址從交易腳本中創建,后者定義誰能花費這個交易輸出(參見“P2SH(Pay-to-Script-Hash)”一節)。對P2SH地址編碼使用的是與創建比特幣地址同樣的雙重哈希函數,只是應用在腳本而不是公鑰:
```
script hash = RIPEMD160(SHA256(script))
```
得到的"腳本哈希"使用Base58Check編碼,版本前綴為5,最后生成3開頭的編碼地址。一個P2SH地址例子是 3F6i6kwkevjR7AsAd4te2YB2zZyASEm1HM。可以使用比特幣瀏覽器命令script encode、sha256、ripemd160和base58check encode(參見附錄appdx_bx])導出,舉例如下:
```
$ echo \
'DUP HASH160 [89abcdefabbaabbaabbaabbaabbaabbaabbaabba] EQUALVERIFY CHECKSIG' > script
$ bx script-encode < script | bx sha256 | bx ripemd160 \
| bx base58check-encode --version 5
3F6i6kwkevjR7AsAd4te2YB2zZyASEm1HM
```
>**提示** P2SH不一定就是多重簽名的交易。雖然P2SH地址*通常都是*代表多重簽名,但也可能表示編碼其他類型交易的腳本。
#### 4.5.2.1 多重簽名地址和P2SH
目前,P2SH最常見的實現是多重簽名地址腳本。顧名思義,底層腳本需要多個簽名來證明所有權,才能花費資金。比特幣多重簽名功能的設計要求是總共N個密鑰中需要M個簽名(也稱為“閾值”),被稱為M-N多重簽名,其中M是等于或小于N。例如,第一章中提到的咖啡店主Bob使用的多重簽名地址,需要1-2簽名,一個是屬于他的密鑰,另一個是他妻子的密鑰,其中任何一方都可以簽名花費這個地址鎖定的輸出。這類似于傳統的銀行中的一個“聯合賬戶”,其中夫妻任何一方都可以單獨簽單消費。或就像Bob雇傭的網頁設計師Gopesh, 可能需要的是一個2-3的多簽名地址,可以確保至少兩個合伙人簽名了交易才可以繼續支付。
我們將會在第五章交易中探討如何構造P2SH(和多重簽名)地址花費資金的交易。
### 4.5.3 靚號地址
靚號地址也是有效的比特幣地址,只不過增加了可讀性。例如,1LoveBPzzD72PUXLzCkYAtGFYmK5vYNR33就是有效的地址,開頭的4個字符Base-58包含了字母love。靚號地址需要生成和測試幾十億個候選私鑰,直到一個符合模式要求的比特幣地址。雖然有一些優化過的靚號生成算法,但是這些方法必然涉及到隨機選擇一個私鑰,生成公鑰,再生成比特幣地址,并檢查是否與所要的靚號模式相匹配,需要重復數十億次,直到找到一個匹配。
一旦找到一個與所需模式匹配的靚號地址,那么這個靚號地址的私鑰就和其他地址一樣被所有者花費比特幣。靚號地址不比其他地址具有更多或更少的安全性。它們依靠和其他地址相同的橢圓曲線加密算法(ECC)和SHA。找一個靚號開頭的地址的私鑰并不比找其他地址容易。
在第一章中,我們介紹了Eugenia,一位菲律賓的兒童慈善機構負責人。我們假設Eugenia組織了一場比特幣募捐活動,并希望使用靚號比特幣地址來宣傳這個募捐活動。Eugenia將會創造一個以1Kids開頭的靚號地址來推廣兒童慈善募捐的活動。讓我們看看這個靚號地址如何被創建,以及對Eugenia慈善募捐的安全性有什么意義。
#### 4.5.3.1 生成靚號地址
應該意識到,比特幣地址不過是由Base58字母表中的符號表示的一組數字。尋找像“1kids”開頭的靚號就像從1Kids11111111111111111111111111111到1Kidszzzzzzzzzzzzzzzzzzzzzzzzzzzzz這個區間找一個地址。以“1kid”開頭的地址區間大約有58<sup>29</sup>個地址(大約是1.4 * 10<sup>51</sup>))。表4-6顯示了這些有“1kids”前綴的地址范圍。
表4-6 “1Kids”靚號的范圍
|From|1Kids11111111111111111111111111111
-|-
| |1Kids11111111111111111111111111112
||1Kids11111111111111111111111111113
||...
|To|1Kidszzzzzzzzzzzzzzzzzzzzzzzzzzzzz
我們把“1Kids”這個前綴當作數字,可以看看比特幣地址中這個前綴出現的頻率。一臺普通臺式電腦, 沒有任何特殊的硬件,大約每秒搜索大約10萬個密鑰。
表4-7 靚號(1KidsCharity)的出現的頻率以及生成所需時間
Length |Pattern |Frequency |Average search time
-|-|-|-
1|1Ki|1 in 58 keys|< 1 milliseconds
2|1Ki|1 in 3,364|50 milliseconds
3|1Kid|1 in 195,000|< 2 seconds
4|1Kids|1 in 11 million|1 minute
5|1KidsC|1 in 656 million|1 hour
6|1KidsCh|1 in 38 billion|2 days
7|1KidsCha|1 in 2.2 trillion|3–4 months
8|1KidsChar|1 in 128 trillion|13–18 years
9|1KidsChari|1 in 7 quadrillion|800 years
10|1KidsCharit|1 in 400 quadrillion|46,000 years
11|1KidsCharity|1 in 23 quintillion|2.5 million years
正如你所見,Eugenia并不能很快創造以“1KidsCharity”開頭的靚號地址,即使她有數千臺電腦同時進行運算。每增加一個字符就會增加58倍的計算難度。超過七個字符的靚號通常需要專用的硬件才能被找到,譬如用戶定制的多個GPU的臺式機。通常是使用無法繼續比特幣挖礦的礦機,被賦予了尋找靚號地址的任務。用GPU搜索靚號的速度比通用的CPU要快很多個量級。
另一種尋找靚號地址的方法是外包給一個靚號礦池,如[【Vanity Pool】](http://vanitypool.appspot.com/)。礦池提供一種服務,把有GPU硬件的人組織起來,通過為他人尋找靚號地址來獲得比特幣。Eugenia可以將搜索7位字符模式的靚號地址的工作外包出去,只需要很少的金額(本書寫作時大概是0.01比特幣或者5美元),幾個小時內就可以得到結果,而不用自己運行CPU搜索上幾個月。
生成一個靚號地址是一項通暴力破解的過程:嘗試一個隨機密鑰,檢查生成的地址是否和所需的模式相匹配,重復這個過程直到成功找到為止。例4-9是個靚號挖礦的例子,用C++程序寫的來尋找靚號地址的程序。這個例子運用到了我們在“其他替代客戶端、資料庫、工具包”一節介紹過的libbitcoin庫。
例4-9 靚號挖礦程序
link:code/vanity-miner.cpp[]
>**注釋** 下面的例4-10 使用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"來看最終需要的時長
$ time ./vanity-miner
Found vanity address! 1KidPWhKgGRQWD5PP5TAnGfDyfWp5yceXM
Secret: 2a802e7a53d8aa237cd059377b616d2bfcfa4b0140bc85fa008f2d3d4b225349
real 0m8.868s
user 0m8.828s
sys 0m0.035s
```
正如我們運行Unix命令所測出的運行時間所示,示例代碼要花幾秒鐘來找出匹配“kid”三個字符模式的結果。你可以嘗試在源代碼中改變search pattern,看一看四個字符或者五個字符需要花多久時間!
#### 4.5.3.2 靚號地址安全性
靚號地址其實就是一把雙刃劍,既可能增加安全,也可能削弱安全措施。用于改善安全性時,一個獨特的地址使競爭對手難以替換掉你的地址,欺騙你的客戶付款給他們。不幸的是,任何人都能創建一個*類似的*隨機地址,甚至另一個靚號地址,欺騙你的客戶。
Eugenia可以讓捐款人捐款到她的一個隨機生成地址(例如:1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy)。 她也可以生成一個獨特的以“1Kids”開頭的靚號地址。
針對這兩種情況,使用單一固定地址(而不是每筆捐款都用一個獨立的動態地址)的風險之一是騙子有可能會黑進你的網站,用他自己的地址取代你的地址,從而將捐贈轉移給他自己。如果你在不同的地方公布了你的捐款地址,你的用戶應該在付款之前認真檢查以確保這個地址跟在你的網站、郵件和傳單上看到的地址是同一個。如果是隨機地址 1j7mdg5rbqyuhenydx39wvwk7fslpeoxzy,普通用戶可能會只檢查頭幾個字符“1j7mdg”,就認為地址匹配。使用靚號地址生成器,那些想替換類似地址的騙子可能很快生成與前幾個字符相匹配的地址,如表4-8所示。
表4-8 生成匹配某隨機地址的多個靚號
Original Random Address|1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
-|-
|Vanity (4-character match)|1J7md1QqU4LpctBetHS2ZoyLV5d6dShhEy
Vanity (5-character match)|1J7mdgYqyNd4ya3UEcq31Q7sqRMXw2XZ6n
Vanity (6-character match)|1J7mdg5WxGENmwyJP9xuGhG5KRzu99BBCX
那靚號地址還能增加安全性嗎?如果Eugenia生成1Kids33q44erFfpeXrmDSz7zEqG2FesZEN的靚號地址,用戶可能看到靚號單詞和*后面的幾個字符*,例如注意到1Kids33。這樣就會迫使攻擊者至少生成6個字母相匹配的的靚號地址(比之前多2個字符),花費比Eugenia多3364倍(58<sup>2</sup>的努力。本質上,Eugenia付出的努力(或者支付給靚號池付出的)迫使攻擊者不得不生成更長的靚號模式。如果Eugenia花錢請礦池生成8個字符的靚號地址,攻擊者將會被迫生成10個字符,這將是個人電腦,甚至訂制的昂貴靚號挖礦機或靚號池也無法生成的。對Eugenia來說可能還能承擔的起,但對攻擊者來說卻一定是承受不起,特別如果欺詐的潛在回報不足以抵消生成靚號地址所需的費用。
### 4.5.4 紙錢包
紙錢包是打印在紙張上的比特幣私鑰。有時紙錢包為了方便起見也包括對應的比特幣地址,但這并不是必要的,因為地址可以從私鑰中派生。紙錢包是一個非常有效的建立備份或者線下存儲比特幣(即冷存儲)的方式。作為備份機制,紙錢包可以提供安全性,以防在電腦硬盤損壞、失竊或意外刪除的情況下造成密鑰的的丟失。作為冷存儲機制,如果紙錢包密鑰在線下生成并且從來沒在電腦中存儲過,那么在防范黑客攻擊,鍵盤記錄器,或其他在線電腦威脅方面會更加安全。
紙錢包有許多不同的形狀,大小,和外觀設計,但基本原則就是密鑰和地址打印在紙上。表4-9展現了紙錢包最基本的形式。
表4-9 比特幣紙錢包的私鑰和公鑰的打印形式
Public address| Private key (WIF)
-|-
1424C2F4bC9JidNjjTUZCbUxv6Sa1Mt62x|5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
使用工具,可以很容易地生成紙錢包,譬如使用bitaddress.org網站上的"client-side JavaScript generator"。這個頁面包含所有生成密鑰和紙錢包所必須的代碼,甚至在完全斷網的情況下,也可以生成密鑰和紙錢包。方法是,把HTML頁面保存在本地磁盤或外部U盤。然后斷開互聯網,用瀏覽器打開文件。更推薦,使用一個純凈版操作系統啟動電腦,比如光盤啟動的Linux系統。在脫機情況下使用這個工具所生成的密鑰,都可以通過USB線(不能用無線)在本地打印機上打印出來,這樣就創造了一個紙錢包,其中的密鑰只存在紙張上從未存儲在任何在線系統上。將這些紙錢包放置在防火保險柜內,然后發送比特幣到這個比特幣地址上,就實現了一個簡單但非常有效的冷存儲解決方案。圖4-8展示了通過bitaddress.org 生成的紙錢包。

圖4-8展示了通過bitaddress.org 生成的紙錢包
這個簡單的紙錢包系統的不足之處是那些被打印下來的密鑰容易被盜竊。一個能夠接近這些紙張的小偷只需偷走它或者用相機拍照,就能控制被這些密鑰鎖定的比特幣。一個更復雜的紙錢包存儲系統是使用BIP-38加密私鑰。所有者使用一個記在腦中的口令保護打印在紙錢包上的這些私鑰。沒有口令,這些被加密過的密鑰毫無用處。紙錢包仍舊優于用密碼保護的錢包,因為這些密鑰從沒有在線過,并且必須從保險箱或者其他物理安全存儲設備中取走。圖4-9展示了通過bitaddress.org 生成的加密紙錢包。

圖4-9 通過bitaddress.org 生成的加密紙錢包的例子,密碼:test
>**警告** 雖然你可以多次向紙錢包存款,但最好一次性提取里面所有的資金。因為在解鎖和花費資金的過程中,如果你沒有一次性全部提取,錢包會生成一個找零地址。如果簽名交易所用的計算機存在安全隱患,就有可能泄露私鑰,丟失找零地址中的資金。一次性提走所有資金可以減少私鑰泄露的風險,如果你所需的金額比較少,可以把剩余的資金一次性發送到一個新的紙錢包里。
紙錢包有許多設計形式和大小尺寸,還有許多不同的特征。還可以作為禮物送給別人,有季節性的主題,像圣誕節和新年主題。另一些設計用于存放在銀行金庫或帶有密碼保護的保險箱中,要么用不透明的刮擦標簽,要么用防篡改的膠紙折疊密封。圖4-10至圖4-12展示了幾個不同安全和備份功能的紙錢包的例子。

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

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

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