こんにちは。AndGoのハードウェア担当です。

「直感的に理解する」シリーズの第3段ということで階層決定性ウォレット(HDウォレット)について解説していきたいと思います。過去のシリーズはこちら↓

【2022/08/10】楕円曲線暗号による電子署名(ECDSA)を直感的に理解する
(https://coinkeninfo.com/explanation-on-ecdsa/)

【2022/07/20】楕円曲線暗号の概要を直感的に理解する
(https://coinkeninfo.com/elliptic-curve-cryptography/)

階層決定性ウォレットは一つのシードから秘密鍵・公開鍵ペアを生成することができるもので,ウォレットの実装の容易さやプライバシーの保護に役立っています。

皆さんが使っているウォレットが故障してしまい,シードフレーズから復元する場合には,パスフレーズの他に,階層決定性ウォレットに関する情報も必要です。これは見逃しがちで階層決定性ウォレットに関する情報がわからなくなってしまうと,ウォレットは復元できても,資産を失ってしまうことになりかねません。

ここでは階層決定性ウォレットの原理を直感的に理解した上で,これらの重要性や使い方について考えていきたいと思います。

また階層決定性ウォレットについては弊社CEOの原よりセキュリティに関する衝撃的な記事がございますので合わせてご覧ください。

【2021/10/13】階層決定性ウォレットのセキュリティ
(https://coinkeninfo.com/hdwallet-security/)

階層決定性ウォレットとは?

以前,楕円曲線暗号をつかった秘密鍵と公開鍵について解説をしました。原理上は一度生成した秘密鍵と公開鍵は使い回すことはできます。しかし,ビットコインはその仕組み上,どのビットコインアドレスからどのビットコインアドレスにいくら送金したかが分かってしまいます。したがってプライバーシーの観点からは秘密鍵・公開鍵を使い回すことは得策ではありません。

秘密鍵・公開鍵は実質上無限に生成することができますので,取引の度にアドレスを生成する方法も考えられますが,複数ある秘密鍵をバックアップする度にシードフレーズを生成して保管しなければなりません。

そこで一つの秘密鍵から別の秘密鍵・公開鍵ペアを生成する方法がBIP32で定められています。これが階層決定性ウォレットです。

マスター秘密鍵から多くの公開鍵・秘密鍵を生成し,階層的なインデックスをつかって「m/84’/0’/1’/0/1」のように管理します。マスター秘密鍵と階層構造のインデックスが決まると,そこから生成される公開鍵・秘密鍵が一意に決定するため,階層決定性ウォレットといいます。もちろんバックアップが必要なものはマスター秘密鍵を生成するためのシードを一つのみです。

このしくみによってプライバシーの保護とバックアップの簡便さが両立します。

階層決定性ウォレットのしくみ

しくみが若干複雑で,親鍵から子鍵を生成する方法がいくつかあるので図にまとめてみました。

マスター秘密鍵・公開鍵ペアの生成

まず以下の図はシードからマスター秘密鍵・公開鍵ペアの生成と,親秘密鍵から子秘密鍵・子公開鍵のペアを生成するための方法です。

まず乱数発生器からシードを生成します。これは128〜256ビットの長さを持つものとなります。シードフレーズ(復元のための英単語)は128ビットのシードであれば12単語,256ビットであれば24単語の長さになります。もちろんシードフレーズからシードに復元も可能です。

次にHMAC-SHA512という関数によってシードを秘密鍵とチェインコードというものに変換します。HMAC-SHA512の上位256ビットは秘密鍵,下位256ビットはチェインコードになります。HMAC-SHA512はハッシュ関数を使っていますので,逆演算はできません。

シードから生成された秘密鍵はマスター秘密鍵といい階層決定性ウォレットの最上位にある秘密鍵となります。そして,マスター公開鍵はマスター秘密鍵を楕円曲線上でG倍することによって求めることができます

子秘密鍵・子公開鍵ペアの生成

ここでこれらから子秘密鍵を導出します。マスター公開鍵と先程生成したチェインコードに加えて,インデックスを加えます。インデックスは32ビットの整数です。原理上好きな数字を使うことができますが,インデックスの使い方にはBIPで決められています。

マスター公開鍵・チェインコード・インデックスはHMAC-SHA512によって512ビットの値に変換されます。上位256ビットにマスター秘密鍵を加えることで(実際にはさらに256で割った余り)子秘密鍵とします。下位256ビットは子の階層のチェインコードになります。そして子公開鍵も楕円曲線暗号を用いてマスター公開鍵同様に求めることができます。

子秘密鍵と子公開鍵の導出方法を式で書くと以下のようになります。(HはHMAC-SHA256関数です。またm/iのスラッシュは割り算ではなくi番目の子秘密鍵を示します。)

\[ m/i = H(M, c, i) + m \]

\[ M/i = (H(M, c, i) + m)*G \]

最後に秘密鍵を加える理由について説明します。マスター公開鍵とマスターチェインコードは公開情報です。つまり誰でも子秘密鍵を生成することができてしまいます。ここで所有者のみが知っている親秘密鍵を加えることで第三者が勝手に子秘密鍵を作ることを防いでいます。

以上の作業を繰り返すさらに子孫秘密鍵・公開鍵ペアを作ることができます。インデックスは32ビットありますし,子孫はいくらでも継承していくことができるので,一つのシードから秘密鍵・公開鍵ペアを生成することができます。このように生成した秘密鍵・公開鍵を拡張秘密鍵・拡張公開鍵といいます。

親公開鍵から子公開鍵の生成

上記の方法では多くの秘密鍵・公開鍵ペアを生成することができますが, 必ず秘密鍵を経由しないといけないのがセキュリティー上心配です。 例えば、入金用に自動的に多くのアドレスを用意したい場合に、最上位の親の秘密鍵が必要になるので一緒に保管しておかなければなりません。本来は公開鍵のみあればビットコインの入金は可能なのですが、このメリットが生かされません。

実は公開鍵のみによって子公開鍵を再生することも可能です。次の図にその方法を示しています。

実はHMAC-SHA512の上位256ビットに楕円曲線上でG倍して,親公開鍵を加えることによって、子公開鍵を生成することが可能です。その原理を説明します。

親秘密鍵・親公開鍵から子公開鍵を生成する式は以下のようになります。

\[M/i/j = (H(M/i, c/i, i) + m/i)*G \]

この式は以下のように変形できます。(楕円曲線上の演算ですが分配法則は使用できます)

\[ M/i/j = H(M/i, c/i, i) * G + M/i \]

この式と図の対応はおわかりいただけるでしょう。一旦,子秘密鍵を経由する方法とは異なる方法ですが全く同一の公開鍵を生成することができます。

強化導出

実はこの方法は一つ大きなリスクがあります。親秘密鍵・親公開鍵から子秘密鍵を生成する式を示します。

\[ m/i/j = H(M/i, c/i, i) + m/i \]

この式を左辺がm/0になるように変形します。

\[ m/i = m/i/j - H(M/i, c/i, i) \]

親秘密鍵は子秘密鍵からH(M/i, c/i, i) を差し引くことで得られることを意味しています。Mはマスター公開鍵,cはチェインコードですので,両方とも公開情報です。したがって,子秘密鍵が漏洩すると,親秘密鍵が簡単に得られてしまうということです。

親秘密鍵が分かってしまうと,子秘密鍵の兄弟秘密鍵も計算できてしまいますので,それ以下の階層全体の公開鍵・秘密鍵の情報を盗むことができるといえます。

これを防ぐためには図のような方法をとります。親公開鍵ではなく親秘密鍵をつかって子秘密鍵を導出します。式で示すと以下のようになります。

\[ m/i/i’ = H(m/i, c/i, i) + m/i \]

この式はm/iがH(・)の中にも入っていますので,m/0を直接求める式をつくることはできません。したがって強化導出でもとめた子公開鍵が漏洩しても親公開鍵を知られてしまうリスクを避けることができます。

ただし,強化導出で求めた子公開鍵を親公開鍵のみによって得ることはできません。この点はトレードオフとなります。

各階層のインデックスの意味

BIP44では各階層のインデックスを次のように定めています。

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

purpose’ は階層決定性ウォレットの使用目的です。レガシーな実装では44’を使います。正規のNested Segwitでは49’,Native Segwitでは84’となります。拡張公開鍵の頭4文字がxpub/ypub/zpubが44’/49’/84’に対応しています。

coin_type’ は使用する暗号資産の種類です。BTCは0’,ETHは60’というように決まっています。https://github.com/satoshilabs/slips/blob/master/slip-0044.md にその他について記載されています。この仕組のおかげで一つのシードフレーズで複数種類の暗号資産を一度に復元することができます。

account' はアカウント番号です。好きなだけ(20億)作ることができます。一つのウォレットに複数のアカウントを作る場合に利用します。ここまでを強化導出することで,一つのアカウントで秘密鍵が盗まれても他のアカウントに波及することのないようにしています。これ以降の階層は入金用にしか使わないので,秘密鍵を生成する必要性がなく,公開鍵のみを非強化導出で生成していきます。

change は0なら受取用,1は送金の際のお釣り用です。(一見,贅沢な使い方ですが,全く問題ありません。)

address_index は一回の入出金ごとに使い捨てていくものです。20億の空間がありますが,1秒に一回取引をしても600年かかるくらいの数があります。(全世界の人から一人ずつ送金してもらうには足りませんね・・・)

ウォレットを使っていると出てくる拡張公開鍵というのはm / purpose' / coin_type' / account' の階層の公開鍵のことになります。coin_type’は間違わないと思いますが,purpose’ や account’ は記録しておきましょう。(pupose’も限られていますし,account’もそれほど多くないのでわからなくなってしまったら全探索してもよいかもしれませんが・・・)

階層決定性ウォレットのユースケース

BIP32では階層決定性ウォレットの使い方の例が掲載されています。BIP44にしたがって鍵生成をしたほうが間違いがないのですが,送受金には秘密鍵・公開鍵ペアさえあれば可能なので,同時の実装でインデックスを自由に使うことができます。

監査用 M/*

親公開鍵から子公開鍵が生成できるので,取引のすべてを追跡できます。秘密鍵を渡さなくても済むので不正送金をすることはできません。

支店ごとのアカウント m/i’

本社ではマスター秘密鍵mを管理し,各支店にこの拡張秘密鍵・拡張公開鍵を管理させるくことで,各支店の売上を管理することができます。強化導出されているので他の支店の情報を除くことはできません。

複数回使用する入金用(B2Bの取引・安全ではない場所での M/i’/0

たとえば取引先とお互いに拡張公開鍵を交換しておき,取引ごとに取引先にアドレスを生成してもらったり,オンライン上に拡張公開鍵をおき,入金用のアドレスを自動生成するということができます。

他にもいろいろな応用が考えられそうです。今更ながら,これぞFinTechという感じですね!

まとめ

階層決定性ウォレットに関する注意点をまとめておきます。

  • 自分が使っている階層決定性ウォレットの構造をよく理解しておく。
  • 拡張公開鍵は万が一漏洩しても資産が失われるようなことがないが,トランザクションやりとりが全て知られてしまうので,プライバシーを気にするのであれば公開しないように気をつける。(もちろnビットコインアドレスは使い捨てなので問題ないです。)

BIP32で紹介されていたユースケース以外にも,秘密鍵管理はハードウェアウォレットで行い,スマートフォンのウォレットには拡張公開鍵のみを入れておき送金できない監視用ウォレットとして使うような便利な使い方も可能です。