BIP-47の記事無いですよね?と突っ込まれたので書いてみた。
(結構前に書かれたBIPでメジャーでないものとか導入されなかったコンセンサスのBIPについては書けてない。今BIPとして登録されてるのが115個くらいで、ブログに投稿したのは、そのうち50個くらい)
Bitcoinではブロックチェーン上に個人を特定する情報が記録されることは無いが、外的要因によりアドレスと個人の関連が分かると、個人がどのような決済をしているのかが分かるようになりプライバシー上好ましくない。このため決済毎に異なるアドレスの使用が推奨されている。ただこの場合プライバシーという点では好ましいが、例えば対外的に自分のアドレスを公開したい場合には不便だ。こういうケースにおいて、代表アドレスは公開するがそのアドレス宛の送金を送金者本人は分かるが第三者はそこにいくらの送金がされたのか分からなくするステルスアドレスという仕組みが2014年くらいに提案された。
BIP-47のペイメントコードはこのステルスアドレスに似たコンセプトのBIPで、2015年に公開された。
https://github.com/bitcoin/bips/blob/master/bip-0047.mediawiki
概要
このBIPは、P2PKHアドレスの再利用において固有のセキュリティまたプライバシーの損失を発生させることなく、現実世界のアイデンティティと関連付けられ公に公開されるペイメントコードを作成するための方法を定義している。このBIPはBIP-43の応用で、BIP-44を実装しているHDウォレットを補完することを目的としている。
動機
ペイメントコードとは売り手と顧客の対話において有用で、顧客のプライバシーを保護しながらトランザクションに識別情報を追加する。ペイメントコードは、信頼できるフルノードなしにダークウォレットスタイルのステルスアドレスのプライバシー上の利点をSPVクライアントに提供し、ブロックチェーンストレージへの依存度を大幅に削減する。
パス階層
BIP-32のパスについて以下の3つの階層を定義する。
m / purpose' / coin_type' / identity'
identityから導出した子鍵はいろんな方法で使われる。
m / purpose' / coin_type' / identity' / 0
(非強化鍵の)0番めの子鍵は通知鍵になる。
m / purpose' / coin_type' / identity' / 0 から 2147483647
これらの(非強化鍵)鍵ペアはECDHのためのデポジットアドレスを生成するのに使われる。
m / purpose' / coin_type' / identity' / 0' through 2147483647'
これらの(強化鍵)鍵ペアは一時的なペイメントコード。
パスのアポストロフィー(')はBIP-32の強化導出が使われていることを示している。
各階層には以下で説明する特別な意味がある。
purpose
purposeはBIP-43のルールに従い、47 '(0x8000002F)が設定される。これはこのノードのサブツリーがこの仕様に従って使用されることを示す。
coin_type
coin_typeフィールドはBIP-44と同じフィールド。
この階層では強化導出が使われる。
identity
identityの導出階層では、拡張公開鍵およびそれに関連する拡張秘密鍵を生成する。
この階層の拡張公開鍵が以下の表現セクションで指定されたメタデータと組み合わされた結果がペイメントコードになる。
この導出階層はBIP-44のアカウントと同じ階層だ。ウォレットはペイメントコードを同じインデックスのBIP-44のアカウントの一部として扱い、ペイメントコードとアカウントをペアで作成すべきである。
例えば(m / 47' / 0' / 0')で表されるペイメントコードは、(m / 44' / 0' / 0')で表されるアカウントの一部になる。
ウォレット内の2つめのアカウントは、index 1を使って作成される新しいアカウント/ペイメントコードのペアで構成される。
この仕様を介して受け取った入金は、BIP-44で受信した支払いと同等で、両方のタイプのアドレスの未使用アウトプットを同じ送金トランザクションのインプットとして使用できる。
この階層では強化導出が使われる。
特に明記されていない限り、ペイメントコードから導出された全ての鍵は、公開導出メソッドを使用する。
バージョン
ペイメントコードには特定の動作セットを識別するバージョンバイトが含まれている。
他に指定されていない限り、異なるバージョンのペイメントコードは相互運用可能だ。アリスがバージョンxのペイメントコードを使い、ボブがバージョンyのペイメントコードを使っている場合でも、お互いの間でトランザクションを送受信できる。
現在、以下のバージョンが定義されている。
- バージョン1
- アドレスタイプ:P2PKH
- 通知タイプ:アドレス
- バージョン2
- アドレスタイプ:P2PKH
- 通知タイプ:bloom-multisig
推奨されるバージョン
- ブルームフィルタリング機能を備えたウォレットは、バージョン1のペイメントコードの代わりにパージョン2のペイメントコードを使用すべき。
- バージョン1のペイメントコードはブルームフィルタリング機能にアクセスできないウォレットにのみ推奨される。
バージョン1
表現
バイナリシリアライゼーション
ペイメントコードには以下の要素が含まれる。
- Byte 0: バージョン。必須値:0x01
- Byte 1:feature bitフィールド。この仕様の他の箇所で指定されていない限り全てのbitは0。
- Bit 0: Bitmessageの通知
- Bit 1-7: 予約値
- Byte 2: 符号。必須値で0x02 or 0x03
- Byte 3-34: x値。secp256k1グループのメンバーであること。
- Byte 35-66: chain code
- Byte 67-79: 将来の拡張用の予約値。特に明記されていない限り0埋め。
Base58シリアライゼーション
ペイメントコードがユーザーに提示される時は、Base58Check形式でエンコードされて提示される必要がある。
プロトコル
以下の例では、アリスとボブは対応ペイメントコードを持つアイデンティティで、アリスがBitcoinトランザクションの送信者でボブが受信者である。
アリスはペイメントコードのプロトコルの範囲外で、適切な方法でボブのペイメントコードを容易に入手できるものと仮定する。
定義
- ペイメントコード: 特定のidentity/accountに関連付けられた拡張公開鍵およびメタデータ
- 通知アドレス: ペイメントコードから導出された0番目の公開鍵に関連付けられたP2PKHアドレス
- 通知トランザクション: 埋め込まれたペイメントコードを含む通知アドレスに送金するトランザクション
- 指定インプット: 通知トランザクションの最初のインプットで、そのインプットが参照するアウトプットのpubkey scriptもしくはインプット内のredeem script、signature scriptのいずれかにあるsecp256k1の公開鍵を公開する。
- 指定公開鍵: 指定インプットのスクリプトの評価中にスタックにプッシュされる最初のsecp256k1公開鍵
- Outpoint: 消費される前のトランザクションの特定のアウトプット。バイナリのシリアライゼーションについてはReferenceセクション参照。
通知トランザクション
アリスが最初にボブと取引を開始する前に、アリスは以下の手順でボブにペイメントコードを通知しなければならない。
※ この手順は(アリスのペイメントコードのバージョンに関係なく)ボブがバージョン1のペイメントコードを使用する場合に使われる。ボブのペイメントコードがバージョン1でない場合は、別の適切なセクションを参照。
- アリスは少量のコインをボブの通知アドレスに送金するトランザクション(通知トランザクション)を構築する。
- アリスはECDHを使ってユニークな共有シークレットを作成する。
- アリスは指定公開鍵に対応する秘密鍵を選択する。
a
- アリスはボブの通知アドレスに関連付けられた公開鍵を選択する。
B, ここでBはB = bG
- アリスはシークレットポイントを計算する。
S = aB
- アリスは64バイトのblinding factorを計算する。
s = HMAC-SHA512(x, o)
- xはシークレットポイントSのx座標
- oは指定インプットが指すOutpoint
- アリスは指定公開鍵に対応する秘密鍵を選択する。
- アリスは自身のペイメントコードをバイナリ形式でシリアライズする。
- アリスは自身のペイメントコード(P)をボブ以外誰も読めないようにする。
- xをx'に置き換える
x' = x XOR (sの先頭32バイト)
- chain codeをc'に置き換える
c' = c XOR (sの最後32バイト)
- xをx'に置き換える
- アリスはPを構成するOP_RETURNアウトプットをトランザクションに追加する。
ここからボブのターン。
- ボブは通知アドレス宛の送金トランザクションを監視する。
- トランザクションを受信すると、クライアントは80バイトのペイロード(通知トランザクション)を持つ標準のOP_RETURNが含まれるかチェックする。
- 通知トランザクションのペイロードの最初のバイトが0x01の場合
- ボブは指定公開鍵を選択する。
A、ここでAは A = aG
- ボブは通知アドレスに関連する秘密鍵を選択する。
b
- ボブはシークレットポイントを計算する。
S = bA
- ボブはblinding factorwを計算する。
s = HMAC-SHA512(x, o)
- xはシークレットポイントSのx座標
- oは指定インプットが指すOutpoint
- ボブは80バイトのペイロードをペイメントコードとして解釈する。以下の除いて
- xをx'に置き換える
x' = x XOR (sの先頭32バイト)
- chain codeをc'に置き換える
c' = c XOR (sの最後32バイト)
- xをx'に置き換える
- 更新されたx値がsecp256k1グループのメンバーである場合、ペイメントコードは有効。
- 更新されたx値がsecp256k1グループのメンバーでない場合、ペイメントコードは無視される。
- ボブは指定公開鍵を選択する。
ボブのクライアントがアリスのペイメントコードを受け取ったので、アリスはボブに支払いができる(最大232回)。
アリスはボブに再度通知トランザクションを送る必要はない。
通知トランザクションを介して受信したビットコインは、プライバシー漏洩を避けるため特別な処理が必要になる。
- 通知アドレス宛に受信されたアウトプットの値は、消費可能な残高の一部としてユーザーに表示されてはならない。
- 通知アドレス宛に受信されたアウトプットはユーザーのペイメントコードのいずれかを使ってECDH計算をするトランザクションのインプットとして使ってはならない。
- 通知アドレス宛に受信されたアウトプットは、ユーザーの消費可能残高に追加される前に、ミキシングサービスを通貨することもできる。
- 通知アドレス宛に受信されたアウトプットは、dust-b-goneもしくは同等の手順でマイナーに寄付することができる。
通知トランザクションの標準スクリプト
アリスは公開鍵を公開するのに以下のいずれかの形式のインプットスクリプトを使用する必要があり、準拠するアプリケーションはこれらの全ての形式を認識すべきだ。
互換性のあるウォレットは、非標準の通知トランザクションを介して送信されたペイメントコードをリカバリするために、通知トランザクションに関連付けられた公開鍵をユーザーが手動で設定する方法を提供してもいい。
通知後のプライバシーに関する考慮事項
通知トランザクションのお釣りアウトプットの軽率な処理は、意図しないプライバシーの損失を引き起こす可能性がある。
前の通知トランザクションのお釣りのアウトプットを消費するトランザクションの受信者は、通知トランザクションの送信者と受信者間の潜在的な接続について学習する。
このリスクを軽減するために、以下の処理を推奨する。
- ミキシングをサポートするウォレットは、そのお釣りを使用する前にミキシングすべきだ。
- ミキシングをサポートしていないウォレットでは、お釣りのアウトプットを次のBIP-44の外部アドレスに送信するトランザクションを作成することでミキシングをシミュレートできる。
送金
- アリスはボブへの送金の度に、以下のようにECDHを使って一意のP2PKHアドレスを導出する。
- アリスは自分のペイメントコードから導出した0番目の秘密鍵を選択する。
a
- アリスはボブのペイメントコードから導出した(0から順に導出)次の未使用の公開鍵を選択する。
B、ここでBは B = bG
- 次の未使用の公開鍵は、アリスまたはボブのいずれにもグローバルなものでなく、アリス−ボブ間のコンテキスト固有のもの。
- アリスはシークレットポイントを計算する。
S = aB
- アリスはSのx座標を使ってスカラー共有シークレットを計算する。
s = SHA256(Sx)
- sの値がsecp256k1グループに無い場合、アリスはボブの公開鍵を取得するために使用されたインデックスをインクリメントして再試行する必要がある。
- アリスはスカラー共有シークレットを使って、このトランザクションのP2PKHアドレスを生成するために使われる一時的な公開鍵を計算する。
B' = B + sG
- アリスは自分のペイメントコードから導出した0番目の秘密鍵を選択する。
ここからボブのターン
- ボブはアリスから通知トランザクションを受け取って以降、B'への入金を監視している。
払い戻し
ボブは決済の過程でアリスのペイメントコードを知っているので、ボブはアリスに払い戻しを行うのに必要な情報をすべて持っている。
払い戻しのトランザクションは支払いのトランザクションと同じだが、参加者の役割だけが切り替わる。
たとえボブが過去にアリスから通知トランザクションを受け取っていたとしても、ボブはアリスに資金を送る前にアリスに通知トランザクションを送らなければならない。
匿名支払い
アリスが自分のアイデンティティと関連付けられることになるボブへの支払いを行いたくない場合は、アリスは取引に使用する一時的なペイメントコードを作成することができる。
- 一時的なペイメントコードは、インデックス0から始まるペイメントコードの強化された子鍵である。
- 一時的なペイメントコードは、単一の支払いに対してのみ使われるべきである。
- 一時的なペイメントコードの通知アドレスは、払い戻しを検知するため監視されなければならない。
- BIP-44のアカウントと一時的なペイメントコードの対応は1対多になる。
コールドストレージ
- 従来の監視専用のウォレットと違い、ペイメントコードと関連付けられたコールドストレージに保管されているウォレットは、入金をすぐに検知することはできない。
- 監視専用のウォレットは通知トランザクションを検知すると、そのトランザクションをオフラインデバイスへの転送に適した実装固有の形式にパッケージする。
- オフラインデバイスはペイメントコードをリカバリし、エアギャップのラウンドトリップの必要性を最小限に抑えるため、多数の関連するキーペア(例えば1000個)を事前に生成する。
- オフラインデバイスは関連する公開鍵をオンラインデバイスへの転送に適した実装固有のフォーマットでパッケージする。
- オンラインデバイスは適切なlookahead幅で入金を監視することができる。
- lookahead幅が予め生成された公開鍵の終わりに達すると、ユーザーはオフラインデバイス上でさらに多くの鍵を生成し、それらをオンラインデバイスに転送する必要がある。
ウォレットのリカバリ
ペイメントコード対応のウォレットの通常操作はSPVクライアントで実行でき、ブロックチェーンの完全なコピーへのアクセスは必要ない。
ただしシードからウォレットをリカバリするには、フルインデックスのブロックチェーンにアクセスする必要がある。
必要なデータは、ユーザーの制御下にあるブロックチェーンのコピーか、または公開されているブロックチェーンエクスプローラを介して取得できる。
公開されているブロックチェーンエクスプローラを使用する場合、ウォレットはTorを介してエクスプローラに接続し、一時的なアドレスを互いに関連付けるようなクエリをグルーピングするのを避けること。
以前の使用可能な資金は一般的にシードからリカバリした後、アクセス不能になることはないが、以前の出金に関する情報は全て失われる。
シードから受け取った資金をリカバリするには、通知アドレス宛に送られたトランザクションを(使用済みのもの含めて)全て入手する必要がある。ユーザーが指定した数分未使用の通知アドレスが連続して登場するまで、各導出アドレスを順次走査することで、各サブチェーンのlookahead幅を再確立する。
ウォレットが送信するトランザクションを適切に処理するため格納する必要があるメタデータは以下のように構成される。
- identityが通知トランザクションを送信したすべてのペイメントコードのリスト
- 前のリストの各ペイメントコードの次の未使用公開鍵に対応するインデックス値
- 未使用の一時的なペイメントコードのインデックス値
- 一時的なペイメントコードに関連するすべての入金を100%確実にリカバリするためには、潜在的な一時的なペイメントコードのアドレス空間232を使い果たす必要がある。
- ほとんどの場合、フォールバックディープスキャンがオプションで利用可能である限り100%未満の確実性が許容される。
- ウォレットは関連する資金をリカバリするために、通知トランザクションの各一時ペイメントコードの通知アドレスをチェックする。
- ほとんどの一時ペイメントコードは払い戻しトランザクションを受け取らないので、ウォレットはこのリカバリのために大きなlookahead幅を使うべきだ。
- リカバリされた値は、通知トランザクションで受信した任意の一時ペイメントコードよりも高い値が選択されてなければならない。
- 一時的なペイメントコードに関連するすべての入金を100%確実にリカバリするためには、潜在的な一時的なペイメントコードのアドレス空間232を使い果たす必要がある。
ウォレットの共有
ペイメントコードを使用するウォレットは、通常各インスタンス間でメタデータを同期する必要があるため、複数のデバイス間で共有するべきではない。
ウォレットが同期メカニズムなしでデバイス間で共有される場合、望ましくないアドレスの再利用が発生する可能性がある。
ウォレットはトランザクションを送信する前に一時的なデポジットアドレスのトランザクションが既にないか、ブロックチェーンのローカルコピーをチェックするか、Torまたは同等の機能を介してパブリックブロックチェーンエクスプローラに問い合わせることでチェックすることがある。
別の通知方法
受信者がシードからウォレットをリカバリしないといけない場合に、資金が失われないことを保証するために、送信者は最初に特定の受信者とやりとりをする時に通知トランザクションを送信しなければならない。
受信者は通知トランザクション以外に送信者が使用できる代替の方法を指定することもできる。
受信者が別の通知方法を指定する場合、準拠している実装では、通知アドレスを継続的に監視することを控えてもいい。ただ代替方法を使用できないユーザーからの支払いを検出する必要があるため通知アドレスは定期的にチェックする必要がある。
受信者はfeature byte内のビットを適切にセットすることで、代替通知の優先度を指定する。
Bitmessageの通知
Bitmessage経由で通知を受け取ることを希望する受信者は、以下の設定をする。
- feature byteのビット0を1に設定する。
- シリアライズされたペイメントコードの67バイトめを希望するBitmessageのアドレスバージョンに設定する。
- シリアライズされたペイメントコードの68バイトめを希望するBitmessageのストリーム番号に設定する。
送信者はこれらの情報を使って有効な通知Bitmessageアドレスを構築する。
- Bitmessageの署名鍵を以下のように導出する。
B = payment code / 0 / 0
- カウンターを1に初期化する
n
- 候補暗号鍵を導出する。
B' = payment code / 0 / n
- BとB'を組み合わせて有効なBitmessageアドレスを形成出来ない場合は、nをインクリメントして再度試みる。
- Bitmessageプロトコル毎にBitmessageアドレスを構成するために、アドレスバージョン、署名鍵、暗号鍵、ストリーム番号を使用する。
送信者はbase58形式の自分のペイメントコードをBitmessageアドレスに送信する。
Bitmessage通知を使用するには、送信者が導出したBitmessageアドレスを見てるBitmessageクライアントを受信者が持ち、受信したペイメントコードをBitcoinウォレットにリレーできる必要がある。
バージョン2
バージョン2のペイメントコードは下記の変更を除いてバージョン1と同じように動作する。
表現
バイナリシリアライゼーション
- Byte 0: バージョン。必須値:0x02
プロトコル
定義
- 通知のお釣り用アウトプット:通知トランザクションのお釣り用のアウトプットは送信者のウォレットにあるが、意図された受信者によって自動的に配置される。
- ペイメントコード識別子:バイナリシリアライゼーションしたペイメントコードのSHA-256ハッシュに0x02を付与して構成されたペイメントコードの33バイト表現
通知トランザクション
※ この手順は、(アリスのペイメントコードに関わらず)ボブがバージョン2のペイメントコードを使っている場合に使用される。ボブのペイメントコードがバージョン2でない場合はこの仕様の別のセクションを参照。
- ボブの通知アドレスを作成しないことを除いて、バージョン1と同様に通知トランザクションを構築する。
- 以下のように通知のお釣り用アドレスを作成する。
- 次のお釣り用アドレスに対応する公開鍵を入手する。
- 以下の形式のマルチシグアウトプットを作成する。
OP_1 <ボブのペイメントコード識別子> <お釣り用アドレスの公開鍵> OP_2 OP_CHECKMULTISIG
上記スクリプトにおけるペイメントコード識別子とお釣り用アドレスの公開鍵の相対的な順序はランダム化されてもいい。
ボブはブルームフィルタに自身のペイメントコード識別子を追加することで、通知トランザクションを検知する。
アリスのウォレットは次の適切な機会に通知トランザクションのお釣り用アウトプットを消費する必要がある。
Test Vector
参照
- BIP32 - HDウォレット
- BIP43 - 決定性ウォレットのpurposeフィールド
- BIP44 - 決定性ウォレットのマルチアカウント階層
- Outpoint
- dust-b-gone
- Base58Check encoding
- Bitmessage
- MLの議論
まとめ&所感
BIP-47は相手のペイメントコードと自分のペイメントコードを使ってECDHで各決済用のアドレスを導出するプロトコルと、ペイメントコードの交換プロトコルを定義したものになってる。
BIP-47ではBIP-32のHDウォレットでまずペイメントコードを作成するための拡張公開鍵を導出する。ペイメントコードとはこの拡張公開鍵にメタデータ(↑のバイナリシリアライゼーション参照)を組み合わせたコードのこと。この拡張公開鍵の直下の階層の0番目の導出鍵を使って生成したP2PKHアドレスが通知アドレスで相手とBIP-47を利用した決済をする際に最初の1回だけこの通知アドレス宛に通知トランザクションを送信する際に使われる。
BIP-47準拠のウォレットを持つユーザー間は、相手が公開している通知アドレス宛に通知トランザクションを送信する(送信者はすでに何らかの外的要因により相手のペイメントコードを知っている状態から始まる)。この通知トランザクションで、送信者自分のペイメントコードをOP_RETURNにセットして送るが、このとき自分のペイメントコードはblinding factorと、自分の秘密鍵と相手の公開鍵を利用してECDHで導出した共有シークレット用いて受信者にしか分からないようになっている。
通知トランザクションで一度、送信者と受信者の間でペイメントコードが交換されたら、後はそれを使って各決済毎にアドレスを導出することができる。なので通知トランザクションを送るのは最初の1回だけ。各決済の際は、相手のペイメントコードから導出した両者の間で未使用の公開鍵を導出し、自分のペイメントコードから新しい秘密鍵を導出してECDHで共有シークレットを導出する。共有シークレットから生成した楕円曲線上の点を未使用の公開鍵と加算してできた点を公開鍵として、その公開鍵から導出したP2PKHアドレス宛に送金をする。
所感
- 決済毎にアドレスを変更する前提で、同じ相手と複数回決済するケースであれば、一度相手とペイメントコードを交換してしまえば後は決済の度に相手からアドレスを提供してもらう必要がないので、そこのインタラクションは必要なくなって決済毎のアドレスが簡単に導出できる。逆に一度だけの決済であれば通知トランザクションを送信する分手数料が無駄になるので意味は無さそう。
- 通常のHDウォレットはシードからウォレットをリカバリするけど、BIP-47で同様のリカバリをする場合SPVノードであってもフルノードのチェーンへのアクセスして通知トランザクションの走査が必須になる。リカバリ処理が少し複雑なのでウォレットの実装は面倒くさそう。
- ステルスアドレスの仕様は結局BIPにはならなかったけど、BIP-47はBIPとしてSamourai WalletやBilioon appとかStashとか対応するウォレットもちらほらある。
あと、以下のような懸念の声もあるみたいね。
[bitcoin-dev] [Bitcoin-development] Reusable payment codes
通知アドレスの再利用により少量コインが乗ったUTXOが大量に集まり放置されることで、UTXOセットの膨張になる可能性があるため、通知アドレスを再利用するのではなくOP_RETURNに相手のペイメントコードのハッシュと自分のペイメントコードを暗号化したデータを入れた方が良いという内容。受信者は前者によりブルームフィルタによりトランザクションが検知可能。この場合OP_RETURNなのでUTXOセットが膨張することも無いと。