Scaling Bitcoin 2019予習シリーズ第二弾は、「Improving routing in the Lightning Network with Trampoline Payments」について。
特にホワイトペーパーが出てる訳でも無いので、確かな内容は分からないんだけど、おそらく現在BOLTにプルリクが出されている↓の内容じゃないかと思われる(提案者もACINQのBastien Teinturierだし)。
トランポリンペイメントとは?
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決済で使われるオニオンルーティングの場合、中継ノードは中継する前後のノードに関する情報は知っているけど、送信者、受信者の情報は分からないというプライバシー特性があるが、このトランポリンペイメントについても同様に、トランポリンノードに送信者の情報および受信者の情報が分からないような仕組みにする必要がある。
トランポリンペイメントの仕組み
では、具体的にどのようにしてトレストレスにトランポリンペイメントがワークするのか見ていこう。
ネットワークビュー
まず、トランポリンノードと制限デバイスのネットワーク形成について。
トランポリンノード
ネットワーク・トポロジーの全体像を把握し、トランポリンペイメントをサポートするトランポリンノードは、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_id
とsha256(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パケットで、以下の構造を持つ。
- タイプ:
trampoline_onion_packet
- データ:
- [
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_packet
のhop_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_packet
のhop_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_packet
のhop_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の支払いと変わらない匿名性を持って支払いを行う。図にまとめると↓のような感じ。
受信者がトランポリンペイメントをサポートしないケース
上記はアリスとボブ両者がトランポリンペイメントをサポートしているケースだったけど、ボブがトランポリンペイメントをサポートしない場合も途中までトランポリンペイメントを利用することができる。
アリスがtrampoline_hop_payload
のリストを構成する際、最後のトランポリンノード(ボブではない)のtrampoline_hop_payload
にだけ以下のrecipient_info
TLVパケットを含めるようにする。
- タイプ:
recipient_info
- データ:
- [
1
:0x0a
] (type
) - [
1
:0x01
] (length
) - [
1
:0x00
] (option_trampoline_routing
)
- [
受信したパケットに↑が含まれていた場合、そのトランポリンノードは自身が最後のトランポリンノードであると認識し、最後のホップを標準のオニオン支払いに変換して送信してもらう。
こうすることでボブがトランポリンペイメントをサポートしていなくても送金できるが、この場合、最後のトランポリンノードに受信者の身元と送金額が分かってしまうというプライベー上のデメリットが発生する。プライバシーを確保したい場合は、トランポリンペイメントをサポートするか、受信者が身元を隠すために別途ランデブールーティングなど利用する他ない。
以上が、現在提案されているトランポリンペイメントの仕組み。まだ提案中の仕様なので、今後変更される可能性はある。
※ トランポリンペイメントで新しく導入されるメッセージの具体的なデータ構造についてはプルリクの提案内容を参照。↓は参考までに、プルリクを和訳したもの(こういう機能単位のドキュメントがあるのいいよね。BOLTの各章に分散して書かれると機能の全体像が掴みづらいので、こういうBIPみたいな方が機能を把握しやすい)。