Develop with pleasure!

福岡でCloudとかBlockchainとか。

高速で秘匿性の高いスマートコントラクトをサポートするZkVM

Scaling Bitcoin 2019予習シリーズ第5弾は、「ZkVM: zero-knowledge virtual machine for fast confidential smart contracts」。おそらく内容はStellarが開発している↓のZkVMの話だと思われる。

https://github.com/stellar/slingshot/tree/main/zkvm

ZkVMブロックチェーン

ZkVMはTxVMの研究から生まれたゼロ知識仮想マシンで、キーとなる機能はゼロ知識証明を利用したコインやコントラクトの秘匿化だが、その他にもTaprootやUtreexoなど現在Bitcoinの改善案として提案されている新しい技術が導入されている。

ブロックチェーンの構造

ZkVMブロックチェーンの参加ノードはそれぞれがブロックチェーンステートを管理する。このブロックチェーンステートは以下の要素で構成される。

名称 定義
initialheader ブロックチェーンの初期ブロックヘッダー
tipheader 最新のブロックヘッダー
utxos Utreexoで管理するUTXOのルート

そしてブロックヘッダーは以下の要素で構成される。

名称 定義
version ブロックのバージョンで、現在は1
height ブロック高
previd 前のブロックのID
timstamp_ms タイムスタンプ
txroot ブロック内のトランザクションのマークルルート
utxoroot Utreexoのルート
ext 将来の拡張用でversion 1では空

既存のネットワークに参加する場合は、公開されている初期ブロックとブロックの履歴を入手しチェーンを同期する。新しいネットワークを作る場合は自分で任意のtimstamp_msutxosをセットして初期ブロックを作成するみたい。

各ノードは新しいブロックを受け取ると、ローカルに保持しているブロックチェーンステートを更新する。

トランザクション

ZkVMブロックチェーンはUTXOモデルのブロックチェーンで、トランザクションのインプットが前のトランザクションのアウトプットを参照し、そのコインを新しいトランザクションアウトプットへ移動させる仕組みはBitcoinと同様。

f:id:techmedia-think:20190906150918p:plain
UTXOモデルのトランザクション

各インプットは前のトランザクションアウトプットを参照する識別子を持ち、暗号署名でそのコインをアンロックする。各アウトプットはコインの新しい宛先。トランザクションの各アウトプットはコントラクトを持ち、コントラクトには値もしくはデータパラメータである任意の数のアイテムが含まれ、Predicateによって保護される。Predicateはこのアウトプットの資産をアンロックするために満たす必要がある条件で、公開鍵やサブプログラムで構成される。

トランザクションは一意のトランザクションIDを生成するのに必要なデータとロジックを含む以下の要素で構成される。

名称 定義
Version バージョン
Time bounds 最小時間と最大時間の範囲を表す時間制限
Program ZkVMの命令シーケンスを表す可変長のバイト配列で、スタックベースのZkVMで実行されるプログラム。
Signature 64バイトの署名
Proof VM実行中にConstraint systemを満たす証明で、可変長の点の配列とスカラー

表から分かるように、実はBitcoinトランザクションのように明示的にインプットやアウトプットをセットするフィールドは存在しない。ZkVMにはinput命令やoutput命令があり、インプットとアウトプットの組み立てもProgram内の命令コードとデータを使って行うようになっている。

ZkVMの実行

ZkVMはトランザクションを検証するためのスタックマシンで、トランザクションのプログラムを実行し、そのトランザクションの有効性を検証し、ブロックチェンステートの更新リストを計算するようになっている。

f:id:techmedia-think:20190906165557p:plain
ZkVMの処理フロー

上述したように、インプットおよびアウトプットの指定もこのProgram内で行われる。例えば、ZkVMがProgramを実行し、その中にinput命令があれば、スタックからUTXOを識別するprevoutをpopし、そのprevoutに対応するコントラクトを構築しスタックにプッシュし、トランザクションログにインプットエントリーを追加する。output命令があれば、スタックからPredicateおよびアイテムをpopしContractを作成し、アウトプットエントリーをトランザクションログに追加する。このようにして消費したUTXOと新しいアウトプットの情報はトランザクションの実行結果としてトランザクションアウトプットに記録される。そして最終的にトランザクションログを使ってブロックチェーンステートを更新する仕組みになっている。トランザクションの検証とトランザクションログによるブロックチェーンステートの更新を分離することで並列性を高めるみたい。

秘匿性

↑のZkVMはには、Program、スタック、トランザクションログ以外に、もう1つConstraint systemと呼ばれるものが含まれている。

ZkVMはゼロ知識証明システムの一種であるBulletproofs上に構築されている。コインの量を示す値やデータはデフォルトで暗号化され、Pedersen commitmentとして表現される。

f:id:techmedia-think:20190906184413p:plain
Constraint System

Programの実行中にさまざまな命令がConstraint systemに制約を追加し、VMがProgramの実行を完全に終えると、Bulletproofsプロトコルトランザクション内に格納された証明の文字列を使ってConstraint systemを検証する。トランザクションにセットする証明を作成するには、トランザクション作成者が最初にVMを証明モードで実行する。するとトランザクションのProgramは検証モードと同じ制約を作成するが、証明をチェックする代わりにConstraint systemを使って証明を生成するようになってるみたい。(この辺のBulletproofsを使った証明の仕組みはもっと掘り下げて別の記事で書きたい。)

Taprootの利用

トランザクションアウトプットに定義されるPredicateは公開鍵やサブプログラムで構成されると書いたが、この時Taprootのコミットメントスキームが利用される。コントラクトのアンロック条件をMASTで構成し、その条件セットをTaprootのスキームを利用して公開鍵にエンコードする。Taprootを利用することでロック時に全てのコントラクトロジックをが明らかになることを回避する。また、コントラクトの全参加者が協力する場合、アウトプットをアンロックする際にコントラクトの条件を公開することもない。

具体的なTaprootの仕組みについては以前書いた↓を参照。

techmedia-think.hatenablog.com

Utreexoを利用したUTXOセットの管理

ZkVMブロックチェーンは、UTXOセットの管理にUtreexoを使用する。現在のBitcoinはUTXOセットの全データをKVSで管理し、その容量は3〜4GBほどになるが、ZkVMブロックチェーンではUTXOセットをUtreexoというハッシュベースのアキュムレータを使って管理する。UtreexoではUTXOのセットで複数のマークルツリーを構成し、そのマークルルートのみを保存するため、UTXOセットの容量は数KBまで圧縮される。Uteexoの具体的な仕組みについては、以前書いた↓を参照。

techmedia-think.hatenablog.com

フルノードのストレージ負担を削減しつつIBDの支援を可能にするSecure Fountain Architecture(SeF)

Scaling Bitcoin 2019予習シリーズ第4弾は、カリフォルニア大学の「SeF: A Secure Fountain Architecture for Slashing Storage Costs of Blockchains」。ホワイトペーパーは↓

https://arxiv.org/pdf/1906.12140.pdf

ブロックチェーンのフルノードはジェネシスブロックから始まる全てのブロックチェーンのデータをスタンドアロンで検証し、保存する唯一のノードだ。一方ブロックチェーンはチェーンが伸びるに連れてデータも成長していく。フルノードの消費リソースとしては署名検証のためのCPUリソース、ネットワーク帯域、ストレージ容量などがあるが、今回フォーカスするのはこの内のストレージコストについて。現在Bitcoinブロックチェーンのデータは全体で215GBを超え、そのうちUTXOセットは3〜4GBになる。そしてブロックチェーンのデータは今後も線形増加していく。

このストレージコストを削減する方法として、現在とれる方法は以下の2つ。

  • 軽量ノード
    SPVノードとも呼ばれるが、ブロックチェーンのデータはダウンロードせずブロックヘッダーのみをダウンロードするタイプのノードで、信頼できるフルノードとの接続が必要になる。ダウンロードするデータ量が限定的であるため、スマートフォンやIoTデバイスなどの軽量デバイスでも動作する。トランザクションの有効性を個別検証することはできなかったり、フルノードに対するプライバシーの課題が残る。
  • プルーニングモード
    プルーニングモードはフルノードの動作モード1つで、フルノードと同様ジェネシスブロックから最新のブロックまで全てのブロックをスタンドアロンで検証する。ただ検証し終わった後の古いブロックは削除していくことで、消費ストレージを削減する。

(単純にブロックチェーンのデータの正しさの検証であればプルーニングモードでも可能だが)上記の2つの方法ではストレージコストを削減することはできても、Bitcoinネットワークに新たなノードが参加してきた際に、そのノードにジェネシスブロックから最新のブロックまで配信することはできない。そして、そのようなデータの提供が可能なフルノードの存在は、Bitcoinのような分散ネットワークを維持していくのに重要な役割を果たす。

SeFの提案では、フルノードのストレージコストを削減しつつも、プルーニングモードとは違い新しく参加するノードに対してブロックチェーンの復元をサポートできるような仕組みを提案するもの。

Secure Fountain Architecture

SeFのアプローチは、各ノードはブロックチェーンのデータを符号化し一部だけ保存することでストレージコストを節約する。新しくネットワークに参加したノードは、複数のノードからそれぞれ符号化されたブロックチェーンデータの一部を受け取り、それらを集めてデコードしブロックチェーンのデータを復元する。分散ストレージシステムにおいて、信頼性を低下することなく分散ストレージシステムのストレージコストを大幅に削減する方法として消失符号を利用したりするが、それと同じアプローチっぽい。つまり単一のノードが保持するデータ量は削減し、ネットワーク内の複数のノードからデータを集めることでブートストラップノードがブロックチェーンを復元できるようにする。

ちなみにRippleはHistory Shardingという仕組みで台帳履歴をネットワークで分散管理するようにしている。ただランダムサンプリングを採用しており、新しく参加したノードのブートストラップコストはSeFのアプローチに比べてかなり大きいみたい。

SeFがどのようにブロックをエンコードし、新しい参加ノードがどうやってそれをデコードしブロックチェーンを復元していくかみていこう。

用語

解説にあたって、SeFで扱う用語を整理↓

  • エポック
    ブロックチェーンがkブロック(kは調整パラメータで例えばk = 10000)成長するのに必要な時間の定義で、SeFにおいてブロックを符号化する処理の単位。
  • ドロップレット
    エポック単位にブロックチェーンのオリジナルのブロックをSeFにより符号化した符号化ブロック。
  • ドロップレットノード
    ストレージ制約のあるノードで、バケットノードに対してドロップレットを提供し、新規参加ノードがブロックチェーンを復元するのを支援を行う。
  • バケットノード
    ネットワークに新規参加するノードで、ドロップレットノードと繋いでブロックチェーンを復元する。ブロックチェーンの復元ができたら、ドロップレットノードになれる。

エンコーディング

各ドロップレットノードは、エポック単位でブロックチェーンがkブロック成長したら、そのk個のブロックをs個(例えばs = 10)のドロップレットに符号化する。

SeFでは噴水符号と呼ばれる消失符号をベースにしてブロックをドロップレットにエンコードする。ドロップレットから元のブロックを復号する場合は、元のブロックの数より僅かに多い数分ドロップレットを収集し、そのドロップレットをデコードすることでブロックの復元ができる。

SeFでは基礎的な噴水符号であるLT(Luby Transform)符号を使用し、以下の手順でk個ブロックからs個のドロップレットに符号化する。

f:id:techmedia-think:20190826094722p:plain
SeFエンコード処理

  1. 最初にランダムに1〜kの範囲内の数値を選択する。この数値をdとする。
  2. エポックで取り扱うk個のブロックの中からランダムにd個のブロックを選択する。
  3. 選択したd個のブロックについてビット単位のXORを取り、その結果をドロップレットとする。ドロップレットの計算に使われたd個のブロックのことをそのドロップレットに対するネイバーと呼ぶ。
  4. 続いて長さkのバイナリベクトルを生成し、m番目のブロックがこのドロップレットの計算に含まれている場合は1、そうでない場合は0をセットする。これがドロップレットに対するd個のブロックのインデックス情報となる。
  5. 4で計算したインデックス情報と一緒に3のドロップレットを保存する。

上記、エンコードが済むとs個の符号化されたブロックが生成され、それを保存し、元のブロックは削除し、エンコード処理は次のエポックへ進む。

このようにして符号化するとk個のブロックがs個のドロップレットになるので、ノードが保存するデータのサイズはs/kに削減できる。例えば k = 10,000でs = 10であれば、データサイズは1/1000になる。Bitcoinの場合、現在の215GBのデータが215MBになる。

reorgへの対応

reorgが発生するとブロックチェーンの一部のブロックが変わる可能性があるため、直近τブロック(例えばτ=550)のブロックはドロップレットにエンコードから除外され、そのまま保存する。

デコーディング

新たにネットワークに参加するバケットノードは、最初に十分な数のn個のドロップレットノードに接続し、 {1 ≦ l ≦ e}個のドロップレットを収集する( e = (t - τ) / kで、tは現在のブロック高)。またまだ符号化されていないブロックを入手するため、1つもしくはそれ以上のドロップレットノードからτ個分のブロックを別途ダウンロードする。

バケットノードはまず最初に最長チェーンのブロックヘッダーをダウンロードしておく(ドロップレットノードもブロックヘッダーは全て保持している)。続いて、各ドロップレットノードから収集したドロップレットを使って、以下の手順でブロックチェーンを復元する。

f:id:techmedia-think:20190826111647p:plain
2部グラフの例:k = 6、ns = 9 ドロップレットの初期2部グラフ

  1. k個のオリジナルのブロックと、収集したドロップレットを使って2部グラフを形成する。各ドロップレットは何番目のブロックがそのドロップレットの生成に関与したか示すインデックスを持っているので、各ドロップレットからブロックに対して接続するエッジがある。
  2. 2部グラフの中からブロックとドロップレットノードの接続が1つだけのブロックを見つける。このようなドロップレットをシングルトンと呼ぶ(↑の図では {C_4}がシングルトン)。
  3. 見つかったシングルトンのヘッダー*1が、最初にダウンロードしているブロックヘッダーと一致するか検証する。またシングルトンのペイロード(ヘッダーより後ろのデータ)からトランザクションのマークルツリーを構築し、そのルートがブロックヘッダーのマークルルートと一致するか検証する。
    • これが一致する場合、このドロップレットは対象のブロックをデコードできたことになる。
    • 一致しない場合、このドロップレットは濁っているのでこのドロップレットを2部グラフから削除する。
  4. 上記のグラフで言うと {C_4}により {B_3}がデコードされたので、 {B_3}と繋がっている他のドロップレット( {C_1, C_2, C_6, C_8})に対して、 {B_3}とbit単位のXORを取り各ドロップレットを更新する。そして、 {B_3}との接続を削除し、2部グラフを更新する。
  5. 全てのブロックがデコードされるまで、2〜4を繰り返す。

上記のようにデコード済みのブロックのデータを各ドロップレットから除去するということを繰り返すことで、新たなシングルトンができそれによりブロックがデコードできるというプロセスを繰り返すことになる。

ちなみに収集したドロップレットに、シングルトンが存在しない場合、ノードは追加のドロップレットノードに接続し、シングルトンが見つかるまでドロップレットを収集する。

上記のデコードを繰り返すことで、ネットワークに新しく参加したノードは、ストレージ制約のあるノードから収集したドロップレットを使ってブロックチェーンのデータを復元できる。

悪意あるノードによるドロップレットの細工

中には悪意のあるノードがいて、正しくないドロップノードを提供するパターンも考えられる。SeFでは単純なLT符号に加えて、上記のデコード処理でシングルトンのヘッダーおよびペイロードと予めダウンロードされた正しいブロックヘッダーの値を比較することで、このような汚れたドロップレットを区別し、処理するように設計されている。

所感

以上が全ブロックチェーンのデータを保持せずストレージ負担を削減しながらも、他の新規参入ノードのブロックチェーンIBDを支援できるノード実装の提案。

  • ネットワークを維持するためには今のところ(既存の軽量クライアントやプルーニングノードでは提供できない)IBDを支援できるノードの存在は重要。
  • 今までそれは全てのデータを保持するフルノードしか無かったので、ネットワークを分散ストレージとみなして、データを再構成するアプローチも興味深い(XOR演算ベースのLT符号とか他にもいろいろ利用できそう)。

*1:(ドロップレットは複数のブロックをXORしたものなので、先頭80バイトはブロックヘッダー)

複数のPayment Channelのアトミックな更新を可能にする「Atomic Multi-Channel Update」

Scaling Bitcoin 2019予習シリーズ第三弾は、「Atomic Multi-Channel Updates with Constant Collateral in Bitcoin-Compatible Payment-Channel Networks」。ホワイトペーパーは↓

https://eprint.iacr.org/2019/583.pdf

著者の1人のPedro Moreno-Sanchezは、Scaling Bitcoin 2018でもMulti-Hop Locksの発表しており、Payment Channel Networkの興味深い新しい提案をよく発表している人物。

Payment Channel Networkの課題

Lightning Networkでも採用されている現在のPayment Channel Networkでは、支払いがアトミックに行われることを担保するため、2フェーズコミット型のプロトコルが採用されている。ここでいう2フェーズコミット型のプロトコルというのは、

  1. 最初に送信者から受信者への支払い経路の各チャネルで支払いの金額がロックされ
  2. 次に受信者から送信者への経路の順にシークレットの値を明らかにし支払いを受け入れる

プロセスが処理されるプロトコルで、必ず経路順に処理が進む。

このPayment Channel Networkには以下の2つの課題がある。

経路制限

現在のPayment Channel Networkでは送信者から受信者までPayment Channelが繋がっている送金経路を確保し、さらにその経路の方向に各チャネルが送金額分送金できるだけの十分なキャパシティがないと送金はできない。

担保

Payment Channel Networkを利用した支払いを行う場合、経路内のチャネルの数をn個、支払うコインの量をαとすると、支払いを実行する際少なくともn × αのコインをロックすることになる。支払いが完了するまで、コインはロックされ、ロックされているコインは他の支払いに使うことはできない。そのため、コインがロックされている時間というのは重要だ。

支払いは通常数秒で終わるのでロック期間も数秒であまり意識することはないが、支払いの際に応答不能になるなどオンチェーン上での紛争解決が必要になった場合、n × αのコインがn × △時間(△はオンチェーン上でのトランザクションの承認時間)ロックされることになる。この支払いの経路に沿ってロックされるコインを担保と表現する。

攻撃者にとってはコインを盗めるようなメリットは無いが、単なる嫌がらせとしてチャネル上のコインをロックする攻撃は実際に考えられる。攻撃者が送信者、受信者となり十分に長い経路の支払いを行い、受信者が2フェーズコミットの②を可能な限り遅らせることで、攻撃者自体はαコインロックするだけで、経路上のコインn × αのコインをスタックさせることができる。チャネル全体で見た場合、担保は支払いの経路が長いほど大きくなる。

Atomic Multi-Channel Update(AMCU)の提案

Atomic Multi-Channel Update(AMCU)では、経路の関係性がない複数のPayment Channelの状態をアトミックに更新することで、上記の経路制限を緩和し、支払いに伴う担保を経路の長さに関係なく一定となるよう削減する。

Atomic Multi-Channel Update プロトコル

既存のPayment Channel Networkのアトミック性

Payment Channel Networkを利用したマルチホップ決済の重要な特性はアトミック性で、経路内の全てのペイメントチャネルの残高が更新されるか(支払いが行われるか)、されないか(支払いが行われないか)だ。

Lightning Networkなどで採用されている現在のPayment Channel NetworkはHTLCを使って、このアトミック性を担保している。ペイメントチャネルを持つ2人のユーザー(アリスとボブ)は、

  • アリスはy=H(R)となるRが提供されるとxコインボブに支払う(Hはハッシュ関数、つまりyのプリイメージが分かればコインを支払う)。
  • タイムアウト時刻tを経過すると、アリスはロックしたxコインを取り戻す。

という内容のコントラクトにコインをロックする。

Payment Channel Networkの支払い経路内の各チャネルのユーザーは、同じyを使ってそれぞれのチャネルでコントラクトを形成し(2フェーズコミットのフェーズ1)、受信者がRを公開してコインを受け取りチャネルを更新する(2フェーズコミットのフェーズ2)。もし、タイムアウト時刻になってもRが公開されない場合は、チャネルを元の残高に更新する(この時相手が応答しない場合はトランザクションをブロードキャストしチェーン上で紛争解決する)。

AMCU プロトコル

AMCUプロトコルは、プロトコルの参加者が以下の4つのフェーズを実行する。これらのフェーズは全てオフチェーンで実行される。

また、AMCUではプロトコルの参加者は他の参加者を認識し、認証された機密メッセージを送信してプロトコルを進める(参加者間でTLSチャネルを確立して使用する)。さらに参加者の内、プロトコルフェーズの調整を支援するコーディネーターを参加者間で1人決める。決め方のルールは強制されないが、参加者のアドレスのリストを辞書順にソートして最初のユーザーをコーディネーターとするなどで簡単に決められる。このコーディネータはネットワーク内のメッセージ数を減らす役割を果たす(その他のセキュリティやプライバシー上の利点などは無い)。

各フェーズにおいて全参加者がコーディネーターにOKの返信をすると次のフェーズに進む。

具体的にAMCUのプロトコルにおける支払いの4フェーズ(セットアップ、ロック、消費、ファイナライズ)について説明する。AMCUでは支払いのアトミック性をHTLCとは別の方法で担保しており、そのコアとなる機能は3つ目の消費フェーズにある。

セットアップ

プロトコルに参加する参加者の各チャネルで利用可能なコインをロックする。ここでチャネル内の残高が必要以上にロックされると、担保が不必要に増大することにもなるので、ペイメントチャネルの残高を2つに分割し、2つのサブチャネルを作る。

  • 1つは、AMCUのプロトコルセッションで必要な量のコイン
  • もう1つは、残りのコインで自由に使用可能

A→B→C→D→Eの経路でAからEに5コイン送金する例で考える(AMCUの場合、経路は関係ないが経路にした場合も、担保が経路の長さによって大きくなることはなく一定になるので、担保を小さく保つという意味ではメリットがある)。この時、それぞれの中間者への手数料を1コインとする。つまりB, C, Dに手数料1コインずつ払うのでAが支払うコインは合計8コイン。

チャネルを開いているAとBは、AMCUセッションで使用するコインと残りのコインにアウトプットを分割する {Tx^{A}_{setup}}トランザクションを作成し、お互いに署名する。これを他のチャネルのユーザーも並行して進める。すると以下のような状態になる。

f:id:techmedia-think:20190821164912p:plain
セットアップフェーズ実行後

※ ユーザー間の数字はチャネルの総キャパシティで、初期状態は左側のユーザーが前残高を持っている状態とする。

セットアップフェーズが終わるとAMCUの送金に必要な額のアウトプットがそれぞれ分離できた状態(サブチャネルができた状態)になる。

ロック

AとBはセットアップフェーズで作成した {Tx^{A}_{setup}}のAMCU用のアウトプットをインプットとし、それをそのまま両者宛(AとBのマルチシグ)に送金するトランザクション {Tx^{A}_{lock}}を作成し、署名する。このトランザクションにはタイムロック {T_{△}}が設定されており、 {T_{△}}経過するまで、ブロックチェーンには追加できない。

これを他のユーザーも並行して進めると、ロックフェーズが終了すると以下のトランザクションをそれぞれ持つようになる。

f:id:techmedia-think:20190821183759p:plain
ロックフェーズ実行後

このロックフェーズが実行されることで、参加者の誰かが将来のフェーズの進行を邪魔しても、 {T_{△}}経過したら、各チャネルはフォールバック可能になる。

消費フェーズ

消費フェーズで実際にコインを送金するが、この時重要になるのがアトミック性で、AMCUの参加者全てがそれぞれの受信者にコインを送金する状態か、全員が送金しない状態かのいずれかになることが担保される必要がある。

AとBはAからBにコインを送金するための {Tx^{A}_{consume}}トランザクションを協力して作成する。このトランザクションのインプットは以下の2つ。

  •  {Tx_{enable}}トランザクションのアウトプットの1つでAとBのマルチシグにロックされた7.99コイン
  •  {Tx_{enable}}トランザクションのアウトプットの1つでAとBのフレッシュアドレスにロックされた0.01コイン

アウトプットはB宛に8コイン送る単一のアウトプット。このトランザクションの作成を他のユーザーも並行して進めると、消費フェーズが完了すると、それぞれ以下のトランザクションを持つようになる。

f:id:techmedia-think:20190821215839p:plain
消費フェーズ実行後

ここで、 {Tx_{enable}}はまだ作られていないトランザクションだ。そのため、このトランザクションをこの時点でブロードキャストしてもブロックチェーンに格納されることはない。この {Tx_{consume}} {Tx_{enable}}にアトミック性を担保する仕組みがある。

ファイナライズ

消費フェーズで全チャネルの消費トランザクションが作られたら、最後に {Tx_{enable}} {Tx_{disable}}を作成する。

まず {Tx_{enable}}トランザクションは、各チャネル毎に作られた {Tx_{setup}}のアウトプットをすべて集めてインプットとし、 {Tx_{consume}}のインプットとなる各チャネルごとに2つずつのアウトプットを持つ1つのトランザクションで、以下のようなトランザクションになる。

f:id:techmedia-think:20190821220420p:plain
ファイナライズフェーズ実行後

つまり、各ユーザーが消費フェーズで作成した送金トランザクションは、この1つの {Tx_{enable}}がチェーン上にあらわれて初めて有効になる。この方法により、直接的な関連のない複数のPayment Channelの更新のアトミック性を担保する。

ただ、このままだと {T_{△}}経過したら {Tx_{setup}}を参照する2つの有効かつ矛盾する内容の {Tx_{lock}} {Tx_{enable}}が有効になってしまう。このため、 {T_{△}}経過したら {Tx_{enable}}を無効化する {Tx_{disable}}を実際は {Tx_{enable}}より先に作成しタイムアウト {T_{△}}を設定して署名しておく。

残高の更新

上記ファイナライズフェーズまで終えると、各ユーザーはやろうと思えばオンチェーン上で {Tx_{setup}} ->  {Tx_{enable}} ->  {Tx_{consume}}を公開することで、送金の紛争解決ができる。

通常はオフチェーン決済を続けると思われるので、チャネルの残高を更新したコミットメントTxを作成し、合意すればAMCUプロトコルは終了する。

所感

経路とHTLCを使わずどうやってアトミックに複数のチャネルを更新するのかと思ったけど、なるほど確かにこういう構成を取ればアトミックにできそうで面白い。これは単純に既存のLNの担保を小さくするだけでなく、チャネルの残高の調整や、ペーパーにも書いてあるとおりクラウドファウンディング(募集額集まったら支払いし、集まらななかったら集金しない)のようなケースにも対応できそうで興味深い。

個人的には以下の点が疑問。

  • 消費フェーズでアウトプット2つ分けてフレッシュアドレスを必要とする理由がイマイチ分かっていない。これ1つじゃダメなのか?
  •  {Tx_{consume}}には両者が署名するには、その後のフェーズで作られる {Tx_{enable}}トランザクション識別子とインデックス情報が必要になるが、この計算方法はどうする?もしくは、まだBitcoinでは導入されてないけど、bip-anyprevoutとかが導入されるとOutPointは空のまま署名できるから、そういう意図なのか?その場合別のOutPoinに切り替えられないよう、アウトプットを2つに分割し、フレッシュアドレスを導入してる?

それにしても、Payment Channelまだまだ奥が深いなー。

ブラインドマージマーニング(BMM)の仕様を定義したBIP-301

ブラインドマージマーニング(BMM)の仕様がBIP-301として定義された↓

https://github.com/bitcoin/bips/blob/master/bip-0301.mediawiki

マージマイニングの仕組みについては、以下の記事が分かりやすい。

btcnews.jp

このBIPでは、サイドチェーンのマイナー(ブロックの候補を作る人、Simon)がメインチェーンのマイナーにどのようにサイドチェーンのブロックのマイニングリクエストを出すかと、↑の記事の最後に挙げられていた賄賂(報酬)をやりとりする方法について定義されている。

簡単に内容をまとめると、サイドチェーンのフルノード運営者(Simonと呼ぶ)はサイドチェーン上で新しいブロックの候補を作成し、そのブロックに対するブラインドマージマイニングのリクエスト(BMMリクエスト)をメインチェーンのマイナーに投げる。このBMMリクエストを投げる方法はオンチェーン/オフチェーンの2種類の方法がある。いずれの方法でも、サイドチェーンのマイニング情報が含まれるCritical Dataと呼ばれるデータ構造をメインチェーンのマイナーに渡し、メインチェーンのマイナーはブロックをマイニングする際に、そのデータへのコミットメントをコインベーストランザクションのアウトプットに挿入することで、マージマイニングを行う(BMM Accept)。

オンチェーンBMMリクエス

BMMリクエストをオンチェーントランザクションとして投げる方法。

このトランザクションはSegwitで導入された拡張シリアライゼーションフォーマットを使用して、Critical Dataをセットするトランザクションになる。Segwitトランザクションは署名をトランザクションのアウトプットの後にwitnessと呼ばれるデータ領域を設けてそこに移動するような拡張フォーマットを導入した。この時flagフィールドも導入され、flag = 1がSegwitトランザクションとされた。今回のBMMリクエストランザクションはflag = 2が使われ、この場合、アウトプットの後にCritical Dataがセットされる領域が確保されるようになる。

そしてこのトランザクションのアウトプット自体がマイナーにより回収可能なマージマイニングの報酬となるようだ。具体的にどのようなアウトプットスクリプトになるかはBIPには書かれてないが、参照実装を見る限り、anyone can spendなアウトプットになるっぽい。

このリクエストはあるサイドチェーンの特定のブロックかつメインチェーンのブロックを指定したCritical Dataが含まれるトランザクションになるので、対象のブロックに取り込まれなければ無効になる。

オフチェーンBMMリクエス

オンチェーンではなくオフチェーンでBMMリクエストを投げる場合は、Lightinng Networkを利用する。この場合サイドチェーンのマイナー(Simon)とメインチェーンのマイナー(Mary)はSimon→Maryへ報酬を支払うためのLNの経路を持っておく必要がある。Critical Dataの内容も少し異なる。LNのCommitment Transactionのアウトプットに以下のアウトプットを追加することで、オフチェーンBMMリクエストの機能をチャネルに追加する。

  • Critical Dataの要件に合うブロックが作成されればメインチェーンのマイナー(Mary)へ報酬を支払い、対象のブロックが作られなければタイムロック後サイドチェーンのマイナー(Simon)が資金を取り戻す

ただこれどういうスクリプトを構成するのか気になる(BIPには記載されてない)。

仕様以外で気になったのはデプロイに関して、BIP-9を使ったデプロイの日時が2020年01月15日開始と明記されている。Segwit以降ちゃんと日時が設定されたソフトフォークは初めてじゃないだろうか?ただ、現状まだtestnetへのデプロイもされていないし、Bitcoin Coreにマージもされていないので、計画通りにデプロイされるのかは不明。

CriticalDataや、拡張トランザクションフォーマット、コインベースに挿入するBMM Acceptのデータ構造などプロトコルの詳細についてはBIPの内容を参照。以下、BIPの意訳↓

概要

ブラインドマージマイニング(BMM)はオプションで(非対称サイドチェーンなどの)extension blockをマイニングする方法だ。BMMは任意のルールセットに対して、ブロックが有効であるという弱い保証を生成し、かつマイナーに実際にその任意の検証を行わせることなく実行される。

BMMは実際には2つ以上のチェーンにまたがるプロセスだ。ここではメインチェーンであるBitcoinへの変更について焦点をあてる。全体像の説明についてはこのポストを参照。

ここでの我々の目標は、メインチェーンのマイナーにサイドチェーンのブロックを見つける行為をトラストレスに「売る」ことができるようにすることだ。

動機

通常の「マージマイニング」(MM)では、マイナーはハッシュ処理を他のチェーンを保護するために再利用できる(Namecoinなど)。ただし、従来のMMには2つの欠点がある。

  1. マイナーは他のチェーンのフルノードを実行しなければならない。(これは彼らがMMするブロックが有効でない限り、自身に対して有効な支払いを作成しないためで、マイナーはまず有効なブロックを作って、次にそれをマージマイニングしなければならない。)
  2. マイナーへの報酬は通常のBTCメインチェーンではなく、他のチェーンで支払われる。例えばNamecoinをマージマイニングしているマイナーはNMCを稼ぐ(そして、電気料金の支払いのためのBTCを売る前に、BTCのためにNMCを売る必要があるだろう)。

BMMは両方の欠点を解決する。

仕様

※このドキュメントでは、メインチェーンのバージョンと対応するサイドチェーンのバージョンを区別するため、曖昧な単語(blockやnode、chainなど)の前にside:\*main:\*という表記を使用する。またサイドチェーンのフルノードを指すためにSimonを使用し、メインチェーンのマイナーを指すのにMaryを使用する。

BMMリクエス

サイドチェーンのブロックを見つけるための権利を購入するため、ユーザーはBMMリクエストをブロードキャストする。

このリクエストは2種類の形式を取ることができる。1つはLightning Networkを必要としないが、Immediate Expiration(以下参照)のための新しい要件を持つ。2つめの形式は、Lightning Network自体からImmediate Expirationを継承するが、追加の準備と異なるより大きなメッセージを必要とする。

どちらの形式でも、トランザクションが含まれているブロックのコインベース内で特定のCritical Dataをコミットする必要がある(BMM Accept参照)。Lightningではないオンチェーンバージョンについては、新しい拡張シリアライゼーショントランザクションタイプを作成した(segwitがwitness dataをどうハンドリングするかとよく似ている)。

Immediate Expiration ("Fill-or-Kill")

このトランザクションの相手方に対して特別な保証をしたい。具体的には、SimonがMaryに「支払い」をするのではなく、SimonがMaryに「オファー」を提供することを推奨する(Maryはこれを承諾または辞退できる)。

重要なことに、Simonはリアルタイムで(つまり迅速かつオフチェーンで)、複数の異なるMaryに安全に多数のオファーを出したい。ただし、最終的に1つのオファーしか受け入れられないようにする。言い換えると、Simonのオファーを直ちに期限切れにする必要がある。1つのオファーのみが本物のトランザクションになることができれば、Simonは一日中複数のオファーを簡単に作れるだろう。全てのSimonが多数のオファーを作るので、Maryはたくさんのオファーの中を選択するためのアクセスを得る。

オンチェーンBMMリクエス

オンチェーンBMMRはLightning Networkを必要としないが、検証のための新しい要件がある。

構造

以下のデータが必要になる。

32-bytes  - h* sideHeaderHash
?~?-bytes - critical data extended serialization
    3-bytes - 0x00bf00 identifying bytes
    1-byte  - nSidechain
    2-bytes - prevSideBlockRef
    4-bytes - prevMainHeaderBytes

sideHeaderHashは、side:chainから来る(side:nodesがside:blocks/headersをビルドする)。identifying bytesは0x00bf00。nSidechainはどのサイドチェーンをBMMするか識別するもので、BMMが行われる頃には世界的に知られるようになる。

prevBlockRefは少し複雑で次のセクションで説明する。

ブロックに含めるための資格を得るため、BMMリクエストには以下の要件が適用される:

  1. リクエストは対応する「BMM Accept」(後述)と一致しなければならない。
  2. 多くてもmain:blockにはサイドチェーンあたり1つのリクエストしか許可されない。言い換えると、700人がサイドチェーン#4のBMMリクエストをブロードキャストしても、main:minerはブロックに含めるリクエストを1つだけ選択しなければならない。
  3. 4バイトのprevMainHeaderBytesは、前のmain:blockheaderの最後4バイトと一致しなければならない。しがたって、Simonのtxnsはそれが知るブロック履歴の中で(そして現在のサイドチェーンの履歴の中で)、現在のブロックに対してのみ有効となる。
prevBlockRef

prevBlockRefは、現在のside:blockの親ブロックを見つけるためにside:chainで行わなければならない「スキップ」の数を数える整数だ。サイドチェーンが再編成されている場合(もしくは無効なサイドチェーンブロックをスキップしている場合)を除いて、この値はゼロだ。side:nodeが直近Nブロックをオーファンさせたい場合、現在のブロックのこの値はNと等しくなり、その後のブロックでゼロに戻る。

https://github.com/bitcoin/bips/raw/master/bip-0301/bmm-dots-examples.png?raw=true

上記は、(小さい数字で記載されている)最大長、再編成の履歴、prevBlockRefの数が異なる3つのブロックチェーン。各side:blockの「prevSideBlockRef」で与えられる順序付けは、各side:blockの「prevSideHeaderHash」の順序と同型になる(prevSideHeaderHashはサイドチェーンにおけるメインチェーンのprevBlockHashと同等)。一方から他方へ自由に変換できる。

拡張されたシリアライゼーション

トランザクションレベルで新しい要件を課すために、Segwitスタイルのトランザクションからダミーのvinおよびflagトリックを借用する。サイドチェーンのCritical Dataトランザクションの要件の全てが、そのトランザクションが含まれるブロックで満たされない限り、そのトランザクションは無効だ。Segwitにおけるこの追加データはSegwitの署名スタックであり、追加要件は署名の場所と有効性だ。サイドチェーンのBMM Critical Dataトランザクションでは、追加データは(nSidechain, h*) のペアで、上記の最初の2つの要件と上記のmain:blocknumberの3つめの要件を満たす必要がある。

https://github.com/bitcoin/bips/raw/master/bip-0301/witness-vs-critical.png?raw=true

これらのトランザクションタイプは僅かに異なるmempoolの振る舞いをするため、2つめのmempoolに保存する必要がある。これらのtxnsが受信され、すぐにチェックされ、有効であればブロックに入れるかどうかチェックされる。それらが要求した特定のブロックに含まれない場合(要求したブロック高よりチェーンTipの方が長い場合)、それらは破棄される。実際、main:blockが見つかった後は、次のブロック高のための新しい支払いがすぐに作成されるため、2つ目のmempool内のすべては破棄される。(これはブロックチェーンが再編成される場合も同じだ)このようなmempool内のtxnsの再評価はこれまでなく、一度評価されてブロックに含まれるか破棄されるかのどちらかだ。再スキャンする必要はない。

おもしろいことに、これらの支払いは常にnon-main:minersからmain:minersに向けられる。したがって、非マイニングフルノードはそれらをmempoolに保持する必要はまったくない。非マイナーノードは、ブロックが見つかるのを待ってからtxnをチェックするだけだ。これらの取引は株式市場のピットトレードオファーによく似ている(対照的にBitcoinの通常の取引は紙の小切手のようなものだ)。

Lightning BMMリクエス

Lightning BMMRでは、SimonsがMarysとのLNのチャネル経路を開く必要がある。特に今日、これは常に実用的であるとは限らない。

LN txnsはprevSideBlockRefを利用できない。なぜならそれらがいつオンチェーンにブロードキャストされるか誰にも分からないからだ。代わりにprevSideBlockHashを使用しなければならない。それ以外は同じデータを必要とする:

4-bytes - Message header (0xD0520C6E)   
1-byte - sidechain number
32-bytes  - h* side:block hash  
32-bytes  - prevSideBlockHash   

オンチェーンのBMMRでは、main:block毎およびサイドチェーン毎に1つのBMMRだけ含めることができたので、Simonが必要なもの全てで同じh*を再利用できることに注意すること。ただし、LNではそのようなルールを適用できない。目標が、zero txnを含む全てをオフチェーンに入れるためだ。そのため、我々はそのリクエストが何であったのか、或いは何に影響を与えたのか知ることはない。

そのためSimonは、各Maryに異なるh*を与えることを保証しなければならない。Simonはこれをside:blockのブロックの内容を制御し、単純にside:nonceをインクリメントすることで簡単に行える。これはside:blockを変更し、そのハッシュを変更する(つまりh*を変更する)。

Mary毎に(より正確にはチャネル毎に)一意のh*を使用し、(サイドチェーン毎に)最大1 h*をブロックにすることで、Simonは最大1回だけ課金されることを保証できる。

おそらく混乱しているので、ここで例を示す。Simonは13 BTCから始まりMaryは40 BTCから始まる。side:block'のtx-feeは合計で現在7.1 BTCで、Simonは自身で0.1 BTC保持し、Maryに7 BTC支払っている。

まず、開始時点Ⅰでは、

Simon 13 , Mary 40 で合計 53
     [Maryによって署名された]Simonのバージョン
        13 ; TimeLockが経過したらSimonへ、もしくはSimonの署名があればMaryへ
        40 ; をMaryへ
     [Simonによって署名された]Maryのバージョン
        40 ; TimeLockが経過したらMaryへ、もしくはMaryの署名があればSimonへ
        13 ; をSimonへ

続いて両者ともⅡに進む

Simon 13 , Mary 40 で合計 53
    [Maryによって署名された]Simonのバージョン
        6 ; TimeLockが経過したらSimonへ、もしくはSimonの署名があればMaryへ
        40 ; をMaryへ
        7 ; critical dataの要件に合えばMaryへ、そうでなければLongTimeLock後Simonへ
    [Simonによって署名された]Maryのバージョン
        40 ; TimeLockが経過したらMaryへ、もしくはMaryの署名があればSimonへ
        6 ; をSimonへ
        7 ; critical dataの要件に合えばMaryへ、そうでなければLongTimeLock後Simonへ

ここから、問題のh* side:blockがマージマイニングされると、両者はⅢに進む。

Simon 13 , Mary 40 で合計 53
    [Maryによって署名された]Simonのバージョン
        6 ; TimeLockが経過したらSimonへ、もしくはSimonの署名があればMaryへ
        47 ; をMaryへ
    [Simonによって署名された]Maryのバージョン
        47 ; TimeLockが経過したらMaryへ、もしくはMaryの署名があればSimonへ
        6 ; をSimonへ

Simonがすぐに処理するなら、彼はこのside:block上に構築されているブロックを気にするMaryのインセンティブを取り除く。Simonのside:block がオーファンすると彼は7 BTCを失う。Simonはそれを安全に実施でき、先に進む前に(つまり上記ⅢのLN txnに進む前に)100 side:block待つか、問題ないと思えばリスクを冒すこともできる。

h* side:blockが見つからない場合、ⅡとⅢは互いに等価だ。SimonとMaryは協力してⅠを再構築してそこに戻ることもできるし、新しいバージョンのⅡに進むこともできる(異なるh*を使って、次のmain:blockで新しいside:blockを再試行する)。

BMM Accept

main:minerが受け入れる各BMMリクエストに対して、main:minerらはmain:coinbase txnにOP_RETURNアウトプットを入れなければならない。(複数のOP_RETURNを許可するようTxのヒョジュんポリシーを変更)

受け入れ用のOP_RETURNアウトプットには以下のデータが必要になる:

1-byte - OP_RETURN (0x6a)
1-byte - Push the following 36 bytes (0x24)
4-bytes - Message header (0xD3407053)
32-bytes - h*
~5-bytes - BMM identifier bytes

このOP_RETRUNアウトプットがない場合、BMMリクエストは受け入れられていない。(そして受け入れられなければ、 main:blockに含めることは出来ない)

後方互換

このBIPは「blindmm」という名前でbit 4を使ってBIP9の「version bits」を使って展開される。

// Deployment of Drivechains (BIPX, BIPY)
consensus.vDeployments[Consensus::DEPLOYMENT_DRIVECHAINS].bit = 4;
consensus.vDeployments[Consensus::DEPLOYMENT_DRIVECHAINS].nStartTime = 1579072881; // 2020年01月15日
consensus.vDeployments[Consensus::DEPLOYMENT_DRIVECHAINS].nTimeout = 1610695281; // 2021年01月15日

参照実装

https://github.com/DriveNetTESTDRIVE/DriveNet

Bitcoin Coreをフォークしたメインチェーンに必要な変更についてはこちら: https://github.com/drivechain-project/bitcoin/tree/sidechainBMM

LNの支払い経路の計算をアウトソースするトランポリンペイメント

Scaling Bitcoin 2019予習シリーズ第二弾は、「Improving routing in the Lightning Network with Trampoline Payments」について。

特にホワイトペーパーが出てる訳でも無いので、確かな内容は分からないんだけど、おそらく現在BOLTにプルリクが出されている↓の内容じゃないかと思われる(提案者もACINQのBastien Teinturierだし)。

github.com

トランポリンペイメントとは?

Lightning Networkをで支払いをする場合、現在は送信者が自身のノードから受信者のノードまでの経路を計算している。この経路を計算するためには、Lightning Networkのノードネットワークの最新情報を保持しなければならない。Lightning Networkが現在の規模のままであれば問題ないかもしれないが、Lightning Networkの採用が進み、チャネル数が数百万などに拡大するとどうなるだろう?経路計算するノードは、より多くのメモリ、ネットワーク帯域および計算能力が必要になる。特にスマートフォンやIoTデバイスのようなリソースが制限されたデバイスにとっては、そのハードルは高くなる。

トランポリンペイメントというのは、そういうリソースが制限されたデバイスのために、経路計算をトラストレスにアウトソースするための提案。例えば以下のようなAからFまでのチャネルグラフがあるとする。

A → B → C → D → E → F

ノードAはFにLNで決済をしたいけど,軽量クライアントで近隣のノード情報しか保持したいため、Cがどこにあるかは知っているけど、Fがどこにあるかは知らない。でもCはネットワーク全体の最新状態を維持するトランポリンノードであった場合、AはCに支払いを送信すると、Cがそこから先のFまでの効率的な経路計算し、支払いをしてくれるという仕組み。

また、通常のLN決済で使われるオニオンルーティングの場合、中継ノードは中継する前後のノードに関する情報は知っているけど、送信者、受信者の情報は分からないというプライバシー特性があるが、このトランポリンペイメントについても同様に、トランポリンノードに送信者の情報および受信者の情報が分からないような仕組みにする必要がある。

トランポリンペイメントの仕組み

では、具体的にどのようにしてトレストレスにトランポリンペイメントがワークするのか見ていこう。

ネットワークビュー

まず、トランポリンノードと制限デバイスのネットワーク形成について。

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

トランポリンノード

ネットワーク・トポロジーの全体像を把握し、トランポリンペイメントをサポートするトランポリンノードは、option_trampoline_routingのサポートを広告する必要がある。

また、トランポリンペイメントペイメントを中継する意思があるトランポリンノードとなるノードはnode_updateという新しいメッセージを送信する。このメッセージはchannel_updateメッセージと同じ方法で中継される。トランポリンペイメントを中継する意思がないノードは、このnode_updateメッセージを送信しない。

制限デバイスはこのnode_updateによってトランポリンノードを識別する。

制限デバイス

制限ノードは上記のnode_updateを監視することで、トランポリンペイメントを行う際に送信先となるトランポリンノードの情報を得ることができる。これらのノード情報を保存し、どのトランポリンノードを使用するかは自由に選択できる。

単純にnode_updateを受信するだけでは、帯域幅の削減にはならないので、ゴシップメッセージのフィルタリングをする必要がある。制限デバイスoption_gossip_filtersをサポートするリモートノードとのみ接続する。そうすると、リモートノードはフィルタにパスしたメッセージのみを制限デバイスに送るようになる。現在定義されているのは以下の2つのフィルタ。

  • channel_update_filter
    制限デバイスは、リモートノードにchannel_update_filterを送信する。するとリモートノードは制限デバイスchannel_updateを送信する前にこのフィルタにかけ、フィルタをパスしたchannel_updateメッセージのみを制限デバイスに転送する。フィルタの中身は単純に距離数で、リモートノードから指定した最大ホップ数分離れているchannel_updateメッセージのみ受け入れ、それ以上離れているノードのchannel_updateは受け取らない。こうすることで、制限デバイスは近隣のノード情報のみを保持するノードとなる。
  • node_update_filter
    制限デバイスは、リモートノードにnode_update_filterを送信する。するとリモートノードは制限デバイスnode_updateを送信する前にこのフィルタにかけ、フィルタをパスしたnode_updateメッセージのみを制限デバイスに転送する。これによりトランポリンノードのリストを定期的に更新し、その手数料率とcltvを最新の状態に保つ。node_update_filterは、node_idsha256(local_node_id || latest_block_hash)の2つの値の距離に基づいたフィルタになっている。

制限デバイスは、node_updateとフィルタを利用して、ストレージコストとネットワークの帯域幅を削減する。

トランポリンノードを利用したトランポリンペイメント

アリス→ボブのLN支払いをしたく、両者がトランポリンペイメントをサポートする場合は、以下のように決済するようになる。

① ボブはまずインボイスを作成するが、この時ボブの近隣のトランポリンノード(TB1, TB2, TB3)を3つ記載する。

② アリスは近隣のトランポリンノードTA1を選択し、もう1つ異なる(近隣でなくてもいい)TA2を選択。そして最後のインボイスの中から1つのトランポリンノードTB3を選択したとする(アリスはインボイスに掲載されていたトランポリンノードを選択してもいいし、選択しなくてもいい)。この段階でトランポリンノードの経路はアリス -> TA1 -> TA2 -> TB3 -> ボブとなる。

③ 続いてアリスは、この経路で支払いをするため、トランポリンペイメントのために新しく導入されるtrampoline_onion_packetを作成する。trampoline_onion_packetは固定サイズのTLVパケットで、以下の構造を持つ。

  1. タイプ:trampoline_onion_packet
  2. データ:
    • [1 : 0x08] (type)
    • [3 : 0xfde202] (length)
    • [1 : version]
    • [33 : public_key]
    • [672 : tranpoline_hops_data]
    • [32 : hmac]

この中のtranpoline_hops_dataフィールドが、次のトランポリンホップのアドレスや転送情報およびそれに関連するHMACの難読化データで構成されるtrampoline_hop_payloadのリストになる。そのため、ここでは、TA1、TA2、TB3、ボブの3つのtrampoline_hop_payloadをセットすることになる。ここで最後のボブはトランポリンノードではないが、TB3にボブが受信者であることを特定されないよう、TB3にとっては次のトランポリンホップであると認識させる(このあたりは従来のLNのオニオンルーティングの仕組みを踏襲している)。そのため、trampoline_onion_packetもトランポリンノードが使用されるトランポリンノードの数を推測できないように固定サイズになっている。

④ 続いてアリスは、最初のトランポリンノードTA1までの経路を計算する。この経路をアリス -> H1 -> H2 -> TA1とする。

⑤続いて、アリスは④の経路の支払いをするため、onion_packetを作成する。このonion_packethop_payloadはH1、H2、TA1用の3つのペイロードをセットする。最後のTA1用のhop_payloadには③で作成したtrampoline_onion_packetがセットされる。※ ただ、H2はその内容がtrampoline_onion_packetであることを知ること無く、他のノードと同様onion_packetとして転送する。

⑥ H1, H2を経由してTA1は受信したonion_packetからtrampoline_onion_packetを見つけ、トランポリンペイメントであることを認識する。そしてtrampoline_onion_packetから次のトランポリンホップTA2への経路を計算する。この経路をTA1 -> H3 -> H4 -> TA2とする。

⑦ TA1はTA2までの支払いを転送するonion_packetを作成する。このonion_packethop_payloadはH1、H2、TA1用の3つのペイロードをセットする。最後のTA2用のhop_payloadにはTA1が受信したtrampoline_onion_packetのTA1分を剥がしたものがセットされる。

⑧ H3, H4を経由してTA2はonion_packetからtrampoline_onion_packetを見つけ、トランポリンペイメントであることを認識する。そしてtrampoline_onion_packetから次のトランポリンホップがTB3であることを認識する。ここでTA2とTB3は直接チャネルを開いているので、T3へ支払いを転送するonion_packetを作成する。このonion_packethop_payloadには受信したtrampoline_onion_packetからTA2分の層が剥がされたものがセットされる。※ TA2→TB3間は直接チャネルを開いているのでルーティングコストはかからない。

⑨ TB3は受信したonion_packetからtrampoline_onion_packetを見つけ、トランポリンペイメントであることを認識する。そしてtrampoline_onion_packetから次のトランポリンホップボブへの経路を計算する。この経路をTB3 -> H5 -> ボブとする。最後のボブ用のhop_payloadにはTB3が受信したtrampoline_onion_packetのTB3分を剥がしたものがセットされる。 ※この時TB3はボブが受信者であることが分からないので、あくまで次のトランポリンホップであると認識している。

⑩ H5を経由してボブは、onion_packetからtrampoline_onion_packetを見つけ、トランポリンペイメントであることを認識する。但し、hmacの値が0x00...00なので自身が受信者であると認識する。また、最後の

以上のように、制限デバイスはトランポリンノードに途中の経路計算を代替させ、送信者、受信者の情報を従来のLNの支払いと変わらない匿名性を持って支払いを行う。図にまとめると↓のような感じ。

f:id:techmedia-think:20190731134818p:plain
トランポリンペイメントフロー

受信者がトランポリンペイメントをサポートしないケース

上記はアリスとボブ両者がトランポリンペイメントをサポートしているケースだったけど、ボブがトランポリンペイメントをサポートしない場合も途中までトランポリンペイメントを利用することができる。

アリスがtrampoline_hop_payloadのリストを構成する際、最後のトランポリンノード(ボブではない)のtrampoline_hop_payloadにだけ以下のrecipient_infoTLVパケットを含めるようにする。

  1. タイプ:recipient_info
  2. データ:
    • [1 : 0x0a] (type)
    • [1 : 0x01] (length)
    • [1 : 0x00] (option_trampoline_routing)

受信したパケットに↑が含まれていた場合、そのトランポリンノードは自身が最後のトランポリンノードであると認識し、最後のホップを標準のオニオン支払いに変換して送信してもらう。

こうすることでボブがトランポリンペイメントをサポートしていなくても送金できるが、この場合、最後のトランポリンノードに受信者の身元と送金額が分かってしまうというプライベー上のデメリットが発生する。プライバシーを確保したい場合は、トランポリンペイメントをサポートするか、受信者が身元を隠すために別途ランデブールーティングなど利用する他ない。

以上が、現在提案されているトランポリンペイメントの仕組み。まだ提案中の仕様なので、今後変更される可能性はある。

※ トランポリンペイメントで新しく導入されるメッセージの具体的なデータ構造についてはプルリクの提案内容を参照。↓は参考までに、プルリクを和訳したもの(こういう機能単位のドキュメントがあるのいいよね。BOLTの各章に分散して書かれると機能の全体像が掴みづらいので、こういうBIPみたいな方が機能を把握しやすい)。

トランポリンペイメントの提案の和訳 · GitHub