HDウォレットのマスターキー生成

詳解ビットコインの続きを読む。

3章はビットコインアドレスについて。送信の宛先としてトランザクションに書くのは公開鍵ハッシュであって、それをbase58checkでエンコードしたアドレスを書くのではない。アドレスは「ここに送ってね」というユーザー間の連絡をやり易くするために使う。トランザクションには、公開鍵ハッシュにデコードして書き込む。

4章はウォレットについて。HDウォレットは、非決定性ウォレットの特別版で、特殊なケースで使用する物だと思っていたが、思い直した。鍵は使う度に新しい物を用意するのが通常であることを考えると、一つのシードから多数の鍵を生成するHDウォレットの方がよく使われるのだろうと思う。理論的なシカケやセキュリティすべき配慮は理解できていないが、一つのシードから鍵がいくらでも作れるということと、このシードさえ失わなければ生成した鍵を無くしても再現できるというは分かった。

Bitcoin Coreのコードも少し読む。

マスターキーの生成を実装しているコードを探す。(日本語解説)

(src/key.cpp)
 301: void CExtKey::SetSeed(const unsigned char *seed, unsigned int nSeedLen) {
 302:     static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'};
 303:     std::vector<unsigned char, secure_allocator<unsigned char>> vout(64);
 304:     CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(vout.data());
 305:     key.Set(vout.data(), vout.data() + 32, true);
 306:     memcpy(chaincode.begin(), vout.data() + 32, 32);
 307:     nDepth = 0;
 308:     nChild = 0;
 309:     memset(vchFingerprint, 0, sizeof(vchFingerprint));
 310: }

引数でシード(seed)を受けとり、304行目でキー(hashKey(= “Bitcoin seed”))とシードからHMAC-SHA512を生成してvoutに格納している。voutに格納した64バイトの前半32バイトは305行目でマスターキーとしてセットし、後半32バイトは306行目でchaincodeにコピーしている。

(src/wallet/wallet.cpp)
4379: void CWallet::SetupDescriptorScriptPubKeyMans()
4380: {
4381:     AssertLockHeld(cs_wallet);
4382: 
4383:     // Make a seed
4384:     CKey seed_key;
4385:     seed_key.MakeNewKey(true);
4386:     CPubKey seed = seed_key.GetPubKey();
4387:     assert(seed_key.VerifyPubKey(seed));
4388: 
4389:     // Get the extended key
4390:     CExtKey master_key;
4391:     master_key.SetSeed(seed_key.begin(), seed_key.size());
...
4410: }

関数CExtKey::SetSeedは4391行目で呼び出しており、このとき引数で指定しているシード(seed_key)は4383行目で生成している。

(src/key.cpp)
 157: void CKey::MakeNewKey(bool fCompressedIn) {
 158:     do {
 159:         GetStrongRandBytes(keydata.data(), keydata.size());
 160:     } while (!Check(keydata.data()));
 161:     fValid = true;
 162:     fCompressed = fCompressedIn;
 163: }

5章はトランザクションについて。トランザクションによる出力(送金先)をUTXO(Unspent Transaction Output)と呼んでいるが、別の新しいトランザクションの入力にしたら未使用(unspent)な状態ではなくなってしまうので、どう扱っているのか疑問に思っていた。このトランザクションを作成したときの「出力」には未使用を表すフラグを付けておいて、あとで別のトランザクションの入力として使用したらフラグを落とすのかと思っても、ブロックに格納されたトランザクションは後から変更できないので、こんな実装ではないのだろうと悩んでいた。トランザクションの検証をするノードがUTXOセットを構築、維持しているという記述を読んでこの疑問が解決した。未使用を表すフラグがある訳ではなく、あるトランザクションの送金先に使用可能な金額が残っているかは、ブロックに格納された全てのトランザクションを集計して求めている。ノードでは最初にこの集計をして、その結果をUTXOの集合(UTXOセット)として持っており、新しく生成したブロックに格納したトランザクションを見て、UTXOセットを更新する。分かってしまえばそんな難しい話でもない。