はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、秘密鍵を1つだけ保存することで、階層構造をもつ多数の鍵を安全に管理・共有できる「HDウォレット」の仕組みを標準化した提案しているBIP32についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIPについてまとめています。

概要

HDウォレット(Hierarchical Deterministic Wallet)は、ひとつのシード(種)から複数の鍵ペア(公開鍵と秘密鍵)を階層的に生成できる仕組みを持つウォレットです。 このウォレットは、シードさえあれば同じ鍵ペア群をどの環境でも再生成できる特徴があり、バックアップや共有の仕組みを簡素化できます。

BIP32は、異なるクライアント間でHDウォレットの互換性を持たせるための標準を定めることを目的としています。 HDウォレットが持つさまざまな機能が記述されていますが、これらすべてをサポートする必要はありません。

仕様は以下の2つの構成で成り立っています。

  • 1つのシードから鍵ペアのツリー(木構造)を導出する仕組み。
  • そのツリー構造をもとに、ウォレット全体の構造をどのように構築するか。

動機

ビットコインの初期の公式クライアントでは、鍵はランダムに生成されていました。 そのため、取引を行うたびに新しい鍵が必要になり、頻繁にバックアップを取る必要がありました。 これを避けるため、100個の予備鍵を事前に生成してキャッシュしておく方式が使われていましたが、それでも限界がありました。 特に複数のシステム間で同じウォレットを共有するような使い方には適していませんでした。

HDウォレットはこの課題を解決します。 楕円曲線暗号の性質を活用することで、秘密鍵を知らなくても対応する公開鍵を生成することが可能です。 これにより、例えばウェブショップの運営者が、ウェブサーバー側で新しい受取用アドレスを毎回生成するような運用ができます。 加えて、秘密鍵を公開する必要がないため、安全性も保たれます。

ただし、従来の決定性ウォレットでは、1本の鍵の連鎖(チェーン)しか生成できませんでした。 この仕組みだと、一部の鍵だけを別のシステムと共有することはできず、「全部か何もないか」という状態になってしまいます。

そのような状況を解決するためにHDウォレットは登場しました。 HDウォレットは、ルートとなる鍵から複数の鍵のチェーンを派生できるようになっています。 これにより、例えばウェブショップでは「受信用アドレス」だけをウェブサーバーで管理させることが可能になります。 一方で「残高返却用アドレス」など、送金に関連する鍵情報は非公開にしたまま安全に運用できます。

このように、HDウォレットは柔軟性と安全性の両立を図る仕組みとして、従来のウォレットの課題を克服しています。

仕様

HDウォレットの鍵導出仕様

用語

この仕様は、Bitcoinで使われる公開鍵暗号、すなわちsecp256k1という楕円曲線を前提としています。 登場する変数は、以下のようなものです。

  • 楕円曲線上の点の座標(公開鍵)
  • 楕円曲線の位数(秘密鍵の範囲)
  • バイト列

楕円曲線上での「加算」は、特殊な演算で、一般的な整数の加算とは異なり楕円曲線暗号の定義に従って計算されます。

また、仕様中には以下のような変換関数が登場します。

  • point(p)
    • 秘密鍵 p から対応する公開鍵(座標)を得る。
  • ser_32(i)
    • 32ビットの整数 i を4バイトにシリアライズする。
  • ser_256(p)
    • 256ビットの整数 p を32バイトに変換する。
  • ser_P(P)
    • 公開鍵 P を圧縮形式でバイト列に変換する(圧縮SEC1形式)。
  • parse_256(p)
    • 32バイトのバイト列 p を256ビットの整数として解釈する。

拡張鍵(Extended Keys)

HDウォレットでは、単なる鍵だけでなく「鍵+追加情報(チェーンコード)」の組として管理します。 これを拡張鍵と呼びます。

  • 拡張秘密鍵
    • (k, c)。k は秘密鍵、c は32バイトのチェーンコード。
  • 拡張公開鍵
    • (K, c)。K は k から得られた公開鍵、c は同じチェーンコード。

この構造により、ひとつの親鍵から2322^{32}個の子鍵を導出でき、そのうち半分(2312^{31})は通常の子鍵(non-hardened)、残り半分はハード化された子鍵(hardened)です。

子鍵導出の仕組み

親の秘密鍵から子の秘密鍵を導出

CKDpriv((k_parent, c_parent), i) により、インデックス i に対応する子の拡張秘密鍵を生成します。

  • i が 2312^{31} 以上なら「ハード化された鍵」、未満なら「通常の鍵」として処理。
  • ハード化の場合、秘密鍵そのものを含むデータで HMAC-SHA512 を計算。
  • 通常の場合は、対応する公開鍵を使って HMAC-SHA512 を計算。
  • 出力を左右に分割し、左部分で鍵を、右部分でチェーンコードを決定します。

得られた子鍵が無効(ゼロや範囲外)だった場合は、次のインデックスで再試行します。

親の公開鍵から子の公開鍵を導出

CKDpub((K_parent, c_parent), i) により、親公開鍵から子公開鍵を計算できます。 ただし、これは通常の子鍵に限られます(ハード化された鍵は不可)。

  • i が 2312^{31} 以上なら失敗とし、2312^{31} 未満なら計算可能。
  • HMAC-SHA512 により新しい点を算出し、親公開鍵に加算することで子公開鍵を得ます。
秘密鍵から公開鍵を得る(Neutered Key)

拡張秘密鍵 (k, c) を拡張公開鍵 (K, c) に変換する処理を N((k, c)) と定義します。 これは署名機能を除いた形で「中性化された鍵」として利用できます。

また、秘密鍵を経由せずに公開鍵からさらに派生鍵を得られるのは「通常の子鍵」のみです。 この特性を活用することで、秘密鍵を知らなくてもアドレスを安全に管理できる環境を構築できます。

公開鍵から秘密鍵を導出することはできない

楕円曲線暗号の前提として、公開鍵から秘密鍵を逆算することは現実的に不可能です。

鍵ツリーの構造

この仕組みにより、鍵はツリー構造で管理されます。 最上位の鍵を「マスター鍵」とし、CKDpriv(m, i) を繰り返すことで階層的に子鍵が生成されます。

この構造は以下のように表されます。

  • m/3H/2/5
    • マスター鍵から3番目のハード化鍵 → 2番目の通常鍵 → 5番目の通常鍵、の順に派生。

また、公開鍵に対しても M/3/2/5 のように記述できます。 ただし、ハード化された鍵は秘密鍵がなければ導出できないため、公開鍵だけではそれ以降の鍵は取得できません。

鍵の識別子

拡張公開鍵は、その公開鍵部分をSHA-256とRIPEMD-160でハッシュし、160ビットの識別子とすることができます。 この仕組みはBitcoinアドレスの形式と互換がありますが、誤って送金されるのを防ぐため、Base58形式で表示することは推奨されません。

先頭32ビットは「フィンガープリント」として、親子関係を識別するのに使用されます。

シリアライズ(直列化)フォーマット

拡張鍵は以下の形式で直列化され、Base58形式でエンコードされます。

  • バージョン(mainnet: xpub/xprv、testnet: tpub/tprv)
  • 階層の深さ(0=マスター鍵、1=レベル1子鍵)
  • 親鍵のフィンガープリント
  • 子番号(インデックス)
  • チェーンコード(32バイト)
  • 鍵データ(公開鍵なら圧縮形式、秘密鍵なら0x00プレフィックス+32バイト)

この合計78バイトにチェックサムを追加し、Base58で111文字の文字列に変換されます。 これにより、「xpub」や「xprv」で始まるHDウォレット用の鍵文字列が完成します。

マスター鍵の生成

derivation.png

HDウォレットのマスター鍵は直接作られるのではなく、「シード」から生成されます。

  1. 128〜512ビット(推奨は256ビット)のランダムなシードを生成。
  2. "Bitcoin seed" をキーとして、シードを HMAC-SHA512 にかけて 64バイトの出力を得る。
  3. 前半32バイトを秘密鍵、後半32バイトをチェーンコードとして使用。
  4. 得られた秘密鍵が無効であればやり直し。

このようにして、安全かつ再現可能なルート鍵が構築されます。

HDウォレットの構造仕様

ウォレット構造の導入

これまでに説明したように、HDウォレットでは階層構造をもつ鍵ツリーを1つのシードから導出できます。 その鍵ツリーの上に「ウォレット」としてどのような構造を定義するか見ていきます。

ここで定義されるウォレット構造はあくまでデフォルトレイアウトであり、全てのクライアントが完全にこれに従う必要はありません。 ただし、異なるウォレットソフト間の互換性を確保するため、この構造に倣うことが推奨されています。

デフォルトのウォレットレイアウト

HDウォレットは「アカウント」という単位で構成されます。アカウントには番号が振られており、番号0がデフォルトアカウントです。ほとんどのウォレットクライアントでは、複数アカウントに対応していないため、番号0のデフォルトアカウントのみを使用します。

各アカウントには2本の鍵チェーンが存在します。

  • 外部チェーン(external chain)
    • 新しい公開アドレスを生成するための鍵列です。
    • 送金を受け取る際のアドレスがこのチェーンから生成されます。
  • 内部チェーン(internal chain)
    • おもにウォレット内部で使用される鍵列です。
    • 具体的には、送金時の「残高返却用アドレス」やマイニング報酬の受け取りなど、対外的に公開しない用途に使われます。

鍵のパス構造としては、以下のように表されます(m はマスター鍵、i はアカウント番号、k はインデックス番号):

  • m/iH/0/k
    • アカウント i の外部チェーンの k 番目の鍵
  • m/iH/1/k
    • アカウント i の内部チェーンの k 番目の鍵

iH はハード化されたインデックスを意味します。 もしウォレットクライアントが内部チェーンに対応していない場合、すべての鍵生成を外部チェーンで行っても問題ありません。

具体的なユースケース

ウォレット全体の共有(m)

同じウォレットを複数のシステムで共有し、どちらからも送金を行いたい場合は、マスター秘密鍵(拡張形式)を共有する必要があります。

この場合、ウォレットクライアントは事前に「先読み」して一定数の公開鍵をキャッシュしておくことで、入金の監視を効率化できます。 外部チェーンはトランザクションによりギャップ(空白のアドレス)が生じる可能性があるため広めに先読みしますが、内部チェーンはその必要が少なくなります。

また、新しいアカウントが使われることをトリガーに、さらに先読みを行うことで、動的にアカウントを追加できます。 ただし、アカウントの「名前」はブロックチェーン上で同期されないため、手動で設定する必要があります。

監査用途(N(m/*))

会計監査などで、すべての取引履歴を第三者に公開する必要がある場合には、すべてのアカウントの公開拡張鍵(Extended Public Keys)を共有することで対応可能です。 これにより、監査人はすべての入出金アドレスを追跡できますが、秘密鍵には一切アクセスできません。

拠点ごとの管理(m/iH)

企業が複数の支店や部署を持つ場合、それぞれに別のアカウントを割り当てることで、1つのマスター鍵から全体を一元管理できます。 本社は各アカウントの入出金をすべて把握でき、部署間での資金移動も可能になります。

定期的なB2B取引(N(m/iH/0))

企業間で頻繁に送金を行う場合、特定アカウントの外部チェーンの公開拡張鍵(例:N(m/1H/0))を共有することで、何度も新しいアドレスを生成できます。

これにより、毎回新しいアドレスを要求する手間なく、匿名性をある程度保ちながら取引が行えます。 マイニングプールの報酬支払いなどにも応用可能です。

セキュリティの低い受信システム(N(m/iH/0))

セキュリティの甘いウェブサーバーでECサイトを運営する場合、支払い受取アドレスの生成だけをサーバー側で行いたいケースがあります。

このとき、アカウントの外部チェーンの公開拡張鍵だけをウェブサーバーに渡すことで、アドレスの生成と入金の監視は可能になりますが、秘密鍵が漏れることはありません。 送金は不可能であり、他のアカウントや内部チェーンの情報も見られないため、セキュリティを確保できます。

互換性

このHDウォレットの標準に準拠するためには、最低限として「拡張公開鍵」または「拡張秘密鍵」をインポートできる機能がクライアントに必要です。 これにより、その鍵から導出される直接の子鍵にアクセスできるようになります。

BIP32で定義されている「マスター → アカウント → チェーン → サブチェーン」というウォレット構造はあくまで推奨構成であり、必須ではありません。 例えば、アカウントの分離や内部チェーンと外部チェーンの区別に対応していないクライアントであっても、最低限の互換性を維持できます。

ただし、異なるクライアント間でのスムーズな連携を実現するためには、この構造を参考にすることが望ましいとされています。 一方で、特定のアプリケーションではより複雑な構造を必要とすることもあり、そのような場合には自由に設計を拡張しても問題はありません。

セキュリティ

BIP32は、楕円曲線暗号(EC)のセキュリティ仮定に基づいて構築されています。 基本的な安全性としては、公開鍵から対応する秘密鍵を導出することはEC離散対数問題を解く必要があり、これは非常に困難(理論上は2の128乗回の演算)とされています。

この標準が前提とする追加的なセキュリティ性は以下のようなものです。

  • 子の拡張秘密鍵とそのインデックス番号が与えられても、それを導出した親の秘密鍵を逆算することは、HMAC-SHA512を2の256乗回試すのと同程度の計算量が必要です。
  • 複数の子鍵(2個以上)の拡張秘密鍵とインデックスの組が与えられても、それらが共通の親鍵から導出されたかどうかを判断するには、同様に2の256乗回のHMAC-SHA512を要します。

一方で、この仕組みには持たない性質(できないこと)明示されています。

  • 親の拡張公開鍵と子の公開鍵だけから、どのインデックスで生成されたか(i値)を特定するのは困難です。
  • 親の拡張公開鍵と非ハード化された子の秘密鍵が与えられても、親の秘密鍵を導出することは困難です。

注意点

通常の秘密鍵や公開鍵と同様に、それらが漏洩すれば資金が盗まれるリスクがあります。 ただし、HDウォレットでは拡張鍵(extended key)という形で、1つの鍵が大量の子鍵をまとめて表現するため、より一層の注意が必要です。

とくに注意すべき点は、以下のような「一見気づきにくい脆弱性」です。

親の拡張公開鍵と、それに対応する非ハード化された子の秘密鍵の両方が漏洩した場合、それは実質的に親の秘密鍵が漏れたのと同じ意味を持ちます。 つまり、親にぶら下がるすべての子鍵(=ウォレットの一部または全部)が攻撃者に知られることになります。

このリスクを回避するために、仕様ではアカウント階層レベルではハード化された鍵(hardened key)を使うことが推奨されています。 これにより、あるアカウント内部の鍵が漏洩しても、他のアカウントやマスター鍵には影響が及ばず、被害を局所化することができます。

引用

This BIP is licensed under the 2-clause BSD license.

最後に

今回は「秘密鍵を1つだけ保存することで、階層構造をもつ多数の鍵を安全に管理・共有できる「HDウォレット」の仕組みを標準化した提案しているBIP32」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!