BIP钱包
BIP 说明
BIP 英文全称:Bitcoin Improvement Proposal(比特币改进建议)。简单来说的话,BIP 就是一个经过筛选后的“意见箱”。
“BIP 意见箱”的运作机制大概是这样的:
- 1、谁都可以提意见,这些意见在“知识库(repository)”中经过开发者的海选
- 2、通过海选的还需要进行淘汰赛,这一轮由比特币核心开发者当评委。
- 3、核心开发者通过后,将得到一个BIP号码。
- 4、有了BIP号码就可以进入决赛了,决赛是投票制。大部分的社区成员和开发者同意。
助记词
私钥是一长串的01字符,不易记忆,所以引出了助记词的定义。
- 规定熵的位数必须是 32 的整数倍,所以熵的长度取值位 128 到 256 之间取 32 的整数倍的值,分别为 128, 160, 192, 224, 256;
- 校验和的长度为熵的长度/32 位, 所以校验和长度可为 4,5,6,7,8 位;
- 助记词库有 2048 个词,用 11 位可全部定位词库中所有的词,作为词的索引,故一个词用 11 位表示,助记词的个数可为 (熵+校验和)/11,值为 12,15,18,21,24
熵(bits) | 校验和(bits) | 熵 + 校验和 (bits) | 助记词长度 |
---|---|---|---|
128 | 4 | 132 | 12 |
160 | 5 | 165 | 15 |
192 | 6 | 198 | 18 |
224 | 7 | 231 | 21 |
256 | 8 | 264 | 24 |
- 1、生成一个长度为 128~256 位 (bits) 的随机序列(熵)
- 2、取熵哈希后的前 n 位作为校验和 (n= 熵长度/32)
- 3、随机序列 + 校验和
- 4、把步骤三得到的结果每 11 位切割
- 5、步骤四得到的每 11 位字节匹配词库的一个词
- 6、步骤五得到的结果就是助记词串
通过助记词生成种子
助记词由长度为 128 到 256 位的随机序列(熵)匹配词库而来,随后采用 PBKDF2 function 推导出更长的种子(seed)。生成的种子被用来生成构建 deterministic Wallet 和推导钱包密钥。
在密码学中,Key stretching 技术被用来增强弱密钥的安全性,增加了暴力破解 (Brute-force attack) 对每个可能密钥尝试攻破的时间,增强了攻击难度。各种编程语言原生库都提供了 key stretching 的实现。PBKDF2 (Password-Based Key Derivation Function 2) 是常用的 key stretching 算法中的一种。基本原理是通过一个为随机函数(例如 HMAC 函数),把明文和盐值作为输入参数,然后重复进行运算最终产生密钥。
为了从助记词中生成二进制种子,BIP39 采用 PBKDF2 函数推算种子,其参数如下:
- 助记词句子作为密码
- “mnemonic” + passphrase 作为盐
- 2048 作为重复计算的次数
- HMAC-SHA512 作为随机算法
- 512 位(64 字节)是期望得到的密钥长度
BIP32 说明
分层确定性钱包(Hierarchical Deterministic Wallets)
钱包是一个私钥的容器,我们可以生成一堆私钥(一个人也有很多账号的需求,可以更好保护隐私),而每个私钥都需要备份就特别麻烦的。为了解决这种麻烦,就有了 BIP32 提议: 根据一个随机数种子通过分层确定性推导的方式得到 n 个私钥,这样保存的时候,只需要保存一个种子就可以,私钥可以推导出来。
主私钥和主链码
首先是从根种子生成主密钥 (master key) 和主链码 (master chain code)
上图中根种子通过不可逆 HMAC-SHA512 算法推算出 512 位的哈希串,左 256 位是 Master Private key(m), 右 256 位是 master chain code, 通过 m 结合推导公钥的椭圆曲线算法能推导出与之对应的 264 位 master public Key (M)。chain code 作为推导下级密钥的熵。
子私钥推导
HD 钱包使用 CKD(child key derivation) 函数从父密钥(parent keys)推导子密钥(child keys),CKD 由下列三个要素做单向散列哈希(one way hash function) 。
- 1、父密钥 (没有压缩过的椭圆曲线推导的私钥或公钥 ECDSA uncompressed key)
- 2、链码作为熵 (chain code 256 bits)
- 3、子代索引序号 (index 32 bits)
索引号个数为 2 的 32 次方,每个父级密钥能推导出该数目一半的子密钥 (索引号从 0x00 到 0x7fffffff (0 to 2 的 21 次方减 1) 会生成正常的密钥;索引号从 0x80000000 到 0xffffffff 会生成增强密钥)。CKD 采用不可逆的 HMAC-SHA512 不可逆加密算法,子密钥不能向上推导出父密钥、同时也不能水平推导出同一级的密钥。
扩展密钥
CKD 推导子密钥的三个元素中,其中父密钥和链码结合统称为扩展密钥 (Extended keys)。256 位的密钥和 256 位的链码串联起来的 512 位就是扩展密钥。
- 包含私钥的扩展密钥用以推导子私钥,从子私钥又可推导对应的公钥和比特币地址
- 包含公钥的扩展密钥用以推导子公钥
扩展密钥使用 Base58Check 算法加上特定的前缀编码,编码得到的包含私钥的前缀为 xprv, 包含公钥的扩展密钥前缀为 xpub,相比比特币的公私钥,扩展密钥编码之后得到的长度为 512 或 513 位
子公钥推导
上述方法中通过推导出的私钥可推导出对应公钥,但 HD 钱包非常好用的特征之一就是在隐藏私钥的前提下通过公钥推导出子公钥,极大加强安全性。在只需要生成地址接受比特币而无权消费的场景下非常有用,通过公钥扩展密钥能生成无穷尽的公钥和比特币地址。
增强扩展密钥推导
密钥需加强保管以免泄漏,泄漏私钥意味着对应的地址上的币可被转走、泄漏公钥意味着 HD 钱包的隐私被泄漏。增强密钥推导 (Hardened child key derivation) 解决下述两个问题:
- 1、虽然泄漏公钥并不会导致丢币,但含有公钥的扩展密钥泄漏会导致以此为根节点推导出来的扩展公钥全部泄漏,一定程度上破坏了隐私性。
- 2、泄漏扩展公钥加上该公钥推导出的后任一代扩展公钥对应的私钥有被推导出该扩展公钥的所有后代私钥的可能性
于此,BIP32 协议把 CKD 函数改为 HKD (hardened key derivation formula) 生成增强密钥推导函数。
CKD 函数以推导扩展密钥的序列号 ( 0x00 到 0x7fffffff)、父链码和父公钥生或父私钥成子链码和子公钥,子私钥从父私钥推导;而 HKD 通过父私钥、父链码和推导增强扩展密钥的序列号 (0x80000000 到 0xffffffff) 增强子私钥和增强子链码。
HD 钱包密钥路径表示
HD路径 | 密钥描述 |
---|---|
m/0 | 从主私钥(m) 推导出的第一代的第一个子私钥 |
m/0/0 | 从第一代子密钥m(0)推导出的第二代第一个孙私钥 |
m/0’/0 | 从第一代增强密钥 (m/0’)推导出得第二代第一个孙密钥 |
m/1/0 | 从第一代的第二个子密钥推导出的第二代第一个孙密钥 |
bip44
基于 BIP32 的系统,赋予树状结构中的各层特殊的意义。让同一个 seed 可以支援多币种、多帐户等。各层定义如下:
m / purpose’ / coin’ / account’ / change / address_index
m 是固定的,Purpose 也是固定的,值为 44(或者 0x8000002C)
1、Coin type
这个代表的是币种,0 代表比特币,1 代表比特币测试链,60 代表以太坊, 118 cosmos的ATOM
完整的币种列表地址:https://github.com/satoshilabs/slips/blob/master/slip-0044.md
2、Account
代表这个币的账户索引,从 0 开始
3、Change
常量 0 用于外部链,常量 1 用于内部链(也称为更改地址)。外部链用于在钱包外可见的地址(例如,用于接收付款)。内部链用于在钱包外部不可见的地址,用于返回交易变更。 (所以一般使用 0)
4、address_index
这就是地址索引,从 0 开始,代表生成第几个地址,官方建议,每个 account 下的 address_index 不要超过 20
Cosmos中的公私钥
cosmos使用256bit的熵来生成 24个助记词。
cosmos-sdk/client/keys/add.go
if len(mnemonic) == 0 {
// read entropy seed straight from crypto.Rand and convert to mnemonic
entropySeed, err := bip39.NewEntropy(mnemonicEntropySize)
if err != nil {
return err
}
mnemonic, err = bip39.NewMnemonic(entropySeed[:])
if err != nil {
return err
}
}
mnemonicEntropySize 为常量 256
cosmos-sdk/crypto/keys/keybase.go
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) {
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
if err != nil {
return
}
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
return
}
cosmos-sdk/crypto/keys/keybase.go
func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info Info, err error) {
// create master key and derive first key:
masterPriv, ch := hd.ComputeMastersFromSeed(seed)
derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, fullHdPath)
if err != nil {
return
}
...
}
masterPriv, ch := hd.ComputeMastersFromSeed(seed)
通过种子生成 主私钥和主链码
derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, fullHdPath)
生成最终的一个主私钥 derivedPriv