# 第5章 钱包
“钱包”一词在比特币中有多重含义。
广义上,钱包是一个应用程序,为用户提供交互界面。钱包控制用户资金访问权限,管理密钥和地址,跟踪余额以及创建和签名交易。
狭义上,即从程序员的角度来看,“钱包”是指用于存储和管理用户密钥的数据结构。
我们将深入介绍第二层含义,本章中钱包是私钥的容器,一般是通过结构化文件或简单数据库来实现。
# 5.1 钱包技术概述
在本节中,我们总结了各种技术,它们为用户构建起友好,安全和灵活的比特币钱包。
关于比特币的常见误解是,比特币钱包里含有比特币。事实上,钱包里只含有密钥。“币”被记录在比特币网络的区块链中。 用户使用钱包中的密钥签名交易,从而控制网络上的钱币。某种意义上,比特币钱包是密钥链。
提示 比特币钱包只含有密钥,而不是币。每个用户都有一个包含多个密钥的钱包。 钱包只包含私钥/公钥对的密钥链(请参阅第四章)。用户用密钥签名交易,从而证明他们拥有这笔交易的输出(他们的钱币)。钱币以交易输出的形式存储在区块链中(通常记为vout或txout)。
根据钱包包含的多个密钥之间是否有关系,主要分为两种类型:
第一种类型是非确定性钱包(nondeterministic wallet),其中每个密钥都是从随机数独立生成的。密钥彼此无关。这种钱包也被称为“Just a Bunch Of Keys(一堆密钥)”,简称JBOK钱包。
第二种类型是确定性钱包(deterministic wallet),其中所有的密钥都是从一个主密钥派生出来,这个主密钥即为种子(seed)。该类型钱包中所有密钥都相互关联,如果有原始种子,则可以再次生成全部密钥。确定性钱包中使用了许多不同的密钥推导方法。最常用的推导方法是使用树状结构,称为分层确定性(hierarchical deterministic)钱包或HD钱包。
确定性钱包由种子进行初始化的。为了便于使用,种子被编码为英文单词,也称为助记词(mnemonic code words)。
接下来的几节将深入介绍这些技术。
# 5.1.1 非确定性(随机)钱包
在第一个比特币客户端中( 现在叫Bitcoin Core)中,钱包只是随机生成的私钥的集合。举个例子,Bitcoin Core客户端第一次启动时预先生成100个随机私钥,之后根据需要再生成足够多的密钥,并且每个密钥只使用一次。这种钱包现在正在被确定性钱包替换,因为它们难以管理、 备份以及导入。随机密钥的缺点就是如果你生成很多密钥,就必须保存它们所有的副本。这就意味着这个钱包必须频繁地备份。每一个密钥都必须备份,否则一旦钱包不可访问时,钱包所控制的资金就会无法挽回地丢失。这就与避免地址重复使用的原则相冲突:每个比特币地址只能用于一次交易。地址重复使用会把多个交易和地址关联在一起,会减少隐私。零型非确定性钱包不是好的选择,特别是要避免重复使用地址,因为要管理更多的密钥,还要更频繁地备份。虽然Bitcoin Core客户端包含0型钱包,但Bitcoin Core开发者并不鼓励大家使用。图5-1展示的是一个非确定性钱包,其含有的随机密钥是个松散的集合。
提示 除了简单的测试之外,任何场合都不鼓励使用非确定性钱包。 对于备份和使用来说太麻烦了。相反,推荐使用基于行业标准的HD钱包,只需要备份种子助记词。
图5-1 0型非确定性(随机)钱包:随机钥匙的集合
# 5.1.2 确定性(基于种子)钱包
确定性,或者“基于种子”钱包包含的私钥都是使用相同种子,通过单向哈希函数衍生而来的。种子是随机生成的数字。还和别的数据,比如索引号码或者“链码”(参见“ 分层确定性钱包(BIP-32/BIP-44)”一节)结合起来派生私钥。在确定性钱包中,只需要种子就足够恢复所有的派生的私钥,所以只要在初始创建时,一个简单备份就足够。种子也足以用于钱包导入或者导出,允许在不同钱包实现之间轻松迁移所有用户密钥。图5-2展示了确定性钱包的逻辑图。
图5-2 1型确定性(基于种子钱包):从种子派生的密钥的确定性序列
# 5.1.3 分层确定性钱包(HD Wallets (BIP-32/BIP-44))
确定性钱包被开发成更容易从单个“种子”中生成许多密钥。确定性钱包的最高级形式是通过BIP-32标准定义的HD钱包。HD钱包包含的密钥以树状结构衍生,使得父密钥可以衍生一系列子密钥,每个子密钥又可以衍生出一系列孙密钥,以此类推,无限衍生。图5-3展示了这种树状结构。
图5-3 2型HD钱包:从种子产生的密钥树
相比较随机(不确定性)密钥,HD钱包有两个主要的优势。第一,树状结构可以被用来表达附加的组织含义,比如子密钥的特定分支用来接收交易收入款项,另一个分支用来负责接收对外付款的找零。密钥的分支也可以用于公司设置,将不同的分支分配给部门、子公司、特定功能或会计类别。
HD钱包的第二个好处是,用户可以创建一系列公钥,而不需要访问对应的私钥。这样,HD钱包就能用在不安全的服务器上,或者仅作为接收用途,它为每个交易发布不同的公钥。公钥不需要被预先加载或者提前衍生,服务器也不需要有用来支付的私钥。
# 5.1.4 种子和助记词(BIP-39)
HD钱包具有管理多个密钥和多个地址的强大机制。如果将它们与一种标准化的方法相结合,从一系列英文单词创建种子,这些单词更易于转录、导出和跨钱包导入,那么它们将更加有用。 这些英文单词被称为助记词,标准由BIP-39定义。 今天,大多数比特币钱包(以及其他加密货币的钱包)使用此标准,使用可互操作的助记词导入和导出种子进行备份和恢复。
让我们从实际的角度来看以下哪些种子更容易转录,记录在纸上、无错拼读、导出导入到别的钱包:
16进制表示的确定性钱包的种子:
0C1E24E5917779D297E14D45F14E1A1A
12个单词的助记词表示的确定性钱包的种子:
army van defense carry jealous true
garbage claim echo media make crunch
2
# 5.1.5 钱包最佳实践
由于比特币钱包技术已经成熟,出现了一些常见的行业标准,使得比特币钱包具备广泛互操作,易于使用,安全和灵活的特性。这些常用的标准是:
助记码,基于BIP-39
HD钱包,基于BIP-32
多用途HD钱包结构,基于BIP-43
多币种和多帐户钱包,基于BIP-44
这些标准可能会随着发展而改变或过时,但是现在它们形成了一套互锁技术,这些技术事实上已成为比特币的钱包标准。
这些标准已被软件和硬件比特币钱包广泛采用,使所有这些钱包实现互操作。用户导出在其中一个钱包上生成的助记词,再导入另一个钱包,就可以恢复所有交易,密钥和地址。
支持这些标准的软件钱包,有(按字母顺序排列)ElectrumSV,SimplyCash,Money Button, Dot Wallet, Hand Cash, CentBee和RelayX。支持这些标准的硬件钱包,有(按字母顺序排列)Keepkey,Ledger。
以下部分将详细介绍这些技术。
提示 如果您正准备实施一个比特币钱包,那么应该构建为一个HD钱包,遵循BIP-32,BIP-39,BIP-43和BIP-44标准,将种子编码为助记词用以备份,像下面章节会描述的。
# 5.1.6 使用比特币钱包
在之前的用户故事中,我们介绍了Gabriel,里约热内卢是一个有进取心的少年,他正在经营一家简单的网络商店,销售比特币品牌的T恤,咖啡杯和贴纸。
Gabriel使用图5-4的Ledger比特币硬件钱包来安全地管理他的比特币。 Ledger是一个简单的USB设备,用于存储密钥(以HD钱包的形式)和签署交易。 Ledger钱包遵循本章讨论的所有行业标准,因此Gabriel不依赖于任何专有技术或单一供应商解决方案。
当Gabriel首次使用Ledger时,设备从内置的硬件随机数生成器生成助记词和种子。 在钱包初始化阶段,屏幕上会按顺序逐个显示单词(见图5-5)。
图5-5 Ledger显示的其中一个助记词
写下这个助记词,Gabriel就创建了一个备份(参见表5-1),可以在Ledger设备丢失或损坏的情况下用于恢复。记下来的助记词可以在新的Ledger钱包,或者任一种兼容的软件和硬件钱包进行恢复。 请注意,单词顺序非常重要,因此,助记词记录在纸上时,每个单词之间都要有空格。Gabriel必须仔细地把每个单词都记录在编号的空格里,以保持正确的顺序。
表5-1 Gabriel的助记器备份
1. | army | 7. | garbage |
---|---|---|---|
2. | van | 8. | claim |
3. | defense | 9. | echo |
4. | carry | 10. | media |
5. | jealous | 11. | make |
6. | true | 12. | crunch |
提示 为了简单起见,表5-1显示了12个单词。 事实上,大多数硬件钱包生成更安全的24个词的助记符。 助记词使用的方式都是相同的,与长度无关。
作为网店的第一次实践,Gabriel使用他的Ledger设备生成一个比特币地址。所有客户的订单都使用此单一地址。 我们将看到,这种方法有一些缺点,不过可以使用HD钱包进行改进。
# 5.2 钱包技术细节
现在我们开始逐个深入了解这些比特币钱包所使用的重要的行业标准。
# 5.2.1 助记词(BIP-39)
助记词是表示(编码)随机数的一组有序的英文单词,用作生成确定性钱包的种子。这些有序单词足以重建种子,并且从种子重新创建钱包以及所有派生的密钥。使用助记词实现确定性钱包的应用会在首次创建钱包时,向使用者展示一个12至24个有序的单词。这些有序单词就是钱包的备份,就可以用来恢复以及重建相同或兼容钱包应用程序的密钥。与随机数序列比较,助记词可以让使用者备份钱包更容易一些,因为更容易阅读和正确抄写。
提示 助记词经常与“脑钱包”混淆。 他们不一样。主要区别在于脑钱包包含用户选择的单词,而助记词是由钱包随机创建呈现给用户的。 这个重要的区别使助记词更加安全,因为人类的随机数来源还是很贫乏的。
助记词定义在比特币BIP-39中(参见【附录3、比特币改进建议(BIPs)】 (opens new window))。需要注意的是,BIP-39是助记词的标准实施方案。还有另外一个标准,使用一组不同的单词,早于BIP-39由Electrum钱包使用。 BIP-39由Trezor硬件钱包的母公司提出,与Electrum的实施不兼容。 其实,BIP-39现在已经通过数十个可互操作的实施获得了广泛的行业支持,已被视为事实上的行业标准。
BIP-39定义了助记词和种子的创建,我们在这里总结为九个步骤。 为了清楚起见,整个过程分为两部分:
1-6步见5.2.2创建助记词,7-9步见5.2.3从助记词到种子。
# 5.2.2创建助记词
助记词是由钱包使用BIP-39中定义的标准化过程自动生成的。 钱包从熵源开始,增加校验和,然后将熵映射到单词列表:
1、创建一个128到256位的随机序列(熵)。
2、提取SHA256哈希的前(熵长/ 32的比值)位,创造随机序列的校验和。
3、将校验和添加到随机序列的末尾。
4、将序列拆分为11位长度的多个段。
5、将每个11位值映射为有2048个单词的预定义字典中的一个单词。
6、生成的有顺序的单词组就是助记码。
图5-6展示了熵如何生成助记词。
图5-6 熵的产生和助记词的编码
表5-2表示了熵数据的大小和助记词的长度之间的关系。
表5-2助记词:熵及助记词长度
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 |
# 5.2.3 从助记词生成种子
助记词表示长度为128至256位的熵。 通过使用密钥延伸函数PBKDF2,熵被用于导出更长的(512位)种子。产生的种子用于构建确定性钱包并导出密钥。
密钥延伸函数有两个参数:助记词和盐(salt)。 密钥延伸函数中盐的目的是增加构建暴力攻击使用的查找表的难度。 在BIP-39标准中,盐还有另一目的,引入密码口令(passphrase),作为保护种子的附加安全因素,我们将在BIP-39可选密码口令章节详细描述。
创建助记词之后的7-9步是:
7、PBKDF2密钥延伸函数的第一个参数是从步骤6生成的助记词。
8、PBKDF2密钥延伸函数的第二个参数是盐。 由字符串常数“mnemonic”与可选的用户提供的密码口令一起组成。
9、PBKDF2使用HMAC-SHA512算法,使用2048次哈希来延伸助记词和盐参数,产生一个512位的值作为其最终输出。 这个512位的值就是种子。
图5-7显示了从助记词如何生成种子
图5-7 从助记词到种子
提示 密钥延伸函数,使用2048次哈希是一种非常有效的保护,可以防止对助记词或密码口令的暴力攻击。 它使得攻击尝试非常昂贵(从计算的角度),需要尝试超过几千个密码和助记词组合,而这样可能产生的种子的数量是巨大的(2512)。
表5-3、5-4和表5-5展示了一些助记码的例子和它所生成的种子(有的带密码口令,有的不带)。
表5-3 128位熵助记词,没有密码口令产生的种子
参数 | 实例 |
---|---|
Entropy input (128 bits) | 0c1e24e5917779d297e14d45f14e1a1a |
Mnemonic (12 words) | army van defense carry jealous true garbage claim echo media make crunch |
Passphrase | (none) |
Seed (512 bits) | 5b56c417303faa3fcba7e57400e120a0ca83ec5a4fc9ffba757fbe63fbd77a89a1a3be4c67196f57c39 a88b76373733891bfaba16ed27a813ceed498804c0570 |
表5-4 128位熵助记词,有密码口令产生的种子
参数 | 实例 |
---|---|
Entropy input (128 bits) | 0c1e24e5917779d297e14d45f14e1a1a |
Mnemonic (12 words) | army van defense carry jealous true garbage claim echo media make crunch |
Passphrase | SuperDuperSecret |
Seed (512 bits) | 3b5df16df2157104cfdd22830162a5e170c0161653e3afe6c88defeefb0818c793dbb28ab3ab091897d0 715861dc8a18358f80b79d49acf64142ae57037d1d54 |
表5-5 256位熵助记词,没有密码口令产生的种子
参数 | 实例 |
---|---|
Entropy input (256 bits) | 2041546864449caff939d32d574753fe684d3c947c3346713dd8423e74abcf8c |
Mnemonic (24 words) | cake apple borrow silk endorse fitness top denial coil riot stay wolf luggage oxygen faint major edit measure invite love trap field dilemma oblige |
Passphrase | (none) |
Seed (512 bits) | 3269bce2674acbd188d4f120072b13b088a0ecf87c6e4cae41657a0bb78f5315b33b3a04356e53d062e5 5f1e0deaa082df8d487381379df848a6ad7e98798404 |
# 5.2.4 BIP-39中的可选密码口令
BIP-39标准允许在推导种子时使用可选的密码口令。 如果没有使用密码口令,助记词是用由常量字符串“mnemonic”构成的盐进行延伸,从任何给定的助记词产生一个特定的512位种子。 如果使用密码短语,密钥延伸函数使用同样的助记词也会产生不同的种子。事实上,使用同一个助记词,每一个可能的密码口令都会导致不同的种子。 基本上没有“错误”的密码口令, 所有密码口令都是有效的,它们都会导致不同的种子,形成一大批未初始化的钱包。这些钱包数量非常之多(2512),根本不可能使用暴力破解或随机猜测。
提示 BIP-39中没有“错误的”密码口令。 每个密码都会导致一些钱包,只是未使用的钱包是空的。
可选密码口令带来两个重要功能:
- (存储在大脑中的)密码口令成为第二重保护,使得仅有助记词是不够的,避免了助记词备份被盗取后利用。
- 起到掩人耳目的效果或“胁迫钱包”,把选定的密码口令指向有小额资金的钱包,分散攻击者注意力,使其不再关注拥有大额资金的“真实”钱包。
然而,需要注意的是,使用密码口令也会引起丢失的风险:
- 如果钱包所有者无行为能力或死亡,没有人知道密码,种子就是无用的,所有存储在钱包中的资金都将永远丢失。
- 相反,如果所有者将密码口令与种子备份在相同的地方,则失去第二重保护的目的。
虽然密码口令非常有用,但考虑到钱包拥有者幸存的可能性,以及允许其家人收回加密货币财产,密码口令只能与精心规划的备份和恢复过程结合使用。
# 5.2.5 使用助记词
BIP-39作为函数库实施,支持多种编程语言:
python-mnemonic (opens new window)
SatoshiLabs团队提出了BIP-39的Python标准参考实现
bitcoinjs/bip39 (opens new window)
BIP-39的JavaScript实现是流行的bitcoinJS框架的一部分。
libbitcoin/mnemonic (opens new window)
BIP-39的C++实现是流行的Libbitcoin框架的一部分。
还有一个BIP-39在独立的网页中实现的生成器,非常适合用于测试和实验。图5-8展示一个独立的网页,可以生成助记词、种子和扩展私钥。
图5-8 独立的网页BIP-39生成器
BIP-39生成器可以在线或离线使用,可以使用这个在线地址 (opens new window)。
# 5.3 从种子创造HD钱包
HD钱包从单个*根种子(root seed)*创建,后者为128,256到512位的随机数。最常见的是,这个种子是从助记词产生的,如上一节所述。
HD钱包的所有密钥是由根种子确定的,使用这个根种子就可以在任何兼容HD钱包中重新创造整个HD钱包。所以简单的转移生成HD钱包根种子的助记词就可以很容易地备份,储存导出以及导入HD钱包中所包含的数以千计百万计的密钥。
图5-9展示从根种子创建主密钥以及HD钱包的主链码的过程。
图5-9 从根种子创建主密钥,主链码
根种子作为HMAC-SHA512算法的输入,得到的哈希值可以用来创造主私钥master private key(m) 和 主链码master chain code(c) 。
主私钥(m)使用标准椭圆曲线乘法过程m * G生成对应的主公钥(M)。
链码用于从父密钥创造子密钥的函数中引入熵。如下一节所示。
# 5.3.1 私有子密钥的衍生
分层确定性钱包使用子密钥衍生child key derivation,简称CKD函数从父密钥衍生出子密钥。
子密钥衍生函数是基于单向哈希函数。这个函数结合了:
一个父私钥或者父公钥(ECDSA压缩密钥)
一个叫做链码(256 位)的种子
一个索引号(32 位)
链码是用来给这个过程引入确定性随机数据的,使得仅凭索引和子密钥也不足以衍生其他子密钥。因此,有了子密钥并不能发现自己的姊妹密钥,除非再有了链码。最初的链码种子(在密码树的根部)是用种子制造的,随后的子链码从各自的父链码衍生出来。
这三个项目(父密钥,链码,索引)相结合并哈希计算生成子密钥,如下。
父公钥,链码以及索引号合并在一起用HMAC-SHA512算法哈希计算之后产生512位的哈希值。所得的哈希拆分为两部分。右半部分的256位成为子链链码。左半部分256位附加到父私钥来衍生子私钥。在图5-10中,我们看到,索引设置为0,生成父级的“0”子级(第一个索引)。
图5-10 扩展父私钥创建子私钥
改变索引允许我们扩展父级,并按顺序创建其他子级,例如子级0、子级1、子级2等。每一个父密钥可以有2,147,483,647 (231) 个子密钥。(231是整个232范围可用的一半,另一半是为特定类型的推导而保留的,我们将在本章稍后讨论。)
向密码树下一层重复这个过程,每个子密钥可以依次成为父密钥,继续创造它自己的子密钥,直到无限代。
# 5.3.2 使用衍生的子密钥
子私钥与不确定(随机)密钥区别不大。因为衍生函数是单向的,所以子密钥不能被用来发现它的父密钥。子密钥也不能用来发现它们的相同层级的姊妹密钥。如果你有第n个子密钥,你不能发现它的姐妹密钥,比如前面的(第n-1)或者后面的子密钥(n+1)或者在同一顺序中的其他子密钥。只有父密钥以及链码才能得到所有的子密钥。没有子链码,子密钥也不能衍生出任何孙密钥。你需要同时有子私钥以及对应的子链码才能创建一个新的分支,衍生出孙密钥。
那子私钥自己可被用做什么呢?它可以用来制作公钥和比特币地址。之后它就可以被用于对那个地址签署交易和支付花费。
提示 子私钥、对应的公钥和比特币地址都与随机创建的密钥和地址不可区分。它们是序列的一部分这一事实在创建它们的HD钱包功能之外是看不到的。一旦被创造出来,它们就和“正常”密钥一样工作了。
# 5.3.3 扩展密钥
正如我们之前看到的,密钥衍生函数可以被用来创造密钥树上任何层级的子密钥,基于以下三个输入量:密钥,链码以及想要的子密钥的索引。密钥以及链码这两个重要的部分被结合之后,就叫做扩展密钥(extended key)。术语“扩展密钥”也被认为是“可扩展的密钥”,因为这种密钥可以用来衍生子密钥。
扩展密钥被储存并且简单地表示为将256位密钥与256位链码所串成的512位序列。有两种类型扩展密钥。扩展的私钥是私钥以及链码的结合。它可被用来衍生子私钥(子私钥可以衍生子公钥)。公钥以及链码组成扩展公钥,它可以用来创建子公钥(只能是公钥),见“生成公钥”章节。
扩展密钥作为HD钱包中密钥树结构的一个分支的根。你可以衍生出这个分支的剩下所有部分。扩展私钥可以创建一个完整的分支,而扩展公钥只能够创造公钥的分支。
提示 一个扩展密钥包括一个私钥或者公钥和一个链码。一个扩展密钥可以创造出子密钥并且能创造出密钥树结构中的整个分支。共享了扩展密钥就可以访问整个分支。
扩展密钥通过Base58Check来编码,很容易在不同的BIP-32兼容钱包之间导入导出。扩展密钥编码用的 Base58Check使用特殊的版本号,Base58编码字符前缀分别为“xprv”和“xpub”,这种前缀可以让编码更易被识别。因为扩展密钥是512或者513位,所以它比我们之前所看到的Base58Check编码串更长一些。
以下面的扩展私钥为例,其使用的是Base58Check编码:
xprv9tyUQV64JT5qs3RSTJkXCWKMyUgoQp7F3hA1xzG6ZGu6u6Q9VMNjGr67Lctvy5P8oyaYAL9CAWrUE9i6GoNMKUga5biW6Hx4tws2six3b9c
下面是上面扩展私钥对应的扩展公钥,同样使用Base58Check编码:
xpub67xpozcx8pe95XVuZLHXZeG6XWXHpGq6Qv5cmNfi7cS5mtjJ2tgypeQbBs2UAR6KECeeMVKZBPLrtJunSDMstweyLXhRgPxdp14sk9tJPW9
# 5.3.4 公用子密钥的衍生
正如之前提到的,分层确定性钱包的一个很有用的特点就是可以不通过私钥而直接从父公钥派生出子公钥。这就给了我们两种衍生子公钥的方法:或者通过子私钥,或者直接通过父公钥。
因此,扩展公钥可以在HD钱包结构的分支中,用来衍生所有的公钥(且只有公钥)。
这种快捷方式可以用来创造非常安全的只有公钥部署环境。这个环境中,服务器或者应用程序不管有没有私钥,只要有扩展公钥的副本就可以。这种部署可以创造出无限数量的公钥以及比特币地址,但是不能花费发送到这个地址里的任何比特币。与此同时,在另一种更安全的服务器上,扩展私钥可以衍生出对应的私钥,签署交易支付花费。
这种方案的常见应用是安装扩展公钥在电商的web服务器上。web服务器可以使用公钥衍生函数去给每一笔交易(比如客户的购物车)创造一个新的比特币地址。服务器没有私钥,也就避免了被盗的风险。没有HD钱包的话,唯一的方法就是在不同的安全服务器上创造成千上万个比特币地址,之后再预加载到电子商务服务器上。这种方法非常繁琐而且要需要持续的维护来确保电商服务器不会“用光”公钥。
这种解决方案的另一种常见的应用是冷存储或者硬件钱包。在这种情况下,扩展私钥可以被储存在纸钱包中或者硬件设备中(比如 Trezor 硬件钱包),扩展公钥可以在线保存。使用者可以根据意愿创造“接收”地址而私钥可以安全地在线下保存。为了支付资金,使用者可以使用扩展私钥离线签署比特币客户端应用或者签署硬件钱包设备(比如 Trezor)上的交易。例5-11说明了扩展父公钥以派生子公钥的机制。
例5-11 扩展父公钥以创建子公钥
# 5.3.5 在网店中使用扩展公钥(xpub)
继续Gabriel网店的故事,让我们看看Gabriel是如何使用HD钱包。
Gabriel建立一个简单的托管的WordPress页面,作为他的网上商店。它的网店非常简单,只有几个页面和带有比特币地址的订单表格。
Gabriel使用他的Trezor设备生成的第一个比特币地址作为他的商店的主要比特币地址。这样,所有收到的款项都支付到了这个Trezor硬件钱包所控制的地址。
客户可以使用表格提交订单,并向Gabriel发布的比特币地址付款,触发一封电子邮件发送给Gabriel,其中包含订单详细信息。每周只有几个订单的时候,这个系统运行得很好。
然而,这个小型网店变得相当成功,并吸引了当地社区的很多订单。Gabriel很快就不堪重负。由于所有订单都支付到相同的地址,因此很难正确匹配订单和交易,尤其是同时接到同一数量的多个订单时。
HD钱包可以在不知道私钥的情况下获取子公钥,该能力为Gabriel提供了更好的解决方案。 Gabriel可以在他的网站上加载一个扩展公钥(xpub),这可以为每个客户订单生成一个唯一的地址。Gabriel可以花费他在Trezor里的资金,但加载在网站上的xpub只能生成地址并收到资金。HD钱包的这个功能非常安全。 Gabriel的网站不包含任何私钥,因此不需要高级别的安全性。
为了导出xpub,Gabriel将基于Web的软件与Trezor硬件钱包配合使用。必须插入Trezor设备才能导出公钥。请注意,硬件钱包永远不会导出私钥,这些密钥始终保留在设备上。图5-12显示了Gabriel用于导出xpub的Web界面。
图5-12 从Trezor硬件钱包导出xpub
Gabriel将xpub复制到他网店的比特币商店软件中。 他使用的软件是Mycelium Gear,这是一个网店的开源插件,用于各种web托管和内容平台。 Mycelium Gear使用xpub为每次购买生成一个唯一的地址。
# 5.3.6 强化子密钥的衍生
从xpub衍生一个分支公钥的能力是很重要的,但牵扯一些潜在风险。访问xpub并不能访问子私钥。但是,因为xpub包含有链码,如果子私钥被知道或者被泄漏的话,链码就可以被用来衍生所有的其他子私钥。泄露的私钥如果再加上父链码,就可能暴露所有子项的所有私钥。更糟糕的是,子私钥与父链码可以用来推断父私钥。
为了应对这种风险,HD钱包使用一种替代衍生函数,叫做强化衍生(hardened derivation),“打破”了父公钥以及子链码之间的关系。这个强化衍生函数使用了父私钥去推导子链码,而不是父公钥。这就在父/子顺序中创造了一道“防火墙”,链码就不能危害父私钥或者同级私钥。强化衍生函数看起来与常规的子私钥衍生相同,不同的是父私钥可以作为哈希函数的输入,而父公钥不行,如图5-13所示。
图5-13 子密钥的强化衍生,省略父公钥
当使用强化私钥衍生函数时,得到的子私钥以及链码与使用常规衍生函数所得到的结果完全不同。得到的密钥“分支”可以被用来生产不易被攻击的扩展公钥,因为它所含的链码不能被用来泄露任何私钥。强化衍生也因此被用在使用扩展公钥的密钥树的上一层创造“隔层”。
简单来说,如果想利用xpub的便捷来衍生公钥的分支,又不想冒泄露链码的风险, 就该从强化父密钥,而不是常规父密钥衍生。最好的方式是,为了避免主密钥泄露,主密钥所衍生的第一层级的子密钥总是通过强化衍生得来。
# 5.3.7 常规衍生和强化衍生的索引号
用在衍生函数中的索引号是32位整数。为了区分密钥是从常规衍生函数中衍生出来还是从强化衍生函数中产出的,这个索引号被分为两个范围。索引号在0和231–1(0x0 to 0x7FFFFFFF)之间的只用于常规衍生。索引号在231和232– 1(0x80000000 to 0xFFFFFFFF)之间的只用于强化衍生。因此,索引号小于231就意味着子密钥是常规的,而大于或者等于231的子密钥就是强化的。
为了让索引号更容易被阅读和展示,强化子密钥的索引号是从0开始展示的,但是右上角有一个小撇号。第一个常规子密钥就表示为0,第一个强化子密钥(索引号为0x80000000)就表示为0'。第二个强化密钥依序有了索引号0x80000001,表示为1',以此类推。当你看到HD钱包索引号i',这就意味着 231+i。
# 5.3.8 HD钱包密钥识别符(路径)
HD钱包中的密钥是用“路径”命名的,且每个级别之间用斜杠(/)来表示(见表5-6)。由主私钥衍生的私钥以“m”开头。由主公钥衍生的公钥以“M“开。因此,主私钥的第一个子私钥是m/0。第一个子公钥是M/0。第一个子私钥的第二个孙私钥(对于主私钥来说是孙私钥)就是m/0/1,以此类推。
密钥的“祖辈”是从右向左读,直到衍生它的主密钥。举个例子,标识符m/x/y/z描述的是子密钥m/x/y的第z个子密钥。而子密钥m/x/y又是m/x的第y个子密钥,m/x又是m的第x个子密钥。 表5-6 HD钱包路径的例子
HD 路径 | 密钥描述 |
---|---|
m/0 | 主私钥(m)衍生的第一个子私钥(0) |
m/0/0 | 第一个子私钥 (m/0)衍生的第一个子私钥(0) |
m/0'/0 | 第一个强化子密钥 (m/0')衍生的第一个常规子密钥(0) |
m/1/0 | 第二个子密钥 (m/1)衍生的第一个字私钥(0) |
M/23/17/0/0 | 第24个子密钥(M/23)衍生的第18个子密钥(M/23/17)的第一个字密钥(M/23/17/0)的第一个子公钥(0) |
# 5.3.9 浏览HD钱包树状结构
HD钱包树状结构提供了极大的灵活性。每一个父扩展密钥有40亿个子密钥:20亿个常规子密钥和20亿个强化子密钥。 而每个子密钥又会有40亿个子密钥并且以此类推。只这个树结构可以无限类推到无穷代。但是,又由于这个灵活性,无限的树状结构进行导航就变得异常困难。尤其是更换不同的HD钱包,因为内部组织到分支以及子分支的可能性是无穷的。
有两个BIP提供了这个复杂问题的解决办法——通过为HD钱包树的结构创建建议标准。BIP-43提出使用第一个强化子索引作为表示树结构“用途”(purpose)的特殊标识符。基于BIP-43,HD钱包应该只使用树的第一个一级分支,其余部分的结构和名称空间由索引号通过定义其用途来标识。举个例子,只使用分支m/i'/的HD钱包,表示特定用途,该用途由索引号“i”标识。
对上述规范进行了扩展,BIP-44提议使用多账户结构,使用44'这个号码作为BIP-43的“用途”。所有遵循BIP-44的HD钱包按照只使用树的第一个分支的要求,被定义为:m/44'/。
BIP-44指定了包含5级预定义树的结构:
m / purpose' / coin_type' / account' / change / address_index
第一层的purpose总是被设定为44'。第二层的“coin_type”特指币种,允许多币种HD钱包中的每一种数字货币在第二层级下都有自己的子树。目前有三种货币被定义:Bitcoin 是 m/44'/0'、Bitcoin Testnet 是 m/44'/1',以及 Litecoin 是 m/44'/2'。
树的第三层级是“account”,允许用户为了会计或者组织目的,再细分钱包到独立的逻辑性子账户。 举个例子,一个HD钱包可能包含两个比特币“账户”:m/44'/0'/0' 和 m/44'/0'/1'。每个账户都是它自己子树的根。
第四层级就是“change”。每一个HD钱包有两个子树,一个是接收地址,另一个用来创造找零地址。注意无论先前的层级是否使用强化衍生,这一层级使用的都是常规衍生。这是为了允许树的这一层级可以在不安全环境下,导出扩展公钥。可用的地址由HD钱包派生为第四层级的子级,就是第五层级“address_index”。比如,比特币主账户的第三个收款地址就是 M/44'/0'/0'/0/2。表5-7展示了更多的例子。
表5-7 BIP-44 HD 钱包结构的例子
HD path | Key described |
---|---|
M/44'/0'/0'/0/2 | 比特币主账户的第三个收款公钥 |
M/44'/0'/3'/1/14 | 比特币第四个账户的第十五个找零收款公钥 |
m/44'/2'/0'/0/1 | Litecoin主账户中的第二个私钥,用于签名交易 |