在比特币的链条中,其实没有账户这个概念。一个用户持有的比特币实际上是其控制的一组utxo,这些utxo可能是同一个地址(对应同一个私钥),也可能是不同的地址(对应不同的私钥)。
出于隐私保护的目的,如果同一个用户控制的UTXO的地址都不一样,就很难从地址得知一个用户持有的比特币总量。但是,管理一组几千个地址就意味着管理几千个私钥,管理起来非常麻烦。
你能用一个私钥管理数千个地址吗?其实是有可能的。虽然椭圆曲线算法决定了一个私钥只能对应一个公钥,但是也可以使用某种确定性算法来确定一个私钥k1,然后计算出其他私钥如k2、k3、K4等。这意味着只需要管理一个私钥,其余的私钥可以根据需要计算。
这种按照一定的确定性算法,只管理一个根私钥,就能实时计算出所有“子私钥”的管理方式,被称为HD wallet。
HD是分层确定性的缩写,意思是分层确定性。先确定根私钥根,然后根据索引计算每层的子私钥:
对于任何私钥K,下一个私钥kn总是可以根据索引来计算:
kn=hdkey(k,n)
也就是说,HD层次实际上是无限的,每个层次的索引从0到232不等,大约有43亿个子键。这种计算叫做求导。
k_n=hdkey(k,n)即HD层次结构实际上是无限的,每个层次结构的索引从0到23.2不等,大约有43亿个子键。这种计算叫做求导。
现在问题来了:如何根据一个私钥计算下一层的子私钥?即如何实现函数hdkey(k,n)?
高清钱包使用的子私钥计算算法不是简单的SHA-256,私钥也不是常见的256位ECDSA私钥,而是扩展的512位私钥,记录为xprv。它通过SHA-512算法和ECC计算子扩展私钥,仍然是512位。通过扩展私钥,可以计算出用于签名的私钥和公钥。
简单来说,只要给定一个根扩展私钥(一个随机的512位整数),就可以计算出任意索引的子扩展私钥。扩展私钥总是可以计算扩展公钥,它被记录为xpub:
从xprv及其对应的xpub中,可以计算出真正用于签名的私钥和公钥。之所以要设计这个算法,是因为xpub这种扩展公钥还有一个特点,就是可以直接计算出其子级的扩展公钥:
因为xpub只包含公钥,不包含私钥,所以可以放心交给第三方(比如观察钱包),第三方可以根据xpub计算出子级的所有地址,然后监控这些地址在比特币链上的余额,但是因为没有私钥,所以只能看,不能花。
因此,高清钱包通过分层确定性算法实现了以下功能:
只要确定了扩展私钥xprv,就可以根据索引计算下一层的任意扩展私钥;只要确定了扩展公钥xpub,就可以根据索引计算下一层的任意扩展公钥;用户只需要在顶层保存一个扩展私钥,就可以计算任意级别任意索引的扩展私钥。理论上,扩展私钥的层数是没有限制的,每一层的层数限制在0 ~ 232,因为扩展私钥中只有4个字节作为索引,所以索引范围是0 ~ 232。
通常根扩展私钥记为m,子扩展私钥根据层次等记为m/x/y/z。
例如,m/0/2表示从m延伸到m/0(索引0),然后延伸到m/0/2(索引2)。
安全高清钱包给私钥管理带来了极大的便利,因为只需要管理一个根扩展私钥就可以管理所有派生的各级私钥。
但是高清钱包的扩展私钥算法有一个潜在的安全问题,就是如果某一级xprv泄露,可以逆向推导出上一级xprv,进而推导出整个高清扩展私钥系统。为了避免子扩展私钥的泄露,对上层扩展私钥进行反向推导。HD wallet还有一个硬化的推导,通过一个算法“切断”母分机私钥和子分机私钥的反向推导。HD规范以索引0 ~ 231为普通衍生索引,索引231 ~ 232为硬化衍生索引。硬化衍生指数通常记录为0 '1 '2 '也就是说,索引0'=231,1'=231 1,2'=231 2,依此类推。
所以m/44'/0表示的子扩展私钥,其一级导数索引44 '是硬化导数,实际索引为231 44=2147483692。M/44 '不能从m/44'/0推导出来。
在只有扩展公钥的情况下,只能计算普通的派生子公钥,而不能计算硬化的派生子公钥,即计算的子扩展公钥索引限于0 ~ 231。所以可以用来观察钱包的指数是0 ~ 231。
BIP-32比特币的BIP-32规范详细定义了HD算法的原理和各种推导规则。可以看这个文档实现高清钱包。
总结高清钱包采用分层确定性算法,通过根扩展私钥计算所有级别的所有子扩展私钥,进而得到扩展公钥和地址;
扩展的子私钥可以通过普通推导和硬化推导来计算,后者更安全,但对应的扩展公钥不能通过硬化推导来计算。
通过扩展公钥,可以在不扩展私钥的情况下计算所有普通的子扩展公钥。这个功能可以实现钱包观察。