https://aaron67.cc/2019/01/22/bitcoin-wallet/
在收發比特币時會使用專門的桌面軟體或手機 App,這類應用程式統稱為比特币“錢包”。
通過之前文章的介紹,你知道:
- 區塊鍊記錄了比特币從誕生至今的所有交易
- 交易是 UTXO 集狀态變化的反映,交易會消耗之前建立的 UTXO,同時建立新的 UTXO(交易鍊條)
- UTXO 直接存儲在區塊鍊上,被鎖定腳本鎖定
- 比特币系統通過交易鍊條串聯起來的 UTXO 來表達某個位址上的所有交易活動(交易曆史),系統中沒有“賬戶”和“賬戶餘額”的概念
- 當我說“我有 1 個比特币”時,其實是在表達“被我的公鑰哈希鎖定的所有 UTXO 的面值之和,是 1 比特币”(不考慮 P2PKH 以外的交易類型)
- 私鑰意味着一切,掌握私鑰就擁有并可以支付其對應公鑰哈希上鎖定的比特币
是以,
- 比特币是直接記錄在區塊鍊上的,并不真正“存放”在錢包軟體裡
- 真正控制你比特币的是私鑰,錢包隻是幫你管理私鑰,這些私鑰控制的所有 UTXO 的面值之和,就是你的“賬戶餘額”
錢包軟體涵蓋的功能大緻有:
- 管理使用者的私鑰,可以導入已有私鑰,也可以建立新的私鑰
- 能從區塊鍊中查找出所有屬于該使用者的 UTXO,即被這些私鑰對應的公鑰哈希鎖定的所有 UTXO
- 在支付時能合理挑選或組合 UTXO 以滿足交易所需金額(之前文章中湊錢買可樂的例子),并計算交易費
- 能用私鑰簽名并向網絡廣播交易
了解錢包的工作原理十分必要,這能讓你更高效的使用軟體,更安全的“存儲”(其實是保護私鑰)和收發比特币。
這篇文章介紹比特币錢包的幕後細節。
一個位址隻用一次
我們常說,比特币交易是
- 公開的,區塊鍊記錄了所有的交易,全網公開可查
- 匿名的,沒有人知道誰控制了哪些位址,除非這個人主動承認并提供證明
- 不可篡改的,因為挖礦共識的存在,交易一旦被寫入區塊,随着區塊鍊的不斷延長(交易确認越來越多)重新計算這些區塊變得沒有可能
出于隐私保護的目的,一個比特币位址隻應被使用一次,這樣能避免别有用心之人根據已知資訊追蹤到你其他的交易活動(社會工程學)。
出于安全的考慮,也應該這麼做,當你不小心洩露了某個位址的私鑰時,不至于損失所有的比特币。
一般的,
- 每次收款都應使用不同的位址
- 支付時如果需要找零應找零到新位址,Alice 去 Bob 店裡買咖啡時的交易找零到了原位址,這是一個不好的例子
不确定性(随機)錢包
比特币的全節點軟體一般都包含錢包功能,這種錢包隻是随機生成私鑰的集合(JBOK,Just a Bunch of Keys),稱為不确定性錢包或随機錢包。
一個私鑰,會對應唯一的公鑰和位址,當你每次都使用新位址收發比特币時,會生成大量的私鑰。
這些私鑰之間彼此獨立,毫無關聯,意味着你需要經常備份用過的私鑰,否則一旦錢包軟體不可通路,你的比特币也會石沉大海。

由于在備份和使用時過于麻煩,不确定性錢包已不再被推薦使用,逐漸被确定性錢包取代。
另外,全節點軟體會下載下傳所有的區塊資料,如果你從頭開始,這将是一個漫長的同步過程,通常需要幾天時間,并占用掉幾百 G 的硬碟空間。
從功能實作的角度看,錢包隻需要“知道”那些與自己私鑰有關的交易和區塊便可正常工作,并不需要下載下傳所有的區塊資料。
現在的錢包軟體基本都是開箱即用的,隻需同步少量資料便可直接使用,十分友善。
确定性(種子)錢包
确定性錢包中的私鑰都可以從一個随機種子(Seed)計算出來,計算過程是單向的,你無法從私鑰計算出種子的内容。
這種錢包在備份和遷移時十分友善,備份一個種子就相當于備份了錢包中的所有私鑰,向新錢包中導入種子就可以恢複所有私鑰。
确定性錢包從邏輯上看,是下面的樣子。
分層确定性錢包
下列 BIP 共同定義了一種确定性錢包的實作,這種錢包被稱為分層确定性(HD,Hierarchical Deterministic)錢包。
- BIP-32,HD 錢包中的密鑰如何衍生
- BIP-39,HD 錢包助記詞(Mnemonic)和種子(Seed)的建立規則
- BIP-44,支援多币種和多賬戶的 HD 錢包
除此之外,還有
- BIP-43,多用途(purpose)HD 錢包的結構定義
- BIP-45,通過 P2SH 實作多簽的 HD 錢包
“分層”的意思是,錢包的中的私鑰具有層級關系,BIP-32 定義了私鑰間的樹形結構。
“确定性”的意思是,當種子(Seed)确定後,錢包中的所有私鑰便都是确定的,都可以從這個種子計算出來,相同的種子計算出的私鑰也都是相同的。
基于樹形結構,HD 錢包的一個父密鑰可以衍生出一系列子密鑰,每個子密鑰又可以繼續衍生出一系列孫密鑰,依次類推無限衍生下去,就像下圖所示的樣子。
現在主流的錢包軟體基本都是相容 BIP-32、BIP-39 和 BIP-44 的 HD 錢包。
從熵源建立助記詞(Mnemonic)
當你用 https://www.bitaddress.org/ 生成一個私鑰時,可以通過随意晃動滑鼠和敲擊鍵盤來引入更多的随機性。
錢包軟體在建立私鑰時,都需要引入類似這樣的随機性以保證密碼學上的私鑰安全。
對 HD 錢包來說也是一樣,一切計算工作都從一個随機序列開始。
- 取一串有 L L L 個二進制位的随機序列(熵)S1, L L L 可以是 128、160、192、224 或 256
- 對 S1 做 SHA256 運算,得到 S2
- 把 S2 的高 L 32 \frac{L}{32} 32L 位作為校驗和拼接在 S1 後,得到 S3,顯而易見,S3 的長度可以被 11 整除
L + L 32 = 33 L 32 = 11 × 3 L 32 L + \frac{L}{32} = \frac{33L}{32} = 11 \times \frac{3L}{32} L+32L=3233L=11×323L
- 從高位到低位,将 S3 每 11 位劃成一組,每組二進制序列都可以轉換成一個十進制數,表示單詞表中的行數
- BIP-39 定義的單詞表中有 2048 個單詞,每個單詞一行(行數從 0 開始計數),在表中查找這些行數上的單詞
- 将這些單詞按順序抄寫下來,就是助記詞
對下面 128 位的随機序列:
5e5d507dc5d543a8c7415656dac4ba0c
計算助記詞的過程為
# S1
5e5d507dc5d543a8c7415656dac4ba0c
# S2 = SHA256(S1)
# http://bit.ly/2Hv5Loe
055a1b4dc3af3267362e5d89b707fac6a94ef40a7be5f20f0940de178f01ea33
# 取高 4 位作為校驗和
# S3 = S1 + Checksum
5e5d507dc5d543a8c7415656dac4ba0c 0
# S3 的二進制串
01011110010111010101000001111101110001011101010101000011101010001100011101000001010101100101011011011010110001001011101000001100 0000
# 從高位到低位将 S3 每 11 位分成一組
01011110010 # 754
11101010100 # 1876
00011111011 # 251
10001011101 # 1117
01010100001 # 673
11010100011 # 1699
00011101000 # 232
00101010110 # 342
01010110110 # 694
11010110001 # 1713
00101110100 # 372
00011000000 # 192
從英語單詞表中查找這 12 個數對應的單詞,得到這個随機序列對應的助記詞為
furnace tunnel buyer merry feature stamp brown client fine stomach company blossom
注意,
- 随機序列是 HD 錢包初始化的起點
- 助記詞從随機序列計算得到,反過來,從助記詞也可以計算出随機序列的内容
從助記詞建立種子(Seed)
種子由助記詞計算而來,使用 PBKDF2(Password-Based Key Derivation Function 2)方法。
- PBKDF2 的第一個輸入是助記詞
- PBKDF2 的第二個輸入是鹽(Salt),由
和使用者指定的密語(Passphrase)拼接而成,這個密語是可選的mnemonic
- PBKDF2 使用 HMAC-SHA512 雜湊演算法,做 2048 次哈希運算來衍生輸入,産生一個 512 位的輸出,這個值就是 HD 錢包的種子
寫一段程式從助記詞計算種子。
package main
import (
"encoding/hex"
"fmt"
"github.com/tyler-smith/go-bip39"
)
func main() {
mnemonic := "furnace tunnel buyer merry feature stamp brown client fine stomach company blossom"
fmt.Println(hex.EncodeToString(bip39.NewSeed(mnemonic, "")))
fmt.Println(hex.EncodeToString(bip39.NewSeed(mnemonic, "bitcoin")))
}
// 輸出
// 2588c36c5d2685b89e5ab06406cd5e96efcc3dc101c4ebd391fc93367e5525aca6c7a5fe4ea8b973c58279be362dbee9a84771707fc6521c374eb10af1044283
// 1e8340ad778a2bbb1ccac4dd02e6985c888a0db0c40d9817998c0ef3da36e846b270f2c51ad67ac6f51183f567fd97c58a31d363296d5dc6245a0a3c4a3e83c5
使用這個 Node.js 庫,可以看到 PBKDF2 的更多細節。
const crypto = require('crypto');
const hash = 'sha512'
const round = 2048
const seed_bytes = 64
var mnemonic = 'furnace tunnel buyer merry feature stamp brown client fine stomach company blossom'
var passphrase = ''
var salt = 'mnemonic' + passphrase
crypto.pbkdf2(mnemonic, salt, round, seed_bytes, hash, (err, derivedKey) => {
if (err) throw err;
console.log(derivedKey.toString('hex'));
});
對于上面得到的助記詞,在密語為空時,計算出的種子為
2588c36c5d2685b89e5ab06406cd5e96efcc3dc101c4ebd391fc93367e5525aca6c7a5fe4ea8b973c58279be362dbee9a84771707fc6521c374eb10af1044283
如果密語為
bitcoin
,計算出的種子為
1e8340ad778a2bbb1ccac4dd02e6985c888a0db0c40d9817998c0ef3da36e846b270f2c51ad67ac6f51183f567fd97c58a31d363296d5dc6245a0a3c4a3e83c5
你能看到,
- 種子從助記詞和使用者密語計算而來
- 助記詞從一個随機序列計算而來,查閱特定的單詞表後最終确定
- 即使随機序列的内容一樣,查閱不同語言的單詞表,可以得到不同的助記詞,進而計算出不同的種子
- 即使助記詞的内容一樣,指定不同的密語,可以得到不同的種子
HD 錢包的确定性來源于種子,當種子确定後,錢包中的所有私鑰就都是确定的,都可以從種子計算出來。
是以你可以直接記錄下這個種子的值,作為 HD 錢包的備份,隻不過這一大串内容抄寫起來有點麻煩。
對一個 HD 錢包,初始化種子的過程涉及到兩個變量:
- 助記詞(由随機序列的内容和助記詞的語言共同決定)
- 使用者指定的密語
是以在備份 HD 錢包時,需要同時備份助記詞和密語,這樣就相當于備份了整個錢包内的所有私鑰。
HD 錢包中的私鑰是樹狀的層級結構。
- 樹根位置的私鑰,稱為主私鑰(Master Private Key),從種子直接計算得到
- 樹中的某個私鑰,從其父私鑰計算得到
從種子衍生主密鑰
BIP-32 定義,HD 錢包使用 HMAC-SHA512 方法從種子衍生主私鑰。
HMAC-SHA512 使用 SHA512 雜湊演算法,以一個消息(Message)和一個密鑰(Key)作為輸入,生成 512 位(64 位元組)的消息摘要(Digest)作為輸出。
從種子計算主私鑰時,種子作為輸入的消息,字元串
Bitcoin seed
作為輸入的密鑰,計算産生 512 位的輸出。
- 輸出的高 256 位,是主私鑰
- 輸出的低 256 位,是主鍊碼(Master Chain Code)
package main
import (
"crypto/hmac"
"crypto/sha512"
"encoding/hex"
"fmt"
"github.com/tyler-smith/go-bip39"
)
func main() {
mnemonic := "furnace tunnel buyer merry feature stamp brown client fine stomach company blossom"
seed := bip39.NewSeed(mnemonic, "")
fmt.Println(hex.EncodeToString(seed))
hmacSHA512 := hmac.New(sha512.New, []byte("Bitcoin seed"))
hmacSHA512.Write(seed)
digest := hmacSHA512.Sum(nil)
fmt.Println("Master private key\t" + hex.EncodeToString(digest[:32]))
fmt.Println("Master chain code\t" + hex.EncodeToString(digest[32:]))
}
// 輸出
// 2588c36c5d2685b89e5ab06406cd5e96efcc3dc101c4ebd391fc93367e5525aca6c7a5fe4ea8b973c58279be362dbee9a84771707fc6521c374eb10af1044283
// Master private key 116c2daffad72d24cd3c122a65f937ec2743f98952e174ae158bf6ea70c78954
// Master chain code a74b75701aba81dd29e94226696cc0e67d6a5f29398d151c05c09c416dbf0865
對剛才的種子,計算出的主私鑰為
116c2daffad72d24cd3c122a65f937ec2743f98952e174ae158bf6ea70c78954
主鍊碼為
a74b75701aba81dd29e94226696cc0e67d6a5f29398d151c05c09c416dbf0865
這個從種子衍生出的主私鑰,跟之前文章介紹的{% post_link bitcoin-keys 比特币私鑰 %}沒有任何差別,通過 Secp256k1 橢圓曲線乘法,可以計算出其對應的主公鑰(Master Public Key):
02c8022cf8de6472f50f08b8b7e364536ab78e25333e0d1e39c0fbf37978ff2f0f
子密鑰的衍生方法
HD 錢包中的每個密鑰(私鑰和公鑰)都有 2 32 2^{32} 232 個子密鑰。
每個子密鑰都用一個序号辨別,表示它是這個父密鑰衍生出的第幾個子密鑰,序号從 0 開始計數。
- 序号在 [ 0 , 2 31 − 1 0, 2^{31} - 1 0,231−1] 範圍的衍生,稱為正常衍生(Normal Derivation)
- 序号在 [ 2 31 , 2 32 − 1 2^{31}, 2^{32} - 1 231,232−1] 範圍的衍生,稱為硬化衍生(Hardened Derivation)、增強衍生或強化衍生
子密鑰衍生(CKD,Child Key Derivation)算法由 BIP-32 定義。
一般的,在衍生子密鑰時,将父密鑰、序号和父鍊碼作為 CKD 的輸入,輸出一個 256 位的子密鑰和一個 256 位的子鍊碼。
這個子鍊碼會在這個子密鑰衍生子密鑰時,作為 CKD 的輸入。
寫個程式算一下,主私鑰
116c2daffad72d24cd3c122a65f937ec2743f98952e174ae158bf6ea70c78954
的第 0 個和第 1 個子密鑰。
package main
import (
"encoding/hex"
"fmt"
"github.com/tyler-smith/go-bip32"
"github.com/tyler-smith/go-bip39"
)
func main() {
mnemonic := "furnace tunnel buyer merry feature stamp brown client fine stomach company blossom"
seed := bip39.NewSeed(mnemonic, "")
masterKey, _ := bip32.NewMasterKey(seed)
fmt.Println("Master private key\t" + hex.EncodeToString(masterKey.Key))
fmt.Println("Master public key\t" + hex.EncodeToString(masterKey.PublicKey().Key))
fmt.Println("Master chain code\t" + hex.EncodeToString(masterKey.ChainCode))
InspectChildKey(masterKey, 0)
InspectChildKey(masterKey, 1)
}
func InspectChildKey(parentKey *bip32.Key, index uint32) {
childKey, _ := parentKey.NewChildKey(index)
fmt.Println(fmt.Sprintf("Child %d private key\t%s", index, hex.EncodeToString(childKey.Key)))
fmt.Println(fmt.Sprintf("Child %d public key\t%s", index, hex.EncodeToString(childKey.PublicKey().Key)))
fmt.Println(fmt.Sprintf("Child %d chain code\t%s", index, hex.EncodeToString(childKey.ChainCode)))
}
// 輸出
// Master private key 116c2daffad72d24cd3c122a65f937ec2743f98952e174ae158bf6ea70c78954
// Master public key 02c8022cf8de6472f50f08b8b7e364536ab78e25333e0d1e39c0fbf37978ff2f0f
// Master chain code a74b75701aba81dd29e94226696cc0e67d6a5f29398d151c05c09c416dbf0865
// Child 0 private key 104eedfeeaa8b2c1217c721f375ece3e6981501c7ccd17fcb58867816d01c1b9
// Child 0 public key 0272fed87974babeee6d01b918d87dcd16d5eabc7eab43c66a547ffea47229563a
// Child 0 chain code e2b3998b1df51120f87da3eee1ab6e8b2afb82234d4d6ae62d6e50570e72f737
// Child 1 private key 7d008006853eea7982e25b7ea325a049161ffa3017c5b80095eda8bc1a2ffb98
// Child 1 public key 02473bb5ace2f2e2dce4bf7cb3b89ae329c85612753bcb02db5e5d70d95e86e776
// Child 1 chain code e2061c5a048003ab0c2060761d1452befb2cba0df9f6f4dc17a8db3ec09114b4
根據 BIP-32 的定義,
- 可以從私鑰和鍊碼,衍生出其所有的子私鑰及對應的子公鑰(及之後每層所有的子私鑰及對應的子公鑰)
- 可以從公鑰和鍊碼,衍生出其正常衍生的子公鑰(及之後每層正常衍生的子公鑰)
- 無法從某個密鑰(公鑰和私鑰)計算出其父密鑰,或同層的其他兄弟密鑰
擴充密鑰
衍生子密鑰時需要将密鑰、鍊碼和子密鑰序号作為 CKD 的輸入,三者缺一不可。
為了友善轉錄,可以将密鑰和鍊碼編碼在一起,得到擴充密鑰(Extended Key)。
擴充密鑰使用 Base58Check 編碼,并添加特定的版本字首。
類型 | 版本字首的值(十六進制) | Base58Check之後的字首 |
---|---|---|
擴充私鑰 | 0488ade4 | xprv |
擴充公鑰 | 0488b21e | xpub |
下面的程式計算例子中的主密鑰、主密鑰的第 0 個子密鑰和主密鑰的第 1 個子密鑰這三者的擴充密鑰。
package main
import (
"fmt"
"github.com/tyler-smith/go-bip32"
"github.com/tyler-smith/go-bip39"
)
func main() {
mnemonic := "furnace tunnel buyer merry feature stamp brown client fine stomach company blossom"
seed := bip39.NewSeed(mnemonic, "")
masterKey, _ := bip32.NewMasterKey(seed)
firstChildKey, _ := masterKey.NewChildKey(0)
secondChildKey, _ := masterKey.NewChildKey(1)
fmt.Println("Master Extended private key\t", masterKey)
fmt.Println("Master Extended public key\t", masterKey.PublicKey())
fmt.Println("Child 0 Extended private key\t", firstChildKey)
fmt.Println("Child 0 Extended public key\t", firstChildKey.PublicKey())
fmt.Println("Child 1 Extended private key\t", secondChildKey)
fmt.Println("Child 1 Extended public key\t", secondChildKey.PublicKey())
}
// 輸出
// Master Extended private key xprv9s21ZrQH143K3ixinZag69usQ2CMqDbEkm74p3PWY2ecvUkaiwPGbykMNLfAEwakjwbexs6kKrCDQCGp5vV4yJziz6XB47smbBCmsYhP85Z
// Master Extended public key xpub661MyMwAqRbcGD3Btb7gTHrbx42rEgK67z2fcRo86NBboH5jGUhX9n4qDd5LbW56NE3NVxupasmRwZnzN9cNvkvWiG4ZFjY8HqGi1bPXuUR
// Child 0 Extended private key xprv9vcPiWn5V1vJdpNtY5qBb5a9xZd8PxT9beX8fYS4eVxm7vrw52N49uwUqHz5vEQjt28o5MbRT4VZaasJQrBUEWxjGhmcb4LVktACFoJ8DyS
// Child 0 Extended public key xpub69bk82JyKPUbrJTMe7NBxDWtWbTcoRAzxsSjTvqgCqVjzjC5cZgJhiFxgZmKC676T9tuYAvCTJJq73i114XkMTnJ7o14yfx7tbQ6GVf7D4a
// Child 1 Extended private key xprv9vcPiWn5V1vJgMpAdK5KHc9BZYvwfwEJnmU9PnjxCfpTWoNVsg1PPM1rsnseesNdCCoiH3ZW3BVZLuEqskmNnt4jEqhZ79EerwzPR3LvXQo
// Child 1 Extended public key xpub69bk82JyKPUbtqtdjLcKek5v7amS5PxA9zPkCB9Zm1MSPbheRDKdw9LLj3VjeRyCkm8gtfzhzmD6sotgKkD5Dn3KhK1NyYEHACc2cSqrsTb
擴充密鑰使用友善,但要注意:
- 雖然洩露某個擴充公鑰不會丢币,但會導緻以此為根節點衍生出的擴充公鑰全部洩露,破壞了隐私性
- 洩露擴充公鑰和該公鑰衍生出的之後任一代公鑰對應的私鑰,有被推導出該擴充公鑰所有後代私鑰的可能
基于多一層安全的考慮,BIP-32 定義了兩種子密鑰衍生方案。
- 正常衍生,從擴充公鑰隻能衍生出前 2 31 2^{31} 231 個子公鑰
- 硬化衍生,後 2 31 2^{31} 231 個子公鑰隻能從擴充私鑰衍生(從擴充私鑰能衍生其之後的所有子密鑰)
衍生路徑
為了能友善表示密鑰間關系,定義了衍生路徑(Derivation Path)的概念。
- 序号之間以
分隔/
-
表示主密鑰m
-
表示第 i i i 個正常衍生的子密鑰,即第 i i i 個子密鑰i
-
表示第 i i i 個硬化衍生的子密鑰,即第 ( 2 31 + i ) (2^{31} + i) (231+i) 個子密鑰i'
m/0'/1'/2
表示主密鑰的第 0 個強化衍生子密鑰的第 1 個強化衍生子密鑰的第 2 個正常衍生子密鑰(樹形結構)。
擴充密鑰加上衍生路徑,可以确定 HD 錢包裡的一個密鑰及從這個密鑰衍生的之後所有層的子密鑰(以這個密鑰為根的子樹)。
HD 錢包裡的密鑰是樹形結構,可以無限層衍生下去,為了能讓不同錢包之間互相相容,BIP-44 對衍生路徑提出了一個規範建議。
m / purpose' / coin_type' / account' / change / address_index
-
總是設為 44,代表錢包遵循 BIP-44 規範purpose
-
代表币種(對應關系),Bitcoin(BTC)用 0 ,Bitcoin Cash(BCH)用 145,Bitcoin SV(BSV)用 236coin_type
-
代表邏輯上的錢包“賬戶”,從 0 開始計數account
-
代表位址類型,為 0 表示是收款位址,為 1 表示是找零位址change
-
是位址索引,從 0 開始計數,表示是第幾個位址address_index
我初始化了一個 HD 錢包,使用衍生路徑
m/44'/236'/0'
作為存放 Bitcoin SV(BSV)的“賬戶”,那麼,
- 我的第一個收款位址是公鑰
對應的位址,第二個收款位址是公鑰m/44'/236'/0'/0/0
對應的位址,以此類推m/44'/236'/0'/0/1
- 當我完成第一次支付并存在找零時,會找零到位址
,下一次支付找零到的位址會是m/44'/236'/0'/1/0
,以此類推m/44'/236'/0'/1/1
- 如果想再建立一個 BSV “賬戶”另作他用,可以使用路徑
m/44'/236'/1'
- 如果還想用這個 HD 錢包存放 BCH,可以使用路徑
m/44'/145'/0'
注意,BIP-44 不是強制标準,你可以随意使用任何衍生路徑,隻要在備份 HD 錢包的時候務必記住這個路徑就好。
在做一些有關 HD 錢包細節的計算時,https://iancoleman.io/bip39/ 這個工具非常不錯,推薦給你。
HD 錢包的優勢
HD 錢包在備份時十分友善。
- 隻需要備份助記詞和密語,就等于備份了整個錢包内的所有私鑰
- 除此之外,你還要記下使用的衍生路徑,這樣才能知道使用了哪些私鑰
另外,從擴充公鑰可以正常衍生子公鑰及對應位址而不用通路擴充私鑰或私鑰本身,這是 HD 錢包一個很重要的安全特性。
密鑰間的樹形結構,與機構的部門設定十分相似,如果一家企業準備使用比特币進行财務收支,可以:
- 将路徑
的擴充公鑰交給各銷售部門獨自管理和使用m/0'/0'/x'
- 銷售部門可以為每筆訂單生成不同的收款位址,友善狀态跟蹤
- 因為從擴充公鑰無法衍生出子私鑰,是以銷售部門隻能收款而無法支付賬戶裡的比特币
- 将路徑
的擴充公鑰交給市場部,市場部可以查閱所有訂單的銷售記錄,同樣無法支付比特币m/0'/0'
- 将路徑
的擴充私鑰交給财務部,财務部可以用這個更上層的擴充私鑰,管理整個公司的加密資産m/0'/0'
配合 BIP-45 定義的 HD 錢包多簽方案,可以友善、安全、靈活的管理公司的加密資産。
WIF
還記得{% post_link bitcoin-keys 《比特币的私鑰和公鑰》 %}文章最後留下的問題嗎?
WIF 壓縮和不壓縮格式表示的私鑰,其結果從長度上看并沒有明顯的差別,為什麼要這麼做
我們常說,一個公鑰會對應一個确定的位址,因為位址是公鑰哈希的編碼。但比特币公鑰可以用不壓縮格式和壓縮格式兩種方法表示,這樣可以計算出兩個不同的公鑰哈希,對應到兩個位址。
對于私鑰
98fd2a819a382f8e142e38242f6caf2a2f6f58e7fe6ca5f23c5b0818b15b4ba6
,對應的公鑰為
x = da52d817a5ae3555f36a94528322eb47016f1334b798f5b4fa614a892dabb3ea
y = f314bf38816673c55c1708cf1e36b55c936db97618d6460c4d223be83bec7788
如果用不壓縮格式表示公鑰,公鑰為
04 da52d817a5ae3555f36a94528322eb47016f1334b798f5b4fa614a892dabb3ea f314bf38816673c55c1708cf1e36b55c936db97618d6460c4d223be83bec7788
對應的 P2PKH 位址為
1B4nPuT41LBxzumQqhrPDsd4NF7DZSWgyQ
。
如果用壓縮格式表示公鑰,公鑰為
02 da52d817a5ae3555f36a94528322eb47016f1334b798f5b4fa614a892dabb3ea
對應的 P2PKH 位址為
1BJhat1AMGYbT9HYJxVekoCaPaqB9ZyTyF
。
對于早期的錢包軟體,都是直接使用不壓縮格式的公鑰,計算對應的位址用于收款。
後來,人們發現公鑰可以用壓縮的格式存儲,這樣可以節省約一半的存儲和傳輸空間,錢包開始逐漸使用壓縮格式的公鑰。
如果一個錢包軟體支援兩種格式的公鑰,當你向錢包裡導入私鑰時,錢包就懵逼了,由于不知道你原來使用的是什麼格式的公鑰,是以需要在區塊鍊裡搜尋這兩個位址上鎖定的 UTXO 進而計算出正确的“賬戶餘額”,這會帶來混亂。
為了向後相容,定義了 WIF 壓縮格式。
- 在實作了壓縮格式公鑰的較新的錢包中,私鑰隻能且永遠被導出為 WIF 壓縮格式(以
或K
為字首)L
- 對于較老的沒有實作壓縮格式公鑰的錢包,私鑰隻能被導出為 WIF 不壓縮格式(以
為字首)5
這樣做的目的就是為了給導入這些私鑰的錢包一個信号:錢包需要使用什麼格式的公鑰來計算位址,搜尋區塊鍊。
總結
使用“錢包”軟體,能友善的收發比特币。
早期的錢包都是離散私鑰錢包,包含在全節點軟體中。
- 離散錢包中的私鑰之間彼此毫無關聯,備份和使用時有諸多不便
- 全節點軟體會同步所有的區塊資料,這是一個十分耗時的操作,對錢包來說,也并不需要全部的區塊資料
确定性錢包從一個種子衍生出錢包内的所有私鑰,分層确定性錢包主要由 BIP-32、BIP-39 和 BIP-44 共同定義。
- BIP-32,為了避免管理一堆私鑰的麻煩,定義錢包内密鑰的樹形結構及分層衍生方案
- BIP-39,定義助記詞,讓備份種子更友善
- BIP-44,對 BIP-32 的衍生路徑提出規範建議
HD 錢包的幕後細節涉及到很多内容,希望這篇文章能幫你了解它們。
- 助記詞(Mnemonic)
- 從随機序列(熵)建立助記詞
- 種子(Seed)
- 主密鑰(Master Key)
- 從助記詞建立種子(PBKDF2),從種子衍生主密鑰(HMAC-SHA512),子密鑰的衍生方法(CKD)
- 擴充密鑰(Extended Key)
- 正常衍生(Normal Derivation)和硬化衍生(Hardened Derivation)
- 衍生路徑(Derivation Path)
- HD 錢包的備份
參考
- 精通比特币(第二版)譯文 原文
- PBKDF2 算法概述
- 了解開發HD 錢包涉及的 BIP32、BIP44、BIP39
- 數字貨币錢包 - 助記詞 及 HD 錢包密鑰原理
- ELI5: What’s the difference between a child-key and a hardened child-key in BIP32
- BIP-32
- BIP-39
- BIP-44
- tyler-smith/go-bip39