Develop with pleasure!

福岡でCloudとかBlockchainとか。

Bitcoinベースの新しいデジタルアセットプロトコル「RGB」

Lightining Networkとの連携も含めた、「RGB」というBitcoinベースの新しいデジタルアセットプロトコルが発表された↓

github.com

RGBのプロトコル仕様は、現状以下の5つのモジュールに分かれている。

  1. RGB
    プロトコルのコアおよび低レベルのアセットロジックを定義。
  2. Kaleidoscope
    他のモジュールを包括しプロトコルとして相互作用するための高レベルの機能を定義。
  3. Bifröst
    クライアントサイド検証の枠組みのプルーフの保存と公開について定義。
  4. Lighting Network Integration
    RGBをLighting Networkに適用する(アセットに対応したルーティング、チャネルの更新、クローズなど)ために必要なBOLTの拡張などを定義。
  5. Proofmarshal
    アセットに特化した形で、スケーラビリティとプライバシーを向上させる、Lightning Networkを補完・代替するレイヤー2ソリューションの定義。

まだ完成という訳ではないっぽく、現時点で使用が少しでも記載されてるのはRGBと、Bifröst、Lighting Network Integration。いずれもまだ作成途中っぽい。Lighting Networkで任意のアセットをオフチェーンで取引するための拡張仕様が定義されてたり、LNとは別のスケーラビリティソリューションの提案とか特徴的。

まずは、RGBモジュールみながらコア機能についてまとめた。

アセットの検証

一般的に、カラードコインはOP_RETURNを利用してBitcoinのトランザクションレイヤーを拡張しているが、Bitconのプロトコル上、OP_RETURNにあるデータは何の意味ももたず、その上に構成されるアセットの取引もコンセンサスレベルで関与することはない。そのため、こういった拡張プロトコルでは、そのプロトコルに対応したウォレットやノードなどのクライアントが、アセットの振る舞いの検証をするClient Side Validationモデルを採用する。RGBも他のカラードコインのプロトコルと同様このClient Side Validationを採用している。

コントラクト

アセットがどのようなもので、アセットの発行方法、転送のルールなどを定義したものをコントラクトと呼んでいる。このコントラクトのデータは以下の2つのデータで構成される。

  • header
    コントラクトの全種類共通のヘッダ項目
  • body
    コントラクトの種類毎に定義される項目

headerのデータ項目は以下の通り。

フィールド 内容
title アセットコントラクトのタイトル
description アセットコントラクトの定義
contract_url コントラクトとlight-anchorの発行に関する一意のURL
issuance_utxo このコントラクトをデプロイするためのコミットメントを持つトランザクションで使用されるUTXO
network 使用中のネットワーク(mainnet, testnet)
total_supply 総供給量(単位はsatoshi)
min_amount dust limitのように転送する際のトークンの最小量
max_hops 再発行前の最大ホップ数(この機能を無効にする場合は0xFFFFFFFFを設定する)
reissuance_enabled 再発行機能を有効にするかどうか
reissuance_utxo (オプション)トークンを再発行する際に使用するUTXO
burn_address トークンを償却する際の送信先アドレス
commitment_scheme このコントラクトで使用するコミットメント方式
version 使用されたブループリントのバージョンを表す16 bitの数値

Open Assets ProtocolなんかだとAsset Definition Fileに近いけどあれはメタデータ的な位置付けで、↑ではアセットの種類による振る舞いやルールも定義するようになっている。

アセット識別子:asste_id

各アセットはasset_idで識別される。このasset_idコントラクトのいくつかのデータ項目のハッシュから計算されるようだが、現時点では算出方法については書かれてない。

Proof-of-burn

アセットを焼却する際は、コントラクトのheader内のburn_addressで指定されたアドレスにアセットを送る。

ブループリント

コントラクトには種類があって、この種類によってアセットがどのように管理されるかであったりその振る舞いが決まる。これをブループリントと呼んでいる。

現状定義されているブループリントは以下の3つ。

Simple issuance: 0x01, version: 0x0008

シンプルなアセット発行のコントラクトで、headerのtotal_supplyで指定された量のアセットを発行し、owner_utxo宛に送る。

このタイプのコントラクトのbodyのデータ項目は、以下の通り。

フィールド 内容
owner_utxo 全てのトークンを受け取るUTXO

この場合コントラクトは以下のようになる。

{
  "kind": 0x01 // 作成するコントラクトの種類で、これは一般的な発行
  "version": 0x0008 // このコントラクトの種類で使われるバージョン
  "title": <String>, // アセットコントラクトのタイトル
  "description": <String>, // 償還可能なアクションと非スクリプトによる条件の定義
  "issuance_utxo": <String>, // このコントラクトへのコミットメントに使われるUTXO
  "contract_url": <String>, // コントラクトおよびlight-anchorを公開するための一意のURL
  "total_supply": <Integer>, // 総供給量(satoshi)
  "max_hops": <Integer>, // 再発行前にアセットに対して実行可能なオンチェーン転送の最大量
  "min_amount": <Integer>, //一緒に転送可能なカラーリングされたsatoshiの最小量
  "network": "BITCOIN", // 使用するネットワーク
  "reissuance_enabled": 0, // 再発行の無効化
  "burn_address": <Address>, // トークンを焼却するのに使用するアドレス
  "commitment_scheme": "OP_RETURN", // このアセットで使用するコミットメント方式
  "owner_utxo": <String>, // 全ての発行トークンを受信するUTXOで、コントラクト指定のフィールド。
}
Crowdsale: 0x02, version: 0x0008

クラウドセール用のブループリント。total_supplyまで指定した価格でトークンを販売する。コントラクトには異なるasset_idをもつ2つのアセットが作られる。1つは通常のトークン用のアセットでもう1つは払い戻し用のトークン。払い戻し用のトークンはクラウドセールの期間前や後に送金してセールを逃したユーザー向けのもの。

このタイプのコントラクトのbodyのデータ項目は、以下の通り。

フィールド 内容
deposit_address トークンを購入する際のBitcoinの送金先アドレス
price_sat 1トークンあたりの価格(satoshi)
from_block クラウドセールを開始するブロック
to_block クラウドセール終了のブロック
Re-issuance: 0x03, version: 0x0008

アセットの発行者が、供給量を増やすことでアセットの再発行を可能にするブループリント。このコントラクトを使用する際は、オリジナルのコントラクトのreissuance_enabledが0でないこと。またオリジナルコントラクトのreissuance_utxoで指定されているUTXOを使ってデプロイする必要がある。

このコントラクトの場合、header内のtitle,description,network,min_amount,max_hops,burn_address,commitment_schemeはオリジナルから変わらないので全て0をセットする。

またbodyのデータ項目はない。

プルーフ

コントラクトは上記のように種類によってルールが異なるが、各コントラクトに設定された全ての条件を満たすことでアセットの所有権を証明し、そのアセットの送付が可能になる。このコントラクトに設定された全ての条件を満たしたことを証明するのがプルーフになる。

プルーフコントラクトと同様headerbodyを持つとされているが、具体的な項目についてはまだ定義されていないっぽい。

アセット送付時のプルーフ

アセットを送付する際は、以下のデータがプルーフになる。

  • アセットの発行から今までの全てのプルーフのチェーン
  • 以下3つの項目からなるリスト
    • 取引するトークンのカラー
    • 取引する量
    • 送信するトークンのアウトプットに関する情報で、UTXOベースのトランザクションを送信する場合はSHA256D(TX_HASH || OUTPUT_INDEX_AS_U32)で、アドレスベースのトランザクションを送信する場合は受信者に送信するアウトプットインデックス。
  • (オプション)meta-dataに関連するmeta-script

これらのプルーフは以下のdark-tagを使ってAES256で暗号化され、Publisher Serverに送られる。

dark-tag

アセットの受取人が生成する30バイトのエントロピーを持つ共通鍵で、アセットを送ってもらう際に、アドレスおよびPublisher Serverの情報と一緒に、アセットの送信元に送る。

受取人がdark-tagを生成する際は、受信用のアドレスを生成するのに使用しているBIP32ベースの導出鍵を使って導出する。

プルーフの情報はこのdark-tagで暗号化されているため、取引されているカラーや量に関してはこのdark-tagを知っているユーザーのみが把握でき、第三者ブロックチェーン上のトランザクションを確認してもこれらの情報は確認できないと思われる。

dark-tagが漏洩すると、発行トランザクションからそのトランザクションまでのアセットの流通経路は分かるが、途中で分岐していた場合などはそっちのアセットの流通経路までは分からない。

Publisher Server

Publisher Serverは、各アセットの取引で生成された暗号化されたプルーフを保存し、そのクエリに対応するサーバー。アセットの発行者によってメンテナンスされる場合もあるし、個々の受信者がメンテナンスしたり、第三者によって提供されることも考えられる。

BitTorrentやIPFSなどの分散ストレージの利用も挙げられているが、RGB自体はPublisher Serverについてシンプルさのため集中型の管理もありとしてる。

コミットメント方式

アセットを新規発行する際は↑のコントラクトのデータを、アセットを送付する際はその条件を満たしていることを証明する↑のプルーフをそれぞれブロックチェーンにコミットする必要がある。Open Assets Protocolでは、これらは基本的にOP_RETURNのアウトプットにMakerOutputと呼ばれるOA用のペイロードをプッシュする形態をとっていたけど、RGBの場合は以下の2種類の方法が用意されている。

OP_RETURN

他のカラードコイン同様OP_RETURNを使う方法で、OP_RETURNの後に↑のコントラクトやプルーフのエンティティ情報をプッシュする。OP_RETURNを使ったRGBの有効なコミットメントを含むトランザクションのルールは以下の通り。

pay-to-contract

pay-to-contractは直訳するとコントラクトへの支払い。コントラクトの内容を元に鍵およびアドレスを生成するプロトコルで、昔Blockstreamが発表したSidechainのホワイトペーパーなどで紹介されている。

RGBのエンティティにコミットするpay-to-contractアドレスは以下のように計算する。

  1. ランダムに128 bitのnonceを生成する。
  2. weak = hmac_sha256(key=original_pubkey, data=(nonce||double_sha256(RGBのエンティティ)))を計算する。
  3. new_pubkey = original_pubkey + G * tweakを計算する。
  4. 計算したnew_pubkeyを公開鍵として、P2WPKH or P2PKHアドレスを生成する。

この公開鍵に対応する秘密鍵は、元の秘密鍵に対して同じ計算を適用することで計算できる。

後は計算したアドレス宛に送金するトランザクションを作るだけで、トランザクションの構成は通常の支払いと何ら変わらない。

アセットの送付方法

RGBで実際にアセットを送付する際、送信者は以下の2つの方法でアセットの所有権を移転できる。

アドレスベース

アセットの受信者がアセットのUTXO自体を受け取りたい場合に使用する。この場合、プルーフには、トランザクション内の受信者の送信先アドレス宛のアウトプットのインデックスをセットする。

※ アセットを受信者のアドレスに送るという直観的に分かりやすい方法で、他のカラードコインなどでも一般的によく使われている方法。

UTXOベース

受信者が既に持っているUTXOに、トークンをバインドしたい場合に使用する。このためこの送付トランザクションには受信者向けのアウトプットは含まれず、プルーフには、送信先のUTXOのハッシュをセットする。

※ この場合ブロックチェーントランザクションを見ただけでは、誰から誰にアセットが送付されているのか推測できない。

この場合、複数のアセットが同じUTXOを指定していた場合、そのUTXOは両方のアセットを持つということになるのかな?

RgbOutPoint

上記のようにアドレスベースの場合は送信トランザクションのアウトプットインデックスを、UTXOベースの場合は受信者の既存のUTXOのOutPointを指定することになるが、それぞれをエンコードしたオブジェクトをRgbOutPointと呼んでいる。

RgbOutPointシリアライズする際は、どちらか分かるようUTXOベースの場合は0x01、アドレスベースの場合は0x02がプレフィックスとして付与される。(長さで分かりそうな気がするけど)

所感

Open Assets Protocolとかだと、取引するアセットの量や宛先は全てトランザクション内のMarkerOutput(OP_RETURNにOpen Assets Payloadをプッシュしたもの)で管理されていて、OAのトランザクションを解釈できれば、ブロックチェーン上でどのアドレスからどのアドレスにいくつのアセットが送られているか確認できるプロトコルだったけど、RGBの場合取引するアセットやその量は全てプルーフに記載され、受信者が生成した共通鍵dark-tagで暗号化されPublisher Serverと呼ばれる外部ストレージに保存されるようになっている。ブロックチェーン単体ではアセットの動きは追えず、外部にあるプルーフが必要になる。UTXOベースのトランザクションであれば、プルーフのみがブロックチェーンアンカリングされているといっても良い。

ノードはトランザクション内のコミットメントとプルーフから受信したアセットの情報を判断するので、フルノードでなくSPVノードのような軽量ウォレットでもアセットの管理ができるだろう。

まだ不明点も多いので具体的な実装を含めてプロトコルの完成までまだ時間かかりそうだけど、他にもLNとの連携や、LNとは別のスケーラビリティソリューションなど面白そうな機能はあるので、今後が楽しみ。

不明点

↓のようにドキュメントの中に出てくるけど、内容や仕組みについてまだ書かれてないことが多い。

  • asset_idの計算方法
  • プルーフの具体的な構成と、検証の仕組み
  • プルーフチェーンの構成方法とその正しさの証明
  • meta-dataやmeta-scriptの内容と振る舞い
  • dark-tagの導出方式
  • Publish Serverと通信する際の具体的なプロトコル

ペイメントチャネルへの資金のチャージ/引き出しを行うSplicing

Lightning Networkはオフチェーン決済を行う2者が資金を両者のマルチシグにデポジットするトランザクション(Funding Tx)をオンチェーンにブロードキャストし、そのデポジットした額(キャパシティ)の範囲内でオフチェーン決済を行う。キャパシティを超える額の決済はできないし、どちらか一方の残高が0になると、0になったユーザーからのそれ以上の送金はできなくなる。ここでチャネルに資金をチャージできるようになると便利だし、逆にチャネルの資金の一部を、別のオンチェーン決済に使いたいというケースも考えられる。こういった既存のチャネルへのチャージおよび既存のチャネルからの引き出しをできるようにしようというのがSplicingのコンセプト。

現在提案されている基本的なSplicingは以下のように動作する。

Splice-in

アリスとボブのペイメントチャネルにアリスが資金をチャージしたいケースでは、以下のようにチャネルに資金をチャージする。

f:id:techmedia-think:20180623145002p:plain

アリスとボブは新しいFunding Txを作成する。この新Funding Txのインプットは

  • 既存のペイメントチャネルのFunding Txのアウトプット
  • アリスがチャネルにチャージするコインを持つUTXO

の2つ。新Funding Txのアウトプットは変わらずアリスとボブの2-of-2のマルチシグ。

続いて新Funding Txをインプットにして、それぞれCommitment Txを作成する。この新しいチャネルのCommitment Txの初期状態の残高は

  • ボブの残高は、旧チャネルの最終のCommitment Txのボブの残高
  • アリスの残高は旧チャネルの最終のCommitment Txのアリスの残高 + 新しく追加したUTXOのコインの量

となる。

両者が新しいCommitment Txの作成・署名・交換を終えたら、新Funding Txをブロードキャストする。これにより、旧チャネルのクローズと新チャネルのオープンが同時に行われる=セトルメントトランザクションが自動的に新しいチャネルのFunding Txになる。

また新Funding Txの作成途中や、新Funding Txがオンチェーン上で必要な承認数を待っている間も、ペイメントチャネルを使ったオフチェーン決済は可能で、新旧両方のチャネルでそれぞれ決済を反映したCommitment Txの更新をすれば良い。

Splice-out

反対にチャネルから資金を取り出すケースも同様の仕組みで対処することができる。

アリスがチャネル上の資金の一部を使ってオンチェーン決済を行うケースでは、以下のようにチャネルから資金を取り出してオンチェーン決済を行う。

f:id:techmedia-think:20180623153436p:plain

アリスとボブは新しいFunding Txを作成する。この新Funding Txのインプットは

  • 既存のペイメントチャネルのFunding Txのアウトプット

新Funding Txのアウトプットは、

  • アリスとボブの2-of-2のマルチシグ。
  • アリスがオンチェーン決済したいアドレス宛のアウトプット。

の2つ。

続いて新Funding Txをインプットにして、それぞれCommitment Txを作成する。この新しいチャネルのCommitment Txの初期状態の残高は

  • ボブの残高は、旧チャネルの最終のCommitment Txのボブの残高
  • アリスの残高は旧チャネルの最終のCommitment Txのアリスの残高 - オンチェーン決済に使用するコインの量

となる。

後はSplice-inと同様、両者が新しいCommitment Txの作成・署名・交換を終えたら、新Funding Txをブロードキャストする。

HTLCを使ったSplicing

もともとSplicingの議論は昨年LNの開発者MLの↓の投稿から始まった。

[Lightning-dev] Channel top-up

↑のプロトコルは、アリスがチャージしたい額のコインをオンチェーンでボブに送信し、それと同額のコインをチャネル上でボブからアリスに送る処理をHTLCを使ってアトミックにするというものだった。

この場合、アリスがボブにオンチェーン上で送金するトランザクションと、ボブからHTLCに設定された期間内にその送金を受け取るトランザクションの2つをオンチェーン上にブロードキャストする必要が出てくる。

↑のSplicingを使えば、オンチェーン上のトランザクションは1つだけで済むのと、プロトコル的にもシンプルになる。ただチャネルのクローズが発生するため、LNの各ノード上はペイメントチャネルのクローズとして認識してしまう。実際はSplicingによりキャパシティの変更があっただけで、チャネル及びルーティングは有効なままなので、それらを認識できるようなメッセージの追加なりが必要になる。

いずれにせよ、LNにおけるSplicingの仕様はまだFixされている訳ではないので、今後新しいアプローチが出てくるかもしれない。

ECDSAベースのScriptless Lightning Network

少し時間が空いたけど、Scriptless Scripts with ECDSAのホワイトペーパーについて

techmedia-think.hatenablog.com

techmedia-think.hatenablog.com

と見てきたので、最後のLightning Networkへの適用について見てみる。

Lightning NetworkのベースとなるのはPayment Channelで、このPayment ChannelはBitcoinスクリプトで

  • タイムロック
  • ハッシュプリイメージのチャレンジ

を組み合わせて実装されているコントラクトだ。

↑のホワイトペーパーでは、LNの後者のハッシュプリイメージのチャレンジをマルチホップを意識してECDSAでスクリプトレスに実装するプロトコルについて説明している。基本的なハッシュプリイメージのチャレンジは↑のAdaptor Signatureのプロトコルでも利用されているが、第三者を経由するマルチホップ決済をするため、このプロトコルとは若干異なったものになる。

Scriptless Lightning Network with ECDSA

三者を経由した決済をするHTLCを考える。この基本的な動作は↓

techmedia-think.hatenablog.com

ここでアリスがボブを経由してキャロルに送金する場合、キャロルが作成したシークレットを使ってトラストレスに三者間の決済が行われる。

(タイムロックの部分は置いといて)このハッシュプリイメージのチャレンジを3者間で行うのを、Scriptless Scriptでどう実現するかというのが課題になる。

ベースは↑のAdaptor Signatureのプロトコルで、シークレットαの取り扱いだけ変更する必要がある。Adaptor Signatureではシークレットαの値をボブが生成していたけど、これを生成するのがキャロルになり、アリスとボブは最初αの値を知らない。

アリスとボブの両者がαの値について知らない場合、Adaptor Signatureのプロトコルの手順3でRを計算できないという問題が出てくる。この問題を解決すれば残りのプロトコルはそのままで良い。

解決策を簡単に言うと、αを生成したキャロルは、楕円曲線上の点(=公開鍵) A = gαをアリスとボブに公開して、そのAを使ってRを計算することでこの問題を解決する。

修正されたプロトコルは以下のようになる。

  1. アリスとボブはP1, P2, R1 = gk1, R2 = gk2, m',  {Q = g^{x1 \cdot x2}}について同意する。
  2. ボブは {R3 := (A)^{k2}}を計算し、k2の値を明らかにすることなく {R2 = g^{k2} \land R3 = (A)^{k2} }のゼロ知識証明と一緒にアリスに送る。アリスも同様に {R'3 := (g^{α})^{k1}}を計算し、k1の値を明らかにすることなく {R1 = g^{k1} \land R'3 = (g^{α})^{k1} }のゼロ知識証明と一緒にボブに送る。
  3. アリスは {R \gets (R3)^{k1}}でRを計算し、ボブは {R \gets (R'3)^{k2}}でRを計算する。計算した同じ楕円曲線上の点Rのx座標をrとする。
  4. ボブは {c1 \gets Enc_{pkA}((k2)^{-1} \cdot m' + pq)} {c2 \gets (ckey) \odot (x2 \cdot r \cdot (k2)^{-1})}を計算する。続いてボブは {c_3 = c_1 \oplus c_2}を計算する。この {\oplus}はPaillier暗号の加法準同型演算を表す。結果、 {c3 = EncpkA((k2)^{-1} \cdot m' + pq + x1 \cdot x2 \cdot r \cdot (k2)^{-1})}となるc3をアリスに送る。
  5. アリスはc3を復号しs'を取得する。ここでアリスはボブが暗号化中に不正行為をしていないか確認する必要がある。これは、 {(R_2)^{s' mod q} = Q^{r} \cdot g^{m'}}が成立するか検証すればいい。問題なければ、 {s'' \gets s' \cdot (k1)^{-1}}を計算しs''をボブに送る。
  6. 少ししてボブはペイメントパス上のキャロルから値αを得る(キャロルがボブからの送金と引き換えにαをボブに公開する)。ボブは {s \gets (α)^{-1} \cdot s''}を計算し、署名(r, s)を出力する。
  7. ボブが作成した署名が公開されると、アリスは {α \gets (s \cdot (s'')^{-1})^{-1}}を計算する。

という形で、ECDSAベースでもHTLCを利用した中間者決済をするScriptless Scriptが実装できると。
(ほとんどAdaptor Signatureのプロトコルと同じなのでサンプルコードは割愛)

タイムロックの取り扱い

Payment Channelを構成するもう1つの要素がタイムロックだが、こちらはおそらくScriptless Scriptとして実装するのは難しいので、スクリプトとは別にタイムロックを担保する仕組みが必要になるだろう。

ちなみに、MimbleWimbleの最小実装をするGrinではトランザクションのタイムロックを実現するのに以下のような変更を提案している。

https://github.com/mimblewimble/grin/blob/master/doc/contracts.md

  • 署名対象のメッセージMについて、そのトランザクションが使用可能になるブロック高をhとし手数料と結合してM = fee | hとする。
  • ロックするブロック高はトランザクションカーネルに含まれる。
  • 現在のブロック高より大きいブロック高が設定されているカーネルを含むブロックはリジェクトされる。

Schnorrの非対話型ゼロ知識証明(NIZKP)について定義しているrfc8235を読んでみた

Schnorrにおいて、ある離散対数の知識を知っていることをゼロ知識で証明するプロトコル(Schnorr NIZK proof)が2017年にrfc2835rfc8235として登録されていたので読んでみた。

RFC 8235: Schnorr Non-interactive Zero-Knowledge Proof

簡単に概要を説明すると、

証明者と検証者がいる中で、証明者がもつ離散対数の知識を検証者にその知識を明かすことなく証明するプロトコルがゼロ知識証明で、二者間で↓のようなやりとりをする。

  1. 通常は証明者が自身が証明したい離散対数に対応した公開鍵と、それとは別にランダムに選択した離散対数に対応する公開鍵を検証者に公開する。
  2. 検証者はチャレンジを生成し、それを証明者に送る。
  3. 証明者が2つの離散対数とそのチャレンジを使ってある計算をし、その計算結果を検証者に送る。
  4. 検証者はその値がと証明者から送られた公開鍵を使って証明者が離散対数の知識を知っていることを確認する。

↑は検証者が生成したチャレンジをベースに計算するので対話的な証明プロセスになってるけど、このチャレンジの生成を決定論的に行うことで(Fiat-Shamir変換)、2,3の対話が不要になり非対話型にできる。

Schnorrベースでこの非対話型のゼロ知識証明のプロトコル仕様を定義してるのがrfc2835で、有限体および楕円曲線を利用した定義がそれぞれされている↓
(でもこれSchnorr限定というものではなく、使われてる計算はそのままECDSAでも成立するよね。)

以下RFCの定義内容の意訳。

1. イントロダクション

堅牢な公開鍵プロトコルを設計するためのよく知られている法則は

「チェックが出来ない場合、受け取ったメッセージが特定の形式(例えば既知のrに対するgrのように)を持っていると仮定しないこと。」

これはCrypto '95でRoss AndersonとRoger Needhamによって定義された8つの原則の6つめの原則で、「sixth principle」として知られている。過去30年の間に、多くの公開鍵プロトコルが、この原則に違反によって、攻撃を防ぐことができなかった。

「sixth principle」を満たすのにいくつか方法があるが、ここでは値を明らかにすることなく、その離散対数の知識を知っていことを証明する1つの技法について説明する。この技法はSchnorr NIZK proofと呼ばれ、three-pass Schnorr identification schemeを非対話型に変形したものだ。安全な暗号学的ハッシュ関数が存在する仮定のもと、オリジナルのSchnorr identification schemeはFiat-Shamir変換によって非対話型になる。

Schnorr NIZK proofは有限体もしくは楕円曲線で実装できる。基底の巡回群が異なることを除いて、技術仕様は基本的に同じだ。完全性を期すため、この文書では有限体と楕円曲線両方のSchnorr NIZK proofについて説明する。

2. 有限体上のSchnorr NIZK proof

2.1 グループパラメータ

有限体上で実装された場合、Schnorr NIZK proofはDSAと同じグループ設定を使用できる。pqを2つの大きな素数とする(q | p - 1)。Gqを素位数qZqの部分群とし、gを部分群の生成元とする。異なるセキュリティレベルを提供する(p, q, r)の値については、NIST Cryptographic Toolkitのサンプルを参照。128 bit以上のセキュリティレベルが推奨される。ここではDSAグループは例としてのみ使用されている。離散対数問題(DLP)が扱いにくい他の乗法群もSchnorr NIZK proofの実装に適している。

2.2 Schnorr Identification Scheme

Schnorr identification schemeのルールはアリス(証明者)とボブ(検証者)の間で対話的に実行される。スキームのセットアップにおいて、アリスは自身の公開鍵A = g^a mod pを公開する。ここでa[0, q-1]からランダムに一様に選択された秘密鍵

プロトコルは以下の3つをパスすることで動作する。

  1. アリスは[1, q-1]の中からランダムに一様にvを選択し、V = g^v mod pを計算する。そして計算したVをボブに送る。
  2. ボブは[1, t-1]からランダムに一様にチャレンジcを選択する。ここでtはチャレンジのビット長(例えば t = 160)。ボブは選択したcをアリスに送る。
  3. アリスはr = v - c * a mod nを計算し、結果をボブに送る。

ボブはプロトコルに最後に以下のチェックを行う。いずれかのチェックが失敗すると、同定は失敗する。

  1. Aが[1, p-1]内にあり、A^q = 1 mod pであることを検証する。
  2. V = g^r * A^c mod pを検証する。

最初のチェックは、Aが有効な公開鍵であることを保証するので、基底gに対するAの離散対数は実在する。一部のアプリケーションでは、アイデンティティ要素を有効な公開鍵として明確に除外することがある点については注意する価値がある。その場合、Aが[1, p-1]内にあるかの代わりに[2, p-1]内にあるかチェックする必要がある。

プロセスは以下の図のようにまとめられる。

2.3 非対話型ゼロ知識証明

Schnorr NIZK proofは、Fiat-Shamir変換を介して非対話型のSchnorr identification schemeが得られる。この変換には、チャレンジを発行する代わりに安全な暗号学的ハッシュ関数の使用が含まれる。より具体的には、チャレンジはc = H(g || V || A || UserID || OtherInfo)として再定義される。ここでUserIDは証明者の一意の識別子でOtherInfoはOPTIONALデータである。ここでハッシュ関数HはSHA-256、SHA-384、SHA-512、SHA3-256、SHA3-384、またはSHA3-512などの安全な暗号ハッシュ関数とする。ハッシュのアウトプットのビット長は少なくともサブグループの位数qのビット長と同じでなければならない。

OtherInfoは、Schnorr NIZK proofにコンテキスト情報を柔軟に含めることができるよう定義されているため、このドキュメントで定義されている手法は一般的に有用だ。例えば、 Schnorr NIZK proof上に構築されたいくつかのプロトコルの中には、プロトコル名やタイムスタンプなどより多くのコンテキスト情報を含めるものもある。OtherInfoの正確な項目は、定義する特定のプロトコルに任されなければならない。ただし、特定のプロトコルにおいてOtherinfoは固定され、プロトコルで明示的に定義されていなければならない。

ハッシュ関数内では、連結される2つの項目の間に明確な境界が存在する必要がある。その項目のバイト長を表す4バイトの整数を常に項目の先頭に付与することが推奨される。OtherInfoには複数の副項目が含まれる場合がある。その場合、隣接する副項目間の明確な境界を保証するため同じルールが適用されるものとする。

2.4 計算コスト

要約すると、A = g^aの指数の知識を証明するため、アリスは{UserID, OtherInfo, V = g^v mod p, r = v - a*c mod q}を含むSchnorr NIZK proofを生成する。ここでcc = H(g || V || A || UserID || OtherInfo)

Schnorr NIZK proofを計算するコストは、およそ1つのモジュロ累乗、すなわちg^v mod pの計算だ。実際には、この冪乗は効率を最適化するためオフラインで事前にされてもいい。残りの演算(乱数生成、剰余乗算、ハッシュ計算)コストは冪乗剰余と比較して無視できる。

Schnorr NIZK proofを検証するコストは、およそ2つの累乗で、1つはA^q mod pの計算で、もう1つはg^r * A^c mod pの計算。

3. 楕円曲線を利用したSchnorr NIZK proof

3.1 グループパラメータ

楕円曲線上に実装すると、Schnorr NIZK proofはECDSAと同じ楕円曲線の設定を使うことがある。説明目的のため、プライムフィールド上の曲線のみ(NIST P-256など)をここで説明する。Schnorr NIZK proofの実装には、ECDSAに適したバイナリフィールド以外の曲線を使うことも可能だ。E(Fp)を有限体Fp上で定義される楕円曲線とする。ここでpは巨大な素数。続いてG素数位数nE(Fp)に対する部分群のジェネレータとして機能する曲線上の基点とする。部分群の余因子(cofactor)はhで示され、通常は4以下の小さな値。加算やマイナス、スカラ乗算などの楕円曲線の演算の詳細については、Handbook of Applied Cryptographyを参照。 elliptic-curve-point-to-octet-stringを含むデータ型の変換は、SEC1の2.3節参照。ここではNISTの曲線を一例として使う。楕円曲線の離散対数問題が簡単に解けない限り、Curve25519などの他の安全な曲線も実装に適している。

3.2 Schnorr Identification Scheme

スキームのセットアップにあたり、アリスは秘密鍵aを[1, n-1]の中からランダムに選択し、公開鍵A = G × [a]を公開する。

プロトコルは以下の3つのやりとりで動作する。

  1. アリスは[1, n-1]の中からランダムな数値vを選択し、V = G × [v]を計算し、Vをボブに送る。
  2. ボブはチャレンジcを[1, t-1]からランダムに選択する。ここでtはチャレンジのビット長(例えば t = 80)。ボブは選択したcをアリスに送る。
  3. アリスはr = v - c * a mod nを計算し、結果をボブに送る。

プロトコルの最後に、ボブは以下のチェックを行う。いずれかのチェックに失敗した場合、検証は失敗する。

  1. A楕円曲線上の有効な点であり、A × [h]無限遠点でないことを検証する。
  2. V = G x [r] + A x [c]が成立するか検証する。

最初のチェックは、基底Gに対するAの離散対数が実際に存在することの確認=Aが有効な公開鍵であることを保証する。ECDSAのような設定は、公開鍵の検証に完全な冪剰余が必要なDSAのようなグループ設定と異なり、公開鍵の検証コストは余因子が小さいため(1,2もしくは4など)ほとんど無視できる。

プロセスをまとめると以下のようになる。

3.2 非対話型ゼロ知識証明

これまでと同様、チャレンジを発行する代わりに安全な暗号学的ハッシュ関数を使用することで、Fiat-Shamir変換により非対話型になる。チャレンジcc = H(G || V || A || UserID || OtherInfo)として定義され、UserIDは証明者の一意な識別子で、OtherInfoは前述したようにOPTIONALデータである。

3.3 計算コスト

まとめると、楕円曲線上の基底Gにに関してA = G × [a]の離散対数の知識を証明するため、アリスは{UserID, OtherInfo, V = G x [v], r = v - a*c mod n}を含むSchnorr NIZK proofを生成する。cc = H(G || V || A || UserID || OtherInfo)

Schnorr NIZK proofを生成するためのコストは、1つのスカラ乗算G x [v]をするコストになる。

楕円曲線の設定でSchnorr NIZK proofの検証をするコストは、楕円曲線に対して1つの乗算、つまりG x [r] + A x [c]を計算するコストになる。楕円曲線の設定において、公開鍵検証のコストは本質的にフリーだ。

4. Schnorr NIZK proofのバリエーション

有限体の設定では、証明者は(UserIDとOtherInfoと一緒に)(V, r)を送信し、検証者は最初にcを計算し、続いてV = g^r * A^c mod pをチェックする。これは2048〜3072 bitのサイズのZpの要素Vと、224〜256 bitのサイズのZpの要素rの伝送を必要とする。以下のように、Zpの2つの要素に送信するデータを以下のように削減することができる。

修正版は、証明者が(V, r)の代わりに(c, r)を送信すること以外は同じように動作する。検証者はV = g^r * A^c mod pを計算し、H(g || V || A || UserID || OtherInfo) = cが成立するかチェックする。この変形例のセキュリティは、(c, r)からVを、(V, r)からcを計算することができるという事実に従う。したがって、(c, r)を送信することは(V, c, r)を送信することと同じで、それは(V, r)を送信することと同じだ。このためSchnorr NIZK proofのサイズは大幅に削減でき、証明者と検証者の計算コストは同じままである。

(V, r)の送信を(c, r)の送信に置き換えることで、同様の最適化手法を楕円曲線を使った設定にも適用できるが、利点は限定的になる。Vが圧縮形式でエンコードされると、この最適化により削減できるサイズは1 bit分のみであるためで、ゼロ知識証明の生成および検証の計算コストは以前と同じだ。

5. Schnorr NIZK proofの応用

J-PAKEやYAKのようないくつかの主要な交換プロトコルでは、参加者が離散対数の知識を持っていることを保証するためにSchnorr NIZK proofに依存している。このドキュメントに記載されている技法は、それらのプロトコルに直接適用することができる。

OtherInfoを含めることで、 Schnorr NIZK proofは幅広い応用に対応するため一般に便利で柔軟性がある。例えば記載されている技術は、ユーザーが公開鍵登録期間中、秘密鍵の証明を証明期間に示すのに使うことができる。ハッシュにはある鍵の登録手順にリンクされているデータが含まれていることが保証されなければならない(OtherInfo内に、CA名、有効期間、申請者の電子メールの連絡先など)。この場合Schnorr NIZK proofは、DSAもしくはECDSAを使って生成されたCSRと機能的に同等だ。

6. セキュリティに関する考慮事項

Schnorrの同定プロトコルは、検証者が正直で離散対数問題の困難性という仮定のもと、以下の特性を満たすことが証明されている。

  1. 完全性
    離散対数を知っている証明者は常に検証チャレンジに合格できる。
  2. 健全性
    離散対数を知らない敵対者が、検証チャレンジに合格する確率は無視できるレベルしかない(2-t)。
  3. 正直な検証者のゼロ知識性
    証明者は正直な検証者に、自身が離散対数を知っているかどうか1 bitも情報を漏らさない。

Fiat-Shamir変換はセキュアな暗号学的ハッシュ関数が存在すると仮定した上で、three-pass対話型のゼロ知識証明プロトコルを(検証者がランダムにチャレンジを選択する)非対話型プロトコルに変換する標準的な手法だ。ハッシュ関数は公に定義されているので、証明者は単独でチャレンジを計算することができ、プロトコルを非対話型にすることができる。この場合、証明者が送信した各コミットメント(gv もしくは G x [v])に、一様にランダムにチャレンジcを割り当てるため、ハッシュ関数(正確にはセキュリティ証明のランダムオラクル)は正直な検証者を実装する。これはまさに正直な検証者がやることだ。

Schnorrの同定スキームと非対話型の要素には、安全な乱数生成器が必要なことに注意することが重要だ。特に、vのランダム精度が低いと秘密の離散対数が明らかになる。例えば、(ランダム値の生成に失敗して)同じランダム値 V = gv mod p が証明者によって2回使われたとする。そして検証者が異なるチャレンジ c と c' を選択する(もしくは、2つの異なるOtherInfo dataにハッシュ関数が使われ、2つの異なるチャレンジ c と c' を生成する)。敵対者は2つの証明トランスクリプト(V, c, r)と(V, c', r')を観察して、以下のように秘密鍵を計算する。

(r-r')/(c'-c) = (v-a*c-v+a*c')/(c'-c) = a mod q.

より一般的には、このような攻撃は、同じvが生成されることはないが敵対者にとって既知の値wに対してv' = v + wという関係が成り立つvとv'を生成するような、やや精度の良い(でも安全ではない)乱数生成器でも機能する。敵対者が2つの証明トランスクリプト(V, c, r)と(V, c', r')を観察すると仮定すると、敵対者は以下のように秘密鍵を計算することができる。

(r-r'+w)/(c'-c) = (v-a*c-v-w+a*c'+w)/(c'-c) = a mod q

この例は、Schnorrのスキームで一時的なシークレットvを生成するのに安全な乱数生成器を使用することの重要性を強調している。

最後に、セキュリティプロトコルが非対話的な方法で離散対数の知識を証明するSchnorr NIZK proofに依存する場合、リプレイ攻撃の脅威が考慮される。例えばSchnorr NIZK proofは(暗号プロトコルで項目間に好ましくない相関関係を導入するため)証明者自身にリプレイバックされる可能性がある。この特定の攻撃はハッシュに一意のUserIDを含めることで防止される。検証者は証明者のUserIDが有効なアイデンティティで自身のものと異なることを確認しなければならない。特定のプロトコルのコンテキストによって、他の形式のリプレイ攻撃について考慮し、必要に応じてOtherinfoに適切なコンテキスト情報を含める必要がある。

再利用可能なペイメントコードを使った二者間の決済アドレスの導出方法を定義したBIP-47

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でない場合は、別の適切なセクションを参照。

  1. アリスは少量のコインをボブの通知アドレスに送金するトランザクション(通知トランザクション)を構築する。
    1. このトランザクションのインプットは、アリスの通知トランザクションと簡単に関連付けられないアドレスにしなければならない。
  2. アリスはECDHを使ってユニークな共有シークレットを作成する。
    1. アリスは指定公開鍵に対応する秘密鍵を選択する。
      a
    2. アリスはボブの通知アドレスに関連付けられた公開鍵を選択する。
      B, ここでBはB = bG
    3. アリスはシークレットポイントを計算する。
      S = aB
    4. アリスは64バイトのblinding factorを計算する。
      s = HMAC-SHA512(x, o)
      • xはシークレットポイントSのx座標
      • oは指定インプットが指すOutpoint
  3. アリスは自身のペイメントコードをバイナリ形式でシリアライズする。
  4. アリスは自身のペイメントコード(P)をボブ以外誰も読めないようにする。
    1. xをx'に置き換える
      x' = x XOR (sの先頭32バイト)
    2. chain codeをc'に置き換える
      c' = c XOR (sの最後32バイト)
  5. アリスはPを構成するOP_RETURNアウトプットをトランザクションに追加する。
    https://github.com/bitcoin/bips/raw/master/bip-0047/reusable_payment_codes-01.png

ここからボブのターン。

  1. ボブは通知アドレス宛の送金トランザクションを監視する。
  2. トランザクションを受信すると、クライアントは80バイトのペイロード(通知トランザクション)を持つ標準のOP_RETURNが含まれるかチェックする。
  3. 通知トランザクションペイロードの最初のバイトが0x01の場合
    1. ボブは指定公開鍵を選択する。
      A、ここでAは A = aG
    2. ボブは通知アドレスに関連する秘密鍵を選択する。
      b
    3. ボブはシークレットポイントを計算する。
      S = bA
    4. ボブはblinding factorwを計算する。
      s = HMAC-SHA512(x, o)
      • xはシークレットポイントSのx座標
      • oは指定インプットが指すOutpoint
    5. ボブは80バイトのペイロードをペイメントコードとして解釈する。以下の除いて
      1. xをx'に置き換える
        x' = x XOR (sの先頭32バイト)
      2. chain codeをc'に置き換える
        c' = c XOR (sの最後32バイト)
    6. 更新されたx値がsecp256k1グループのメンバーである場合、ペイメントコードは有効。
    7. 更新されたx値がsecp256k1グループのメンバーでない場合、ペイメントコードは無視される。

ボブのクライアントがアリスのペイメントコードを受け取ったので、アリスはボブに支払いができる(最大232回)。

アリスはボブに再度通知トランザクションを送る必要はない。

通知トランザクションを介して受信したビットコインは、プライバシー漏洩を避けるため特別な処理が必要になる。

  1. 通知アドレス宛に受信されたアウトプットの値は、消費可能な残高の一部としてユーザーに表示されてはならない。
  2. 通知アドレス宛に受信されたアウトプットはユーザーのペイメントコードのいずれかを使ってECDH計算をするトランザクションのインプットとして使ってはならない。
  3. 通知アドレス宛に受信されたアウトプットは、ユーザーの消費可能残高に追加される前に、ミキシングサービスを通貨することもできる。
  4. 通知アドレス宛に受信されたアウトプットは、dust-b-goneもしくは同等の手順でマイナーに寄付することができる。
通知トランザクションの標準スクリプト

アリスは公開鍵を公開するのに以下のいずれかの形式のインプットスクリプトを使用する必要があり、準拠するアプリケーションはこれらの全ての形式を認識すべきだ。

互換性のあるウォレットは、非標準の通知トランザクションを介して送信されたペイメントコードをリカバリするために、通知トランザクションに関連付けられた公開鍵をユーザーが手動で設定する方法を提供してもいい。

通知後のプライバシーに関する考慮事項

通知トランザクションのお釣りアウトプットの軽率な処理は、意図しないプライバシーの損失を引き起こす可能性がある。

前の通知トランザクションのお釣りのアウトプットを消費するトランザクションの受信者は、通知トランザクションの送信者と受信者間の潜在的な接続について学習する。

このリスクを軽減するために、以下の処理を推奨する。

  • ミキシングをサポートするウォレットは、そのお釣りを使用する前にミキシングすべきだ。
  • ミキシングをサポートしていないウォレットでは、お釣りのアウトプットを次のBIP-44の外部アドレスに送信するトランザクションを作成することでミキシングをシミュレートできる。
送金
  1. アリスはボブへの送金の度に、以下のようにECDHを使って一意のP2PKHアドレスを導出する。
    1. アリスは自分のペイメントコードから導出した0番目の秘密鍵を選択する。
      a
    2. アリスはボブのペイメントコードから導出した(0から順に導出)次の未使用の公開鍵を選択する。
      B、ここでBは B = bG
      1. 次の未使用の公開鍵は、アリスまたはボブのいずれにもグローバルなものでなく、アリス−ボブ間のコンテキスト固有のもの。
    3. アリスはシークレットポイントを計算する。
      S = aB
    4. アリスはSのx座標を使ってスカラー共有シークレットを計算する。
      s = SHA256(Sx)
      1. sの値がsecp256k1グループに無い場合、アリスはボブの公開鍵を取得するために使用されたインデックスをインクリメントして再試行する必要がある。
    5. アリスはスカラー共有シークレットを使って、このトランザクションのP2PKHアドレスを生成するために使われる一時的な公開鍵を計算する。
      B' = B + sG

https://github.com/bitcoin/bips/raw/master/bip-0047/reusable_payment_codes-04.png

https://github.com/bitcoin/bips/raw/master/bip-0047/reusable_payment_codes-05.png

ここからボブのターン

  1. ボブはアリスから通知トランザクションを受け取って以降、B'への入金を監視している。
    1. ボブはアリスのペイメントコードから導出された0番目の公開鍵と、ボブのペイメントコードから導出された秘密鍵0〜n(nは希望する先読みの幅)を使ってアリストのn個の共有シークレットを計算する。
    2. ボブはアリスと同様の手順で、一時的な入金アドレスを計算する。
      B' = B + sG
    3. ボブは各一時アドレスの秘密鍵を以下のように計算する。
      b' = b + s

https://github.com/bitcoin/bips/raw/master/bip-0047/reusable_payment_codes-02.png

https://github.com/bitcoin/bips/raw/master/bip-0047/reusable_payment_codes-03.png

払い戻し

ボブは決済の過程でアリスのペイメントコードを知っているので、ボブはアリスに払い戻しを行うのに必要な情報をすべて持っている。

払い戻しのトランザクションは支払いのトランザクションと同じだが、参加者の役割だけが切り替わる。

たとえボブが過去にアリスから通知トランザクションを受け取っていたとしても、ボブはアリスに資金を送る前にアリスに通知トランザクションを送らなければならない。

https://github.com/bitcoin/bips/raw/master/bip-0047/reusable_payment_codes-06.png

匿名支払い

アリスが自分のアイデンティティと関連付けられることになるボブへの支払いを行いたくない場合は、アリスは取引に使用する一時的なペイメントコードを作成することができる。

  • 一時的なペイメントコードは、インデックス0から始まるペイメントコードの強化された子鍵である。
  • 一時的なペイメントコードは、単一の支払いに対してのみ使われるべきである。
  • 一時的なペイメントコードの通知アドレスは、払い戻しを検知するため監視されなければならない。
  • BIP-44のアカウントと一時的なペイメントコードの対応は1対多になる。
コールドストレージ
  • 従来の監視専用のウォレットと違い、ペイメントコードと関連付けられたコールドストレージに保管されているウォレットは、入金をすぐに検知することはできない。
  • 監視専用のウォレットは通知トランザクションを検知すると、そのトランザクションをオフラインデバイスへの転送に適した実装固有の形式にパッケージする。
  • オフラインデバイスはペイメントコードをリカバリし、エアギャップのラウンドトリップの必要性を最小限に抑えるため、多数の関連するキーペア(例えば1000個)を事前に生成する。
  • オフラインデバイスは関連する公開鍵をオンラインデバイスへの転送に適した実装固有のフォーマットでパッケージする。
  • オンラインデバイスは適切なlookahead幅で入金を監視することができる。
  • lookahead幅が予め生成された公開鍵の終わりに達すると、ユーザーはオフラインデバイス上でさらに多くの鍵を生成し、それらをオンラインデバイスに転送する必要がある。
ウォレットのリカバリ

ペイメントコード対応のウォレットの通常操作はSPVクライアントで実行でき、ブロックチェーンの完全なコピーへのアクセスは必要ない。

ただしシードからウォレットをリカバリするには、フルインデックスのブロックチェーンにアクセスする必要がある。

必要なデータは、ユーザーの制御下にあるブロックチェーンのコピーか、または公開されているブロックチェーンエクスプローラを介して取得できる。

公開されているブロックチェーンエクスプローラを使用する場合、ウォレットはTorを介してエクスプローラに接続し、一時的なアドレスを互いに関連付けるようなクエリをグルーピングするのを避けること。

以前の使用可能な資金は一般的にシードからリカバリした後、アクセス不能になることはないが、以前の出金に関する情報は全て失われる。

シードから受け取った資金をリカバリするには、通知アドレス宛に送られたトランザクションを(使用済みのもの含めて)全て入手する必要がある。ユーザーが指定した数分未使用の通知アドレスが連続して登場するまで、各導出アドレスを順次走査することで、各サブチェーンのlookahead幅を再確立する。

ウォレットが送信するトランザクションを適切に処理するため格納する必要があるメタデータは以下のように構成される。

  1. identityが通知トランザクションを送信したすべてのペイメントコードのリスト
    1. ウォレットがシードからリカバリされた場合、このリストは失われる。
    2. リカバリされたウォレットは、新しく作成されたウォレットであるかのように通知トランザクションを送信しなければならない。
  2. 前のリストの各ペイメントコードの次の未使用公開鍵に対応するインデックス値
    1. このインデックス値は、順番に一時的なデポジットアドレスをチェックすることをリカバリ可能。
    2. ウォレットはこのリカバリ操作中にギャップを検出できるlookahead幅を使ってもいい。
  3. 未使用の一時的なペイメントコードのインデックス値
    1. 一時的なペイメントコードに関連するすべての入金を100%確実にリカバリするためには、潜在的な一時的なペイメントコードのアドレス空間232を使い果たす必要がある。
      1. ほとんどの場合、フォールバックディープスキャンがオプションで利用可能である限り100%未満の確実性が許容される。
    2. ウォレットは関連する資金をリカバリするために、通知トランザクションの各一時ペイメントコードの通知アドレスをチェックする。
    3. ほとんどの一時ペイメントコードは払い戻しトランザクションを受け取らないので、ウォレットはこのリカバリのために大きなlookahead幅を使うべきだ。
    4. リカバリされた値は、通知トランザクションで受信した任意の一時ペイメントコードよりも高い値が選択されてなければならない。
ウォレットの共有

ペイメントコードを使用するウォレットは、通常各インスタンス間でメタデータを同期する必要があるため、複数のデバイス間で共有するべきではない。

ウォレットが同期メカニズムなしでデバイス間で共有される場合、望ましくないアドレスの再利用が発生する可能性がある。

ウォレットはトランザクションを送信する前に一時的なデポジットアドレスのトランザクションが既にないか、ブロックチェーンのローカルコピーをチェックするか、Torまたは同等の機能を介してパブリックブロックチェーンエクスプローラに問い合わせることでチェックすることがある。

別の通知方法

受信者がシードからウォレットをリカバリしないといけない場合に、資金が失われないことを保証するために、送信者は最初に特定の受信者とやりとりをする時に通知トランザクションを送信しなければならない。

受信者は通知トランザクション以外に送信者が使用できる代替の方法を指定することもできる。

受信者が別の通知方法を指定する場合、準拠している実装では、通知アドレスを継続的に監視することを控えてもいい。ただ代替方法を使用できないユーザーからの支払いを検出する必要があるため通知アドレスは定期的にチェックする必要がある。

受信者はfeature byte内のビットを適切にセットすることで、代替通知の優先度を指定する。

Bitmessageの通知

Bitmessage経由で通知を受け取ることを希望する受信者は、以下の設定をする。

  • feature byteのビット0を1に設定する。
  • シリアライズされたペイメントコードの67バイトめを希望するBitmessageのアドレスバージョンに設定する。
  • シリアライズされたペイメントコードの68バイトめを希望するBitmessageのストリーム番号に設定する。

送信者はこれらの情報を使って有効な通知Bitmessageアドレスを構築する。

  1. Bitmessageの署名鍵を以下のように導出する。
B = payment code / 0 / 0
  1. カウンターを1に初期化する
n
  1. 候補暗号鍵を導出する。
B' = payment code / 0 / n
  1. BとB'を組み合わせて有効なBitmessageアドレスを形成出来ない場合は、nをインクリメントして再度試みる。
  2. Bitmessageプロトコル毎にBitmessageアドレスを構成するために、アドレスバージョン、署名鍵、暗号鍵、ストリーム番号を使用する。

送信者はbase58形式の自分のペイメントコードをBitmessageアドレスに送信する。

Bitmessage通知を使用するには、送信者が導出したBitmessageアドレスを見てるBitmessageクライアントを受信者が持ち、受信したペイメントコードをBitcoinウォレットにリレーできる必要がある。

バージョン2

バージョン2のペイメントコードは下記の変更を除いてバージョン1と同じように動作する。

表現

バイナリシリアライゼーション
  • Byte 0: バージョン。必須値:0x02

プロトコル

定義
  • 通知のお釣り用アウトプット:通知トランザクションのお釣り用のアウトプットは送信者のウォレットにあるが、意図された受信者によって自動的に配置される。
  • ペイメントコード識別子:バイナリシリアライゼーションしたペイメントコードのSHA-256ハッシュに0x02を付与して構成されたペイメントコードの33バイト表現
通知トランザクション

※ この手順は、(アリスのペイメントコードに関わらず)ボブがバージョン2のペイメントコードを使っている場合に使用される。ボブのペイメントコードがバージョン2でない場合はこの仕様の別のセクションを参照。

  1. ボブの通知アドレスを作成しないことを除いて、バージョン1と同様に通知トランザクションを構築する。
  2. 以下のように通知のお釣り用アドレスを作成する。
    1. 次のお釣り用アドレスに対応する公開鍵を入手する。
    2. 以下の形式のマルチシグアウトプットを作成する。
      OP_1 <ボブのペイメントコード識別子> <お釣り用アドレスの公開鍵> OP_2 OP_CHECKMULTISIG

上記スクリプトにおけるペイメントコード識別子とお釣り用アドレスの公開鍵の相対的な順序はランダム化されてもいい。

ボブはブルームフィルタに自身のペイメントコード識別子を追加することで、通知トランザクションを検知する。

  1. フィルタが通知トランザクションを返すと、バージョン1の通知トランザクションと同様の手順で、送信者のペイメントコードがアンブラインドされる。

アリスのウォレットは次の適切な機会に通知トランザクションのお釣り用アウトプットを消費する必要がある。

Test Vector

参照

まとめ&所感

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セットが膨張することも無いと。