I.概要
我々はすぐにわかります、自分のキーとアドレスを管理するために開始すると、バックアップキーは非常に痛みを伴うことです:ちょうどあなたが再びバックアップする必要があります、新しいアドレスを作成します。
これは、私たちの世代のキーの間にはほとんど相関関係があるので、あなたは別のキーに一つのキーから派生することはできませんです。通常の状況下では、これは問題ではありません。あなたのサイトは一日あたりの注文数千人のためのアドレスを生成する必要がある場合は、それは別の問題です。階層的な決定論キー(階層的な決定論キー)キー管理の問題と解決策を解決することです。
積層は、( Hierarchichal
)キー間の階層関係があることを意味し、マスターキーは、サブ鍵から生成することができます。例えば、上の図では、マスターキーは、最初のmサブ層キーから生成することができるm/0
、m/1
...と、例えば、第一の層の第2層の鍵から鍵を生成し続けることができm/1/0
、 m/1/1
そう...階層キーツリーのルートノードとしてマスターキーを構成するために、拡張する続けます。
不確実性は、( Deterministic
)階層の鍵番号に基づいて、主要な不確実性は、特定のコンテンツの親キーから導出することができる、意味します。例えば、上の図では、我々は、マスターキー、キーの任意の数からm個の子孫を導き出すことができ、例えば、/ 1/1/3をmです。不確実性のレベルは、我々だけでマスターキーをバックアップし、その上に子孫キーの数を記録するために必要なキーになります。
第二に、マスターキーの生成
階層キーツリーを使用してタイプを決定する最初のステップは、第一のマスターキー階層キーツリーを生成することで、下の図は、マスター鍵生成の主要工程を示しています。キーの予測不可能性を増加させるための共通鍵の生成など、シード・データ(エントロピー):
主骨格とコード秘密鍵:エントロピーをハッシュした後HAMCデータ512 2つの部分に得られる分割を形質転換しました。マスター秘密鍵は、マスター公開鍵を導出し続けることができ、主鎖は、予測不可能サブキーを改善するエントロピー符号サブキー世代として使用することができます。
NBitcoinでは、レベルを特徴付けるために使用されるキーはExtKeyクラスを決定しました。
種子は、例えば、オブジェクト階層を生成するマスター鍵を渡すか、キーのインスタンスができます。
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(); } } }