区块链开发:比特币入门之使用分层确定性密钥
一、概述
一旦我们开始自己管理密钥与地址,很快就会发现,备份密钥 是一件很痛苦的事情:只要生成一个新的地址,你就需要备份一次。
这是因为我们生成的密钥之间没有什么关联,你不可能从一个 密钥推导出另一个密钥。通常情况下,这不是问题。但是,如果 你的网站每天需要为成千上万的订单生成地址,就是另一回事了。 而分层确定性密钥(Hierarchical Deterministic Key)就是 为解决这一密钥管理问题而提出的解决方案:
分层(Hierarchichal)指的是密钥之间存在层级关系,从父密钥可以 生成子密钥。例如在上图中,从主密钥m可以生成第一层子密钥m/0、m/1... 而从第一层的密钥又可以继续生成第二层的密钥密钥,例如m/1/0、 m/1/1...如此不断延伸,就构成了以主密钥为根节点的一颗分层密钥树了。
确定性(Deterministic)指的是,根据密钥在层级中的编号,就可以从 父密钥确定性地推导出该密钥的具体内容。例如在上图中,我们可以从主密钥 m推导出任何一个指定编号的后代密钥,例如m/1/1/3。层级密钥的确定性使得我们 只需要备份主密钥并记录后代密钥的编号就可以了。
二、生成主密钥
使用分层确定型密钥树的第一步,是首先生成层级密钥树的主密钥,下图展示 了主密钥生成的主要流程。和普通的密钥生成类似,种子数据(熵)用来增加 密钥的不可预测性:
熵经过HAMC哈希变换后,得到的512位数据拆分为两部分:主链码和主私钥。主私钥 可以继续推导出主公钥,而主链码则可以作为子密钥生成的熵,提高子密钥的 不可预测性。
在NBitcoin中,使用ExtKey类来表征层级确定密钥:
可以利用种子或者传入一个Key实例来生成层级主密钥对象,例如:
Key key = new Key();ExtKey masterKey = new ExtKey(key);string cc = Encoders.Hex.EncodeData(masterKey.ChainCode); //链码string prv = Encoders.Hex.EncodeData(masterKey.PrivateKey); //私钥string pub = masterKey.PrivateKey.PubKey.ToHex(); //公钥
不过为了便于备份层级密钥树,通常我们会选择使用助记词来生成种子, 进而推导出主密钥。例如,下面的代码将生成助记词,最后将 助记词转换为层级主密钥:
//生成并保存助记词Mnemonic mc = new Mnemonic(Wordlist.Englisht);File.WriteAllText("./mnemonic.txt",mc.ToString());//载入助记词,生成主密钥string words = File.ReadAllText("./mnemonic.txt");Mnemonic mc2 = new Mnemonic(words,Wordlist.Englisth);ExtKey masterKey = mc2.DeriveExtKey("whoami"/*password*/);using NBitcoin;using NBitcoin.DataEncoders;using System;using System.IO;namespace Newmnemonic{ class Program { static void Main(string[] args) { Mnemonic mnemonic = new Mnemonic(Wordlist.English); Console.WriteLine("mnemonic => {0}", mnemonic); byte[] seed = mnemonic.DeriveSeed("whoami"); Console.WriteLine("seed => {0}", Encoders.Hex.EncodeData(seed)); File.WriteAllText("../mnemonic.txt", mnemonic.ToString()); string sentence = File.ReadAllText("../mnemonic.txt"); mnemonic = new Mnemonic(sentence, Wordlist.English); Console.WriteLine("mnenomic => {0}", mnemonic); ExtKey xkey = mnemonic.DeriveExtKey("whoami"); Console.WriteLine("master private key => {0}", Encoders.Hex.EncodeData(xkey.PrivateKey.ToBytes())); Console.WriteLine("master public key => {0}", xkey.PrivateKey.PubKey.ToHex()); Console.WriteLine("master chaincode => {0}", Encoders.Hex.EncodeData(xkey.ChainCode)); Console.ReadLine(); } }}
三、派生子密钥
在层级密钥树中,使用父密钥(Parent Key)和父链码(Parent Chaincode), 就可以推导出指定序号的子密钥:
在上图中参与单向哈希运算的三个信息:父公钥、父链码和子密钥 序号一起决定了HMAC哈希的512位输出,而这512位输出的一半将作为子密钥的链码, 另一半则分别用于生成子公钥和子私钥。
在NBitcoin中,使用ExtKey实例的Derive()方法, 就可以生成指定指定编号的子密钥及链码了:
例如,下面的代码生成主密钥的7878#子密钥并显示其链码、私钥WIF和公钥:
ExtKey key7878 = masterKey.Derive(7878);Console.WriteLine("hd-key 7878 chaincode => {0}",key7878.ChainCode); //链码Console.WriteLine("hd-key 7878 private key => {0}", key7878.PrivateKey); //私钥Console.WriteLine("hd-key 7878 public key => {0}",key7878.PrivateKey.PubKey); //公钥
无私钥派生
值得指出的是,只需要父公钥和父链码就可以推导出指定编号的子公钥和 子链码,这意味着可以在不泄露主私钥的情况下动态生成子公钥(以及地址), 当你为网站增加比特币支付功能时,这一特性非常有意义:
ExtPubKey masterPub = masterKey.Neuter(); //剔除私钥ExtPubKey pub7878 = masterPub.Derive(7878); Console.WriteLine("pub key 7878 public only derivation => {0}",pub7878.PubKey); //仅公钥推导using NBitcoin;using NBitcoin.DataEncoders;using System;namespace DeriveChildkey{ class Program { static void Main(string[] args) { ExtKey xkeyMaster = new ExtKey(); ExtKey xkey_38 = xkeyMaster.Derive(38); Console.WriteLine("child#38 prv key => {0}", Encoders.Hex.EncodeData(xkey_38.PrivateKey.ToBytes())); Console.WriteLine("child#38 pub key => {0}", xkey_38.PrivateKey.PubKey.ToHex()); ExtPubKey xpkey_38 = xkey_38.Neuter(); ExtPubKey xpkey_38_6 = xpkey_38.Derive(6); Console.WriteLine("child#38#6 pub key => {0}", xpkey_38_6.PubKey.ToHex()); Console.ReadLine(); } }}
四、使用扩展密钥
在生成子密钥的过程中,最重要的两个参数,就是密钥和链码了。因此 如果在父密钥的表示当中包含这两部分信息,就可以直接使用父密钥来 生成子密钥了 —— 这就是扩展密钥/Extended Key的直观含义:
我们可以使用层级密钥对象的serializePubB58()或serializePrivB58()方法将 其转换为扩展密钥形式,也可以使用层级密钥类的静态方法deserializeB58()将一个扩展 密钥恢复为层级密钥:
例如,下面的代码创建一个随机主密钥,派生7878#子密钥,然后分别 生成其扩展私钥和扩展公钥:
ExtKey masterKey = new ExtKey(); //随机生成层级主密钥ExtKey key7878 = masterKey.Derive(7878);BitcoinExtKey bxk7878 = new BitcoinExtKey(key7878,Network.RegTest); //扩展私钥BitcoinExtPubKey bxpk7878 = bxk7878.Neuter(); //扩展公钥
需要指出的是,扩展密钥使用前缀区分不同的网络,因此我们也需要在生成扩展密钥 时,传入特定的网络对象:
也可以从从扩展密钥恢复出对应的层级密钥,例如
String xprv = "tprv....";BitcoinExtKey bxk = new BitcoinExtKey(xprv,Network.RegTest); //导入扩展密钥ExtKey key = bxk.ExtKey; //获得层级密钥using NBitcoin;using NBitcoin.DataEncoders;using System;namespace Extendedkey{ class Program { static void Main(string[] args) { ExtKey xkMaster = new ExtKey(); ExtKey xk_78 = xkMaster.Derive(78); Console.WriteLine("child#78 prv key => {0}", Encoders.Hex.EncodeData(xk_78.PrivateKey.ToBytes())); BitcoinExtKey bxk_78 = new BitcoinExtKey(xk_78, Network.RegTest); Console.WriteLine("child#78 extended prv key => {0}", bxk_78); Console.WriteLine("child#78 extended pub key => {0}", bxk_78.Neuter()); string bxkText = bxk_78.ToString(); BitcoinExtKey bxkRestored = new BitcoinExtKey(bxkText, Network.RegTest); Console.WriteLine("restored child#78 prv key => {0}", Encoders.Hex.EncodeData(bxkRestored.ExtKey.PrivateKey.ToBytes())); Console.ReadLine(); } }}
创建一个随机主密钥
从主密钥派生78号子密钥,导出其扩展公钥和扩展私钥并存入文件
从文件中读取扩展私钥,并将其转化为对应的层级密钥
五、使用强化派生密钥
扩展密钥同时包含了链码和密钥信息,这对于继续派生子密钥很方便, 但同时也带来了安全上的隐患。下图中展示了第N层链码和公钥及其某个 后代私钥泄漏的情况下,受影响的公钥和私钥:
解决的办法是改变密钥派生的算法,使用父私钥而不是父公钥来生成子链码 及子密钥,这样得到的子密钥被称为强化密钥(hardened key):
比特币根据子密钥序号来区分派生普通密钥还是强化密钥:当序号 小于0x80000000时,生成普通子密钥,否则生成强化子密钥。
例如,下面的代码分别生成普通子密钥和强化子密钥:
int id = 123;ExtKey normalKey = masterKey.Derive(id);ExtKey hardenedKey = masterKey.Derive(id,true);
显然,你需要从一个包含私钥的层级密钥才能派生强化子密钥
using NBitcoin;using System;namespace Hardenedkey{ class Program { static void Main(string[] args) { ExtKey xkey = new ExtKey(); ExtKey normalChild = xkey.Derive(12); Console.WriteLine("normal child key => {0}", normalChild.IsHardened); ExtKey hardenedChild = xkey.Derive(12, true); Console.WriteLine("hardened child key => {0}", hardenedChild.IsHardened); Console.ReadLine(); } }}
六、路径表示法
在使用层级确定性密钥时,使用路径表示法可以方便地定位到一个远离 若干层的后代密钥。例如,在下面的图中分别表示了密钥m/1'/1'和 M/2/3在整个层级密钥树中的亲缘关系:
路径的各层之间使用/符号隔开,M表示主公钥,密钥序号之后使用H则表示 这是一个强化派生密钥,否则就是一个普通派生密钥。
在NBitcoin中首先使用KeyPath类静态方法ParsePath()将指定的路径字符串 解析为KeyPath实例,然后再调用Derive()方法创建密钥。例如:
KeyPath path = KeyPath.Parse("M/1H/2H/3");ExtKey descentKey = masterKey.Derive(path);
BIP44 给出了一种五层路径划分的建议,可用于多个币种:
你可以根据自己的需求决定是否采用这一建议方案。
using NBitcoin;using NBitcoin.DataEncoders;using System;namespace DeriveChildkeyPath{ class Program { static void Main(string[] args) { ExtKey master = new ExtKey(); KeyPath path = KeyPath.Parse("m/44'/0'/0'/0/123"); ExtKey derived = master.Derive(path); Console.WriteLine("descent prv key => {0}", Encoders.Hex.EncodeData(derived.PrivateKey.ToBytes())); Console.WriteLine("descent pub key => {0}", derived.PrivateKey.PubKey.ToHex()); Console.ReadLine(); } }}
出处:http://www.cnblogs.com/5ishare/
本文版权归作者和博客园共有,未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如文中有误,欢迎指出。以免更多的人被误导。
- 免责声明
- 世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
- 风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
- 世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。