概述
比特幣中錢包并不是傳統(tǒng)意義的錢包誉裆,它不包含比特幣勾给,僅僅包含密鑰训柴。每個用戶都有一個包含多個密鑰的錢包哑舒,錢包只包含私鑰/公鑰對的密鑰鏈。用戶用密鑰簽名交易幻馁,從而證明他們擁有交易輸出洗鸵,最終花費比特幣。關(guān)于交易輸出的概念宣赔,可以查看 《比特幣那些事(2)——交易》预麸。
錢包
比特幣錢包根據(jù)其包含的多個密鑰是否相互關(guān)聯(lián),可以分為兩種類型:
- 非確定性錢包(Nondeterministic Wallet)
- 確定性錢包(Deterministic Wallet)
非確定性錢包中的所有密鑰都是由 隨機(jī)數(shù) 獨立生成的儒将。密鑰之間彼此無關(guān)吏祸,因此也稱為 “Just a Bunch of Keys”,簡稱 JBOK 錢包钩蚊。
確定性錢包中的所有密鑰都是從一個 主密鑰 派生出來的贡翘。主密鑰也稱為 種子(Seed)。確定性錢包中所有密鑰相互關(guān)聯(lián)砰逻,如果有原始種子鸣驱,則可以再次生成全部密鑰。
非確定性錢包
在早期的比特幣客戶端(Bitcoin Core蝠咆,也稱比特幣核心客戶端)中踊东,錢包只是隨機(jī)生成的私鑰集合。隨機(jī)密鑰的缺點就是如果你生成很多私鑰刚操,就必須保存它們所有的副本闸翅。每一個密鑰都必須備份,否則一旦錢包不可訪問時菊霜,錢包所控制的資金就付之東流坚冀。
確定性錢包
確定性錢包通過使用單項離散函數(shù)從公共的種子生成的私鑰。種子是隨機(jī)生成的數(shù)字鉴逞。在確定性錢包中记某,種子可以恢復(fù)所有的已經(jīng)生成的私鑰司训,因此,只要在初始創(chuàng)建時對種子進(jìn)行備份就可以了液南。
分層確定性錢包
確定性錢包使用了許多不同的密鑰推導(dǎo)方法壳猜。最常用的推導(dǎo)方法是使用樹狀結(jié)構(gòu),稱為 分層確定性錢包(Hierarchical Deterministic Wallet贺拣,簡稱 HD 錢包)蓖谢。在 HD 錢包中,父密鑰可以衍生出一系列子密鑰譬涡,每個子密鑰又可以衍生出一系列孫密鑰闪幽,以此類推,無限衍生涡匀。
舉例
下面盯腌,我們通過一個例子來介紹 HD 錢包的實現(xiàn)原理。
Alice 經(jīng)營了一家網(wǎng)絡(luò)商店銷售T恤陨瘩。她使用 Trezor 比特幣硬件錢包(硬件 HD 錢包)來管理她的比特幣腕够。
Alice 首次使用 Trezor 時,設(shè)備從內(nèi)置的硬件隨機(jī)數(shù)生成器生成 助記詞舌劳。錢包會在屏幕上按順序逐個顯示助記詞帚湘。通過記下這些助記符,Alice 創(chuàng)建了一個備份甚淡,如下所示大诸。
1 | army | 2 | van | 3 | defense | 4 | carry | 5 | jealous | 6 | true |
---|---|---|---|---|---|---|---|---|---|---|---|
7 | garbage | 8 | claim | 9 | echo | 10 | media | 11 | make | 12 | crunch |
注:這里舉例顯示了 12 個助記詞。事實上贯卦,大多數(shù)硬件錢包會生成更安全的 24 個助記詞资柔。
工作原理
HD 錢包的密鑰推導(dǎo)主要包括以下幾個步驟:
- 創(chuàng)建助記詞
- 創(chuàng)建種子
- 創(chuàng)建錢包
下面我們依次來介紹這個三個主要步驟。
創(chuàng)建助記詞
BIP-39 是助記詞行業(yè)標(biāo)準(zhǔn)撵割,定義了助記詞和種子的創(chuàng)建贿堰。助記詞是由錢包使用 BIP-39 中定義的標(biāo)準(zhǔn)化過程自動生成的。助記詞的生成主要包含以下這些步驟:
- 創(chuàng)建一個 128 至 256 位的隨機(jī)序列(熵)
- 提取隨機(jī)序列哈希值的前幾位(隨機(jī)序列長度/32)啡彬,作為隨機(jī)序列的校驗和
- 將校驗和拼接至隨機(jī)序列的末尾
- 將序列進(jìn)行分割成多個單元羹与,每個單元占 11 位
- 將每個單元的值映射到一個包含 2048(2^11)個單詞的字典
- 映射得到有順序的單詞組,即助記詞
根據(jù)上述助記詞的生成步驟庶灿,可以推測出隨機(jī)序列(熵)與助記詞長度的關(guān)系注簿,如下表所示:
Entropy(bits) | Checksum(bits) | Entropy + Checksum(bits) | Mnemonic Length(words) |
---|---|---|---|
128 | 4 | 132 | 12 |
160 | 5 | 165 | 15 |
192 | 6 | 198 | 18 |
224 | 7 | 231 | 21 |
256 | 8 | 264 | 24 |
創(chuàng)建種子
助記詞創(chuàng)建之后,可以通過密鑰延伸函數(shù) PBKDF2 進(jìn)一步生成種子跳仿。
密鑰延伸函數(shù) PBKDF2 有兩個參數(shù):助記詞、鹽 (Salt)捐晶。鹽的目的是為了增加暴力攻擊的難度菲语。
種子的生成主要包含以下步驟:
- PBKDF2 密鑰延伸函數(shù)的第一個參數(shù)是助記詞妄辩。
- PBKDF2 密鑰延伸函數(shù)的第二個參數(shù)是鹽。鹽由助記詞和可選的用戶提供的密碼組成山上。
- PBKDF2 密鑰延伸函數(shù)內(nèi)部使用 HMAC-SHA512 算法眼耀,進(jìn)行 2048 次哈希運算,生成一個 512 位的種子佩憾。
下表所示為示例的 128 位熵轉(zhuǎn)換成 512 位種子的結(jié)果哮伟。
Entropy(128 bits) | 0c1e24e5917779d297e14d45f14e1a1a |
---|---|
Mnemonic(12 words) | army van defense carry jealous true garbage claim echo media make crunch |
Passphrase | (none) |
Seed(512 bits) | 5b56c417303faa3fcba7e57400e120a0ca83ec5a4fc9ffba757fbe63fbd77a89a1a3be4c67196f57c39a88b76373733891bfaba16ed27a813ceed498804c0570 |
創(chuàng)建錢包
錢包的創(chuàng)建主要包含以下幾項工作:
- 創(chuàng)建主私鑰,即密鑰樹的根
- 創(chuàng)建子私鑰
- 創(chuàng)建子公鑰
下面我們依次介紹這幾個步驟妄帘,在介紹完之后楞黄,我們再來看看其中存在的安全風(fēng)險,進(jìn)而介紹硬件子私鑰的創(chuàng)建抡驼。
創(chuàng)建主私鑰
HD 錢包的確定性源自于 根種子(Root Seed)鬼廓,即上述過程所生成的種子。
根種子通過 HMAC-SHA512 算法可生成 512 位哈希值致盟。該將哈希值分成左右兩部分碎税,分別得到:
- 主私鑰(m)(Master Private Key(m)):主私鑰(m)通過橢圓曲線算法可以生成 主公鑰(M)(Master Public Key(M))。
- 主鏈碼(Master Chain Code)
創(chuàng)建子私鑰
我們知道 HD 錢包采用樹狀結(jié)構(gòu)進(jìn)行密鑰推導(dǎo)馏锡。在樹狀的密鑰結(jié)構(gòu)中雷蹂,除了主密鑰是通過根種子推導(dǎo)的,其他層級的密鑰都是通過其母密鑰推導(dǎo)的杯道,采用 子密鑰衍生函數(shù) CKD (Child Key Derivation)匪煌。
子密鑰衍生函數(shù)的調(diào)用需要三個參數(shù):
- 母私鑰
- 母鏈碼
- 索引號:32 位的值,因此每個母私鑰可以推導(dǎo)出 2^32 個子私鑰蕉饼。
子私鑰的具體衍生過程:根據(jù)母私鑰推導(dǎo)出母公鑰虐杯,將 母公鑰-母鏈碼-索引號 合并后使用 HMAC-SHA512 算法并結(jié)合 母私鑰 生成 512 位哈希值。將該哈希值繼續(xù)拆分成左右兩部分昧港,分別得到:
- 子私鑰
- 子鏈碼
上述子密鑰衍生函數(shù)所使用的三個參數(shù)擎椰,其中母私鑰和母鏈碼的結(jié)合被稱為 擴(kuò)展私鑰(Extended Private Key)。通過上述子私鑰推導(dǎo)的原理创肥,可以知道:一個擴(kuò)展密鑰作為 HD 錢包中密鑰樹的一個分支达舒,可以衍生出該分支下的所有密鑰。
與 擴(kuò)展私鑰 相對應(yīng)是 擴(kuò)展公鑰(Extended Public Key)叹侄,它由 母公鑰 和 母鏈碼 組成巩搏,可用于通過母公鑰直接創(chuàng)建子公鑰。
創(chuàng)建子公鑰
分層確定性錢包還有一個特點是:可以不通過私鑰而直接從母公鑰衍生出子公鑰趾代。這就給我們提供了兩種衍生子公鑰的方法:
- 通過子私鑰衍生子公鑰
- 通過母公鑰衍生子公鑰
子公鑰的具體衍生過程(利用擴(kuò)展公鑰):將 母公鑰-母鏈碼-索引號 合并后使用 HMAC-SHA512 算法并結(jié)合 母公鑰 生成 512
位哈希值贯底。將該哈希值繼續(xù)拆分成左右兩部分,分別得到:
- 子私鑰
- 子鏈碼
這種子公鑰的衍生過程不涉及任何私鑰撒强,運用到實際場景禽捆,可以實現(xiàn)私鑰和公鑰的分開管理笙什。比如:在電商場景中,網(wǎng)絡(luò)服務(wù)器僅維護(hù)公鑰樹結(jié)構(gòu)胚想,給每一筆交易創(chuàng)建一個比特幣地址(只能接收比特幣琐凭,而不能花費比特幣)。為了安全起見浊服,網(wǎng)絡(luò)服務(wù)器不會有任何私鑰统屈。電商服務(wù)器則維護(hù)了私鑰樹結(jié)構(gòu),保證比特幣的花費權(quán)在自己手上牙躺。
創(chuàng)建硬化子私鑰
現(xiàn)在愁憔,我們深究一下上述子私鑰和子公鑰的創(chuàng)建方式,它們分別使用了 擴(kuò)展私鑰 和 擴(kuò)展公鑰述呐。這兩種擴(kuò)展密鑰都包含了相同的母鏈碼惩淳。這時候就可能存在安全風(fēng)險:由于擴(kuò)展公鑰包含母鏈碼,如果子私鑰泄露了乓搬,攻擊者就可以通過擴(kuò)展公鑰的母鏈碼和子私鑰組成一個擴(kuò)展私鑰思犁。那么,該分支下的所有私鑰都會泄露进肯。更糟糕的是激蹲,子私鑰與母鏈碼可以用來推斷母私鑰。
為了應(yīng)對這種風(fēng)險江掩,HD 錢包使用一種叫做 硬化衍生(Hardened Derivation)衍生函數(shù)学辱。其本質(zhì)就是讓子私鑰衍生和子公鑰衍生使用不同的鏈碼。
具體實現(xiàn)是 使用母私鑰去推導(dǎo)子鏈碼环形。非硬化子私鑰衍生則是使用 母公鑰去推導(dǎo)子鏈碼策泣。
核心源碼
如下所示為比特幣開源庫 BitcoinKit 中關(guān)于密鑰推導(dǎo)的核心方法。從中抬吟,我們能一窺其技術(shù)原理萨咕。
// BitcoinKitPrivateSwift.swift
// 子私鑰和子公鑰的衍生方法源代碼
func derived(at childIndex: UInt32, hardened: Bool) -> _HDKey? {
var data = Data()
// 是否使用硬化衍生
if hardened {
data.append(0)
guard let privateKey = self.privateKey else {
return nil
}
// 強(qiáng)化衍生時,加入母私鑰
data.append(privateKey)
} else {
// 非強(qiáng)化衍生火本,加入母公鑰
data.append(publicKey)
}
var childIndex = CFSwapInt32HostToBig(hardened ? (0x80000000 as UInt32) | childIndex : childIndex)
// 加入索引號
data.append(Data(bytes: &childIndex, count: MemoryLayout<UInt32>.size))
// 結(jié)合母鏈碼危队,生成哈希值。注意钙畔,是否為強(qiáng)化衍生將影響生成的鏈碼
var digest = _Hash.hmacsha512(data, key: self.chainCode)
let derivedPrivateKey: [UInt8] = digest[0..<32].map { $0 } // 左半部分為私鑰
let derivedChainCode: [UInt8] = digest[32..<64].map { $0 } // 右半部分為鏈碼
var result: Data
if let privateKey = self.privateKey {
// 子私鑰的衍生茫陆。調(diào)用本方法時會傳入 privateKey
guard let ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN)) else {
return nil
}
defer { secp256k1_context_destroy(ctx) }
// 本質(zhì)上,使用了母私鑰衍生子私鑰
var privateKeyBytes = privateKey.map { $0 }
var derivedPrivateKeyBytes = derivedPrivateKey.map { $0 }
if secp256k1_ec_privkey_tweak_add(ctx, &privateKeyBytes, &derivedPrivateKeyBytes) == 0 {
return nil
}
// 子私鑰
result = Data(privateKeyBytes)
} else {
// 子公鑰的衍生擎析。調(diào)用本方法時不會傳入 privateKey
guard let ctx = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_VERIFY)) else {
return nil
}
defer { secp256k1_context_destroy(ctx) }
// 本質(zhì)上簿盅,使用了母公鑰衍生子公鑰
let publicKeyBytes: [UInt8] = publicKey.map { $0 }
// 子公鑰推導(dǎo)的特殊處理,結(jié)合了母公鑰
var secpPubkey = secp256k1_pubkey()
if secp256k1_ec_pubkey_parse(ctx, &secpPubkey, publicKeyBytes, publicKeyBytes.count) == 0 {
return nil
}
if secp256k1_ec_pubkey_tweak_add(ctx, &secpPubkey, derivedPrivateKey) == 0 {
return nil
}
var compressedPublicKeyBytes = [UInt8](repeating: 0, count: 33)
var compressedPublicKeyBytesLen = 33
if secp256k1_ec_pubkey_serialize(ctx, &compressedPublicKeyBytes, &compressedPublicKeyBytesLen, &secpPubkey, UInt32(SECP256K1_EC_COMPRESSED)) == 0 {
return nil
}
// 子公鑰
result = Data(compressedPublicKeyBytes)
}
let fingerPrint: UInt32 = _Hash.sha256ripemd160(publicKey).to(type: UInt32.self)
return _HDKey(privateKey: result, publicKey: result, chainCode: Data(derivedChainCode), depth: self.depth + 1, fingerprint: fingerPrint, childIndex: childIndex)
}
參考
- 《精通比特幣》
- 《區(qū)塊鏈技術(shù)指南》
- Mnemonic Code Converter
- BitcoinKit