Lightning Networkの送金処理で使用されている技術【後編】
本連載も、今回で最終回となります。今回は、前回に引き続きLightning Network BOLTの送金処理について解説します。
なお、今回の解説は2018年8月上旬の仕様を元にしています。
送金できる条件
送金を開始するために必要な情報として、以下の2つがあります。
- 送金する経路
- 請求書(invoice)
「送金する経路」は、送金するLNノードから始まり、着金先のLNノードに行き着くまでに、どのチャネルを使えば良いかという情報です。
下図では経路が1つしか存在しませんが、実際はLNノードが網の目のようにつながっているため、その中からいずれかを選ぶことになります。
「請求書(invoice)」は着金先から直接もらいます。
invoiceはBOLT11形式になっており、以下の情報を格納することが可能です。
項目名 |
---|
payment_hash |
支払いの目的(文字列、SHA256) |
支払先のnode_id |
invoiceの有効期限 |
min_final_cltv_expiry |
fallback on-chain address |
extra routing information for a private route |
「支払先のnode_id」は必須ではなく、通常はinvoiceの署名から復元させます。
「extra routing information for a private route」は、invoiceを作成するLNノードが非公開のチャネルを持っている場合に追加できます。そうすることで、LNノードを非公開にしたまま着金を受け付けることができるようになります。
データは支払先のLNノードが署名した後、Bech32という形式でエンコードします(native Segwitのアドレス形式もBech32ですが、互換性はありません)。
経路情報
Lightning Networkは送金を転送できます。どのような経路で送金するかは支払い元が決定し、中継するLNノードは指示通りに転送します。経路情報および暗号化についてはBOLT4にて説明されています。
経路情報の取得
送金経路は送金元が決定するため、少なくとも自分のLNノードから送金先のLNノードまでのチャネルを知っていなくてはなりません。そのために、チャネル情報を全体に展開する仕組みがあります。
チャネルにはpublicとprivateがあります。publicなチャネルは全体に情報を展開するため、送金経路として使用することができます。一方でprivateなチャネルは送金や着金するときだけ使用し、全体には展開しません。
LNノード間でチャネルをオープンし、両ノードが送金したトランザクション(funding transaction)が6confirmation以上経過し、両ノードがそれぞれ展開するチャネル情報に署名をすると、そのチャネル情報をLightning Networkメッセージで展開できるようになります。
チャネル情報のメッセージを受信したLNノードは、その情報を自分が接続しているLNノードに送信します。それを受信したLNノードが別のLNノードに展開し……という動作を繰り返して、いつかは全体にチャネル情報が行き渡るようになっています。
全体にチャネル情報が行き渡るのは良いのですが、既に別のLNノードから受信しているチャネル情報をもらうこともあり無駄が多いです。そこで、2018年7月末にチャネルを問い合わせるLightning Networkメッセージ仕様(gossip query)が新しく追加されました。
送金経路の作成
送金経路は、自分が持っているチャネル情報から作成します。
BOLTの仕様により20回まで中継して送金させることができるため、その範囲で自分から送金先の経路を作ります。経路の作り方はBOLT仕様にはありませんが、推奨する方法としては、手数料とタイムアウトの両方を考慮しましょう、ということになっています。
通常は、最初に一番良い経路で送金を試し、ダメだったら2番目、3番目……とリトライしていき、考えられる経路がすべてダメだった場合に「送金失敗」と判断することになると思います。
Onionルーティングプロトコル
送金にはLightning Networkメッセージを使うため、データ自体は符号化されているものの、チャネルの相手はデコードして読むことができます。その際、誰から誰に送金するのかという情報が全部見えてしまうのは好まれませんし、転送するノードによって改ざんされてしまうのも困ります。そのため、送金経路情報はOnionルーティングプロトコルにより、さらに符号化されています(Sphinx(PDF)という方式を拡張しているとのこと)。
この方式で符号化すると、データの一部だけが復号できます。送金の場合は、次の送金先に関する情報だけが復号できます。
送金を開始するLightning Networkメッセージはupdate_add_htlc
という名前ですが、そのパラメータに「相手への送金情報(非onion)」と「相手が復号すると次の転送先への送金情報がわかるデータ(onion)」を付けて送ります。そうすることで、update_add_htlc
を受信した人は転送先だけの送金情報しかわからないことになります。最終的な送金先がonionデータを復号すると、次の送金先がないため自分への送金であることがわかります。
送金
送金は、HTLCの追加と反映の2段階に分かれます。preimage
が展開されればBitcoinが手に入るという状況を先に作り、その状態が確定したらpreimage
を展開します。preimage
の展開は、Lightning Networkメッセージで行うか、ブロックチェーンに展開するかのいずれかになります。
2者間の送金:HTLCの追加
それでは、2者間の送金を見ていきましょう。
まず、HTLCの追加までのLNノード間通信です。
送金者(payer)が送金先(payee)からinvoiceをもらうところはLightning Networkメッセージではなく、それ以外のルートで行っています(前回の記事参照)。update_add_htlc
~commitment_signed
~revoke_and_ack
によりpayeeにHTLCが追加され、次のcommitment_signed
~revoke_and_ack
でpayerにHTLCが追加されます。
update_add_htlc
では、相手への送金額と、それ以降のonionルーティング情報を渡します。ここでは直接相手に送金しているだけなので、解読すると自分が最後の送金先だということがわかります。
commitment_signed
は、HTLCが追加された後の相手のcommitment transaction(後述)への署名を渡します。そうすることで、相手は自分と相手の署名がそろい、funding transactionを使用するトランザクションをブロックチェーンに展開する準備ができます。この状態でcommitment transactionを展開(unilateral close)すると、payerがpreimage
をブロックチェーンに展開することでpayerは送金されたBitcoinを入手できます(その代わり、使用できるようになるまでは時間がかかる)。
revoke_and_ack
は、相手から受信したcommitment_signed
が間違っていなかったため受け取ったという意味と、今まで使っていたcommitment transactionを破棄するという意味で古いper_commitment_secret
を渡します。
ここまででようやくHTLCの追加が完了し、preimage
待ちとなります。
2者間の送金:HTLCの反映
続いて、HTLCの反映です。
update_fulfill_htlc
~commitment_signed
~revoke_and_ack
によりpayerのHTLCが反映され、次のcommitment_signed
~revoke_and_ack
でpayeeにHTLCが反映されます。update_fulfill_htlc
は、preimage
を渡すLightning Networkメッセージです。一致したHTLCのBitcoinを反映してお互いのチャネルに持つBitcoin量を変更し、HTLCを削除します。
データの交換が一度に行えればもっと簡単なのですが、ネットワーク経由で行う以上はどちらかが先に渡す必要があり、それにより相手だけが得をすることがないようにするため手順が分かれています。
3者間の送金
転送するLNノードが間に入った場合は、次図のようになります。
先にpayerからpeyeeまでの間にHTLCを追加した状態を作り、payeeまで届いたらpreimage
を折り返します。
もし、転送のどこかでHTLCの追加に失敗した場合はupdate_fulfill_htlc
の代わりにupdate_fail_htlc
という失敗用のメッセージを返し、HTLCを削除します。
トランザクション
Lightning Networkで使用するトランザクションについて説明します。
なお、使用するトランザクションのバージョンは2で、すべてnative segwitです。詳細はBOLT3やBOLT5に記載されています。
funding transaction
チャネルをオープンする際に使用するトランザクションです。
仕様としては、送金先がお互いのMultiSigになっていること、というくらいしか決められていません。
送金を行うのは、作りたいLNノード(funder)です。そのため、通常のオープン直後はfunderからしか送金できませんが、Lightning Networkメッセージとしては予め相手に使用できるBitcoin量を割り当てることもできます(当然、自分が送金できるBitcoin量は減ります)。
それ以降、送金する場合にはcommitment transactionを、同意してチャネルをクローズする場合にはclosing transactionを作成して署名を交換しますが、そのINPUTになるのはfunding transactionです。
チャネルをオープンする際はcommitment transactionを作成することで、チャネルをオープンし終わる前に相手がいなくなったとしてもfunding transactionに入っているBitcoin量を取り戻せるようにします。
commitment transaction
送金する際、相手が途中で不在になったり異常が起きたりしてチャネルを継続できなくなった場合に、ブロックチェーンに展開するトランザクションです。commitment_signed
メッセージで相手のcommitment transactionに署名しますが、Lightning Networkメッセージで直接トランザクションを交換することはありません。その代わり、BOLT仕様により作成するトランザクションの構造が決められており、それに従ってトランザクションを作成し、署名を行うようになっています。
少しでもトランザクションの作り方を間違えると署名の検証に失敗するため、すぐにわかります。そういうことはあってはならないため、検証に失敗する署名を受信した場合はすぐにチャネルをクローズする仕様になっています。
送金先は、最大で4種類あります(個数ではない)。
- to_local output
- to_remote output
- offered HTLC outputs
- received HTLC outputs
to_local outputとto_remote outputは、チャネル内で確定している自分と相手のBitcoin量です。小さい額では送金できないため、Bitcoin量によってはどちらかがない場合もあり得ます。
offered HTLC outputsとreceived HTLC outputsは、送金でHTLCの追加まで行い、まだ反映をしていない分です。offered=自分が送金した方、received=自分に送金された方です。update_add_htlc
後のcommitment_signed
で交換する署名にはこれらが含まれます。
なお、HTLCは複数(仕様での最大は483だが、チャネルオープン時に宣言できる)存在できます。
この4種類のうち、to_remote outputだけはアドレスへの送金(P2WPKH)ですが、それ以外はスクリプトへの送金(P2WSH)です。
offered HTLC outputsは自分から相手への送金なので、相手がpreimage
を使えば受け取り、受け取らないまま時間切れになれば取り戻せるようなスクリプトです。逆にreceived HTLC outputsは自分への送金なので、自分がpreimage
を使えば受け取れるし、時間切れになれば相手が取り戻せるスクリプトです。
そしてto_local、offered HTLC、received HTLCの3つは、相手が展開したcommitment transactionが過去のものだった場合に自分が取り戻せるようなスクリプトになっています(ただし、自分が取り戻す動作を起こす必要あり)。
どのくらい過去のcommitment transactionかを見つけやすくするためかと思いますが、トランザクションのsequence
とlocktime
に仕掛けがあります。commitment number
という世代を管理する値があるのですが、sequence
とlocktime
+チャネル間の情報を使うことでcommitment number
を算出できるようになっています。
最新のcommitment transactionをブロックチェーンに展開してクローズすることをunilateral closeと呼びます。
HTLC Timeout/Success transaction
それぞれoffered HTLC outputsからの送金がHTLC Timeout transaction、received HTLC outputsからの送金がHTLC Success transactionとして決められています。
スクリプトはかなり複雑ですが、「古いcommitment transactionから取り戻す場合」「タイムアウトして取り戻す場合」「preimage
で取り戻す場合」の3パターンを含んでいます(Bitcoinスクリプトは逆ポーランド法のようにスタックを使って演算していきます。ここではスクリプト自体の解説は省略)。
- offered HTLC script
- A: 古いcommitment transactionから取り戻す
- B: タイムアウトして取り戻す
- C:
preimage
で取り戻す
- received HTLC script
- A: 古いcommitment transactionから取り戻す
- B:
preimage
で取り戻す - C: タイムアウトして取り戻す
これらのトランザクションのINPUTはfunding transactionではなくcommitment transactionです。その署名もまたcommitment_signed
で交換します。commitment_signed
は、commitment transactionの署名と各HTLC Timeout/Success transactionの署名を送信します。
さて、このトランザクションの送金先ですが、これもまたスクリプトになっています。形式はto_local outputと同じです。
revoked transaction
送金のたびにcommitment transactionを作り直しますが、チャネルがオープンしている間は古いcommitment transactionでもブロックチェーンに展開することが可能です(署名が揃っているため正常なトランザクションとして認識される)。最新のものではない、破棄された古いcommitment transactionをrevoked transactionと呼び、revoked transactionを展開してチャネルが閉じられることをrevoked transaction closeと呼びます。
破棄されたトランザクションをブロックチェーンに展開するのはBOLT仕様としては違反行為に当たり、ペナルティとして相手がチャネルの全Bitcoin量を取り戻せる権利が与えられます(展開した人にとってはチャネルの全量を奪われることになります)。
破棄するという動作は、revoke_and_ack
メッセージにより行います。このメッセージで1世代前のcommitment transactionで使っていたper_commitment_secret
という秘密鍵を渡します。per_commitment_secret
があると、相手の<revocationsecret>
、すなわち上記スクリプトの<revocationpubkey>
の秘密鍵を求めることができます。<revocationsecret>
があると、上記スクリプトのどれからでも即時送金することが可能になります。
古いcommitment transactionを展開した相手は<revocationsecret>
を作ることができないので、offered HTLCにせよreceived HTLCにせよ、ブロック高が高くなってスクリプトが解けるようになるまでは何もできません。
revoked transactionを展開した人が何もできない間に全部取り戻しなさい、ということです。
ただ、ずっとブロックチェーンを監視するのは、常時起動しているコンピュータならまだしも、スマートフォンのように制限がある環境では難しいものがあります。
そのため、WatchTowerのような外部で監視する仕組みも考えられています(BOLTで直接触れられていないため省略)。
closing transaction
自分と相手が同意してチャネルをクローズ(mutual close)する場合に使用するトランザクションです。
専用のLightning Networkメッセージもあり、Bitcoin量の送金先や手数料も相談して決定します。
この方法がunilateral closeやrevoked transaction closeに比べ、一番手間がかからないクローズ方法です。
おわりに
3回に渡ってLightning Networkについて解説してきましたが、いかがでしたでしょうか。「難解だ」と感じられたのではないかと思いますが、実際に実装してみるともっと難解です(笑)。
本連載が、皆さんのLightning Networkの動作を理解したり、Lightning Network BOLTを読む際の助けになれば幸いです。