比特币钱包

总纲

比特币钱包中存储的不是比特币,而是比特币拥有者的密钥

钱包分类

Nondeterministic Wallets

钱包中存储预先生成的一组随机私钥,该类型又称作 type-0 类型钱包。这种钱包已经不使用。

Deterministic Wallets

通过“种子”,可以把所有的密钥推导出来的钱包,该类型又称作 type-1 类型钱包。该类型钱包只需要备份“种子”,即能将所有的密钥保存。导出/迁移密钥,也只需要导出/迁移种子即可。

Hierarchical Deterministic(HD) Wallets

在deterministic钱包类型基础上发展而来,实现BIP-32和BIP-44协议的钱包。之所以称为hierarchical,可以看如下示意图:
图1: a tree of keys generated from a seed:bip32

通过seed生成主密钥,该密钥为深度等于0的根节点,在此节点上可以衍生出子节点,每个子节点即代表了密钥;每个子节点又能继续向后衍生,以此无穷无尽。从数量上,每一个节点,可以有约40亿个子节点(2^32),因此可以算得上是“无穷无尽”了。
在实际使用的时候,某一个节点的密钥专门用作地址用于接收,某一个节点的密钥专门用于付款。由于节点数量的“无限性”,在实际应用的时候,非常容易匹配业务。

钱包技术详解

助记词 Mnemonic Code Words(BIP-39)

助记词是一组单词,用来生成seed,它的规则是:

  1. 生成一组随机序列(墒),长度为128:32:256比特(最短128比特,按32比特递增)
  2. 计算该随机序列的哈希值SHA256
  3. 将哈希值的前4个比特去除,添加到随机序列后
  4. 将延展的序列按11比特分成12个区间
  5. 根据预先定义的2038字典表,检索每个区间所对应的单词
  6. 将这些单词组合起来,就是助记词

得到了助记词后,需要使用PBKDF2(密钥延展函数)生成seed,该函数需要两个参数,因此生成seed步骤,

  1. 第一个参数即助记词,第二个参数是盐,由"mneonic"+"passphrase"组成,其中"passphrase"可以为空(该规则在BIP-39中定义)
  2. 使用PBKDF2计算,该函数实际上计算了2048次的HMAC-SHA512,计算结果为seed

PBKDF2的使用,增加了暴力破解的成本;passphrase的引入,增强了安全性,但同时也要注意passphrase的保存。

创建主密钥

对seed使用HMAC-SHA512,将计算结果一分为二,左256位是主私钥m,右256位是主链码(chain code)。通过公式 M = m*G 可以计算得到主公钥。有了主密钥后,就可以生成子密钥了。

子私钥

使用CKD(child key derivation)函数从父密钥中计算子私钥,该函数需要揉合三个参数:

  1. 父私钥或父公钥
  2. 256位的chain code:
  3. 从零开始的索引值,32位

chain code的作用是给确定性数据增加随机性,这样:

a) 只知道父密钥和索引值,无法得知子私钥
b) 知道子私钥无法得知兄弟私钥

该工作流程可以参考下图(截自 Mastering Bitcoin 2nd Edition):
图2: derive child private key

从上面的工作流程,我们可以看到,有了密钥和chain code,就可以生成该节点的所有子孙节点的密钥。因此,比特币中定义了扩展密钥(extend key),它的组成:

public or private key + chain code

扩展密钥会使用Base58Checking进行编码,编码后的扩展私钥以xprv开头,扩展公钥以xpub开头。

子公钥衍化

HD钱包可以通过公钥来生成子公钥,通过这种方式:

  1. 提高安全性:公钥用来生成比特币地址,没有对应的私钥无法交易地址中的币
  2. 部署在电子商务的服务器上用于生成接收地址
  3. 冷钱包存储:扩展私钥离线保存,扩展公钥可以在线保存

生成子公钥的流程如图:
图3: Derive Child Public Key

子公钥带来了便利,也带来了风险。为了更好理解风险的由来,这里引入几个数学公式。从前面的学习中我们知道公钥的组成是 0x04XY,XY分别是椭圆曲线上某一点的坐标,即

point(private key) == public key //1

(通过私钥用小写k表示,公钥用大写K表示,为了区分,这里不使用这种写法)

对于子公钥child public key,加上point(i)后,是兄弟子公钥,: child_public_key + point(i)(i是index)。进一步的,

child_public_key + point(i) == point((child_private_key + i) %p) //2

也就是说,知道了一个子公钥,我们通过计算point(i),可以得到所有的兄弟子公钥(其中p是一个大质数,可以先不管)。
通过图2,我们知道子私钥由父私钥生成:

child_private_key == (parent_private_key + lefthand_hash_output) % G //3
child_public_key == point((parent_private_key + lefthand_hash_output) % G )//4
child_public_key == point(child_private_key) == parent_public_key + point(lefthand_hash_output)//5

那么,在已知扩展公钥的前提下,存在的攻击方式有:
攻击1: 暴力破解该扩展公钥下的所有子孙的chain code
这个很好理解,因为根据图3只需要添加一个整数作为索引,即可生成该扩展公钥下的所有的子孙公钥和子孙chain code

攻击2: 如果获得了该扩展公钥子孙中的某个私钥,那么就能获得所有该私钥下所有的子孙扩展私钥
通过攻击1,我们得知攻击者可以得到所有的子孙chain code,那私钥和chain code结合,即能得到该私钥衍生的所有的私钥(公式3)。

攻击3: 如果攻击者获得了该扩展公钥下的某个子私钥,那么可以得到该扩展公钥的私钥
还是使用公式3,不过需要使用减法运算:

parent_private_key == child_private_key - parent_chain_code

为了解决上述可能的攻击,打破父子公钥之间的关系,现在的钱包使用BIP-44 hardened child key derivation.

Hardened Child Key Derivation

使用私钥生成子私钥和子chain code,工作流程如:
图4:HardenedChildKeyDerivation

为了区分和正常衍化算法的区别,对index做了区分:

  • 正常衍化:i表示,取值范围0-2^31-1
  • 硬化衍化:i'表示,取值范围2^31-2^32-1

因此很容易推得: i' = i + 2^31

HD钱包中密钥的标识

基于HD的树形结构,使用"/"表示不同的层级,使用m表示私钥,M表示公钥,如:

m/0'/0: 第一个硬化子私钥衍生的第一个正常私钥

HD钱包导航

因为HD钱包可以生成无穷无尽的密钥,方便业务部门灵活的对应某一个分支。这种灵活性也带来实现的复杂性,为了解决这个问题提出了BIP-43,该改进的核心就是HD钱包应该只使用某一个分支上的密钥。作为BIP-44的扩展,HD钱包应该使用:m/44' 下的分支,具体的结构如下:

m / purpose' / coin_type' / account' / change / address_index
  • purpose': 始终为44'
  • coin_type': 0'-bitcoin, 1'-bitcoin testnet, 2'-litecoin
  • account':表示逻辑子账户
  • change: 钱包包含两个子树,一个是找零地址,一个是接收地址;这一层级是正常衍化(注意上面三个层级都是硬化衍化)
  • address_index: 在change分支下衍化出来的地址的索引值

参考资料

文中所用图片都来自以下参考资料。
maste bitcoin
bitcoin developer guide