Wallet 钱包

一般来说,钱包是一个用户界面,通过钱包可以对链上资产进行访问、管理、显示余额,创建交易,并对交易进行签名,根据提供的功能,也可以和合约进行交互。

钱包主要分为两类,类型区分主要是取决于所保存的密钥之间是否有关联。

第一类是非确定性钱包,就是钱包中的的每个私钥都是通过不同的随机数生成的,他们之间没有什么关联,这类被称为 Just a Bunch Of keys (JBOK)钱包。

第二类钱包就是确定下钱包,其特点是不再通过随机数来生成私钥,并且所有的密钥都是从一个主密钥而来,这样钱包中的所有密钥之间都存在关联关系,确定性钱包有多种派生办法,最常用的一种是类似树形结构的,称之为层级确定性(hierarchical deteministic)HD 钱包。

但是现在主流钱包这两种生成私钥方式都是支持的。

非确定性(随机生成)钱包

早期区块链中的钱包都是随机生成一个私钥供用户使用,每个私钥对应一个公钥,然后再从公钥生成地址,现在已经不流行这种方式,因为这种方式每个地址都对应一个不同的私钥,管理,储存,使用都是比较麻烦的事情,现在确定性钱包比较流行。

例如以太坊会使用一个JSON格式的keystore文件来单独保存随机生成的私钥,文件使用额外的密码加密,从而确保安全,在使用的时候需要导入这个JSON文件,然后再使用密码进行解密。

但是JSON中的私钥并不是简单的被密码直接加密,相反,密码会通过连续的hash运算进行扩展,然后再进行加密运算,hash计算会重复262144轮,JSON文件的deterministic字段中会显示这个次数,这样做是防止密码被暴力破解,因为密码经过了极多次数的hash计算,这将导致了暴力破解需要极大地时间和算力成本,只要密码不是太简单,基本没有被破解的可能。

确定性(种子密钥)钱包

确定下钱包,包含了从一个种子密钥(主密钥)所派生的私钥,而种子密钥是一个随机生成的数字加一些其他数据,在确定性钱包中,种子密钥可以用来回复所有派生的密钥,因此用户只需要保护好种子密钥,就足以保护资金和智能合约的安全。并且种子密钥也可以进行导入导出操作,从而可以在不同软件之间进行迁移。

层级式确定性钱包 BIP-32/BIP-44

目前使用的确定性钱包标准是比特币的(BIP-32)[https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki]中定义的,其中定义了使用树形结构推导多个密钥,使用一个私钥可以推导出一系列子密钥,同时每个子密钥又可以推导出一系列孙子密钥。

而(BIP-44)[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki]中允许一个种子,生成多个不同coin类型的的地址。

种子和助记词 BIP-39

目前生成密钥种子的主流方法是使用一组助记词,通过一组特定顺序的单词,就可以创建,或者恢复私钥,也就是常说的助记词,这个方法在(BIP-39)[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki] 中进行了标准化,大多数助记词都是用这个标准,生成助记词,并将助记词转化为种子。

对于人的读写来说 16 进制的字符串相当难以记录和书写,当然出错几率也大很多,但是对于单词,就比较好记录,这些单词可以安全的写在纸上或者其他方式记录,但是不要将正确的顺序也记录下来不然别人就有可能掌控了你的账户,所以可以记录这些单词,但是通过另一种方式记录这些单词的顺序。

所以从单一种子为多种数字货币生成密钥建立在一下标准之上:

  1. BIP-39 助记词的标准
  2. BIP-32 层级式的确定性钱包标准
  3. BIP-43 多用途层级确定式钱包结构
  4. BIP-44 多种币种和多账户钱包

这些标准被大多数钱包所采用,所以可以将一个助记词从一个钱包导入到另一个钱包中,从而迁移自己的所有资产。

助记词到种子

助记词是根据BIP-39中定义的所生成的,钱包从随机源获得一个随机数,然后加上校验码,然后再把这些数字映射为英文单词。JavaScript可以使用 BIP-39 中提到的 (bitcore-mnemonic)[https://github.com/bitpay/bitcore/tree/master/packages/bitcore-mnemonic] 或者 (bip39)[https://github.com/bitcoinjs/bip39], 生成助记词之后,助记词单词的个数可以是 12 到 24个。

这里助记词代表了 128或者256比特的随机数(包含校验值),使用了密钥扩展算法,例如 PBKDF2,然后就可以将这个助记词扩展为512比特长的种子,然后使用这个512比特的种子,来构建确定性钱包和派生其他密钥。

使用密钥扩展算法需要两个参数:助记词和salt(盐),加盐有两个目的,目的一是这里的盐可以是用户设置的密码,这样哪怕有助记词没有密码也无法,得到正确的种子密钥,另一种是防止通过循环表格的方式进行暴力破解。

标准中提到如果没有设置密码,那么使用 mnemonic 进行助记词扩展运算,从而生成 512 bite的种子密钥,这里需要注意的是,在知道助记词的情况下,无论密码是否正确,都是可以得到一个种子密钥的,所有密码都可以生成无数钱包地址的种子密钥,所以基本上只要密码足够复杂,是没有暴力破解的可能得。

种子创建层级式确定性钱包

HD钱包是通过唯一的种子密钥创建的,种子密钥通常是上面提到的方法生成的,HD钱包每一个密钥都是这个种子密钥派生的,这使得导入,恢复,导出都变得比较容易。只需要在钱包中迁移种子密钥的助记词即可。

确定性钱包标准是使用了 BIP-32 和路径定义标准 BIP-43 和 BIP-44,大部分钱包都是用这个标准,所以基本上已经成为了行业标准。

在 BIP-32 标准中,密钥是可以被扩展的,使用正确的数学运算,经过扩展的 父 密钥就可以被用来推导出 子 密钥,因此产生了密钥和地址的树状层级结构。

HD 钱包的非常有用的特征就是可以从父公钥派生子公钥,这个过程就不需要使用私钥,因此有两种方式生成子公钥:1. 使用子私钥 2. 使用对应的父公钥。

在 BIP-43 中,使用第一级增强扩展子密钥的索引码作为一个特殊指示符,指定改分支的“用途”, 因此基于 BIP-43 一个HD钱包只应用一个 level-1 层,这个索引码确定了剩余树的结构和命名空间,也确定了钱包的用途。

在这个基础上 BIP-44 提出了一个支持多币种多账号结构的嘻嘻,改结构将用途索引码定位44‘。所以遵循 BIP-44 标准的钱包都只使用树形结构的这一个m/44’/*分支

BIP-44定义的结构中包含了五中预定义的树形层次

1
m / purpose' / coin_type' / account' / change / address_index

purpose 的值总是44’, coin_type 用来指定当前分支支持的比重,以实现HD钱包对多个币种的支持。(coin-type)[https://github.com/satoshilabs/slips/blob/master/slip-0044.md]中标识了所有已注册的币种对应的值。

例如m/44’/60’是以太坊 m/44’/503’是Conflux

account’ 层允许用户吧钱包分成逻辑上的子账户,用户记账或者管理, 从而形成多个不同分支。

change’ 层第一层0被用来创建接收地址,第二层1用来创建找零地址。

address_index 层从0开始递增的不同地址,例如 m/44’/503’/0‘/0/2 表示主账号第三个地址, 如果你有是个地址那么这一位应该是9

在JavaScript中可以使用 ethers 来从助记词生成私钥或者公钥,并且可以通过树形层次(path)派生新的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {HDNode} from '@ethersproject/hdnode'

const hdnode = HDNode.fromMnemonic(mnemonic)
console.log(hdnode.address)
console.log(hdnode.publicKey)
console.log(hdnode.privateKey)

// 根据path派生新的子节点

const childnode = hdnode.derivaPath('m/44'/503'/0‘/0/0')
console.log(childnode.address)
console.log(childnode.publicKey)
console.log(childnode.privateKey)