比特幣那些事(4)——錢包

原文鏈接

image

概述

比特幣中錢包并不是傳統(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ī)密鑰的缺點就是如果你生成很多私鑰刚操,就必須保存它們所有的副本闸翅。每一個密鑰都必須備份,否則一旦錢包不可訪問時菊霜,錢包所控制的資金就付之東流坚冀。

image

確定性錢包

確定性錢包通過使用單項離散函數(shù)從公共的種子生成的私鑰。種子是隨機(jī)生成的數(shù)字鉴逞。在確定性錢包中记某,種子可以恢復(fù)所有的已經(jīng)生成的私鑰司训,因此,只要在初始創(chuàng)建時對種子進(jìn)行備份就可以了液南。

image

分層確定性錢包

確定性錢包使用了許多不同的密鑰推導(dǎo)方法壳猜。最常用的推導(dǎo)方法是使用樹狀結(jié)構(gòu),稱為 分層確定性錢包(Hierarchical Deterministic Wallet贺拣,簡稱 HD 錢包)蓖谢。在 HD 錢包中,父密鑰可以衍生出一系列子密鑰譬涡,每個子密鑰又可以衍生出一系列孫密鑰闪幽,以此類推,無限衍生涡匀。

image

舉例

下面盯腌,我們通過一個例子來介紹 HD 錢包的實現(xiàn)原理。

Alice 經(jīng)營了一家網(wǎng)絡(luò)商店銷售T恤陨瘩。她使用 Trezor 比特幣硬件錢包(硬件 HD 錢包)來管理她的比特幣腕够。

image

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)化過程自動生成的。助記詞的生成主要包含以下這些步驟:

  1. 創(chuàng)建一個 128 至 256 位的隨機(jī)序列(熵)
  2. 提取隨機(jī)序列哈希值的前幾位(隨機(jī)序列長度/32)啡彬,作為隨機(jī)序列的校驗和
  3. 將校驗和拼接至隨機(jī)序列的末尾
  4. 將序列進(jìn)行分割成多個單元羹与,每個單元占 11 位
  5. 將每個單元的值映射到一個包含 2048(2^11)個單詞的字典
  6. 映射得到有順序的單詞組,即助記詞
image

根據(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)捐晶。鹽的目的是為了增加暴力攻擊的難度菲语。

種子的生成主要包含以下步驟:

  1. PBKDF2 密鑰延伸函數(shù)的第一個參數(shù)是助記詞妄辩。
  2. PBKDF2 密鑰延伸函數(shù)的第二個參數(shù)是鹽。鹽由助記詞和可選的用戶提供的密碼組成山上。
  3. PBKDF2 密鑰延伸函數(shù)內(nèi)部使用 HMAC-SHA512 算法眼耀,進(jìn)行 2048 次哈希運算,生成一個 512 位的種子佩憾。
image

下表所示為示例的 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)
image

創(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ù)拆分成左右兩部分昧港,分別得到:

  • 子私鑰
  • 子鏈碼
image

上述子密鑰衍生函數(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ù)拆分成左右兩部分,分別得到:

  • 子私鑰
  • 子鏈碼
image

這種子公鑰的衍生過程不涉及任何私鑰撒强,運用到實際場景禽捆,可以實現(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)子鏈碼策泣。

image

核心源碼

如下所示為比特幣開源庫 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)
}

參考

  1. 《精通比特幣》
  2. 《區(qū)塊鏈技術(shù)指南》
  3. Mnemonic Code Converter
  4. BitcoinKit
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挪鹏,隨后出現(xiàn)的幾起案子见秽,更是在濱河造成了極大的恐慌,老刑警劉巖讨盒,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異步责,居然都是意外死亡返顺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門蔓肯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遂鹊,“玉大人,你說我怎么就攤上這事蔗包”耍” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵调限,是天一觀的道長舟陆。 經(jīng)常有香客問我,道長耻矮,這世上最難降的妖魔是什么秦躯? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮裆装,結(jié)果婚禮上踱承,老公的妹妹穿的比我還像新娘。我一直安慰自己哨免,他們只是感情好茎活,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著琢唾,像睡著了一般载荔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上慧耍,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天身辨,我揣著相機(jī)與錄音,去河邊找鬼芍碧。 笑死煌珊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泌豆。 我是一名探鬼主播定庵,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蔬浙?” 一聲冷哼從身側(cè)響起猪落,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎畴博,沒想到半個月后笨忌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡俱病,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年官疲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亮隙。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡途凫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出溢吻,到底是詐尸還是另有隱情维费,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布促王,位于F島的核電站犀盟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏硼砰。R本人自食惡果不足惜且蓬,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望题翰。 院中可真熱鬧恶阴,春花似錦、人聲如沸豹障。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽血公。三九已至昵仅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間累魔,已是汗流浹背摔笤。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留垦写,地道東北人吕世。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像梯投,于是被迫代替她去往敵國和親命辖。 傳聞我的和親對象是個殘疾皇子况毅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容