Lightning Networkの送金処理で使用されている技術【前編】
前回はLightning Network BOLTの全体像を解説しましたが、ここからはLightning Networkで使用されている技術について個別に解説していきます。すべてを説明することは難しいため、送金に関する技術を中心に行います。
今回は、Lightning Networkで使用される以下の用語を解説します。
なお、Lightning Networkの技術は日々研究が行われているため、仕様もしばしば追加されています。
今回の記事は、2018年7月上旬の仕様を元にしています。
Lightning Network BOLT
仕様書にあたるLightning Network BOLTは、2018年7月の時点で0~11まであります。
今回の解説でもBOLTへのリンクをいくつか載せているので、詳細についてはそちらを参照してください。
章 | 概要 |
---|---|
BOLT #0 | 用語、テーマソング |
BOLT #1 | Lightning Networkメッセージ |
BOLT #2 | 状態とメッセージ構造 |
BOLT #3 | HTLC、鍵 |
BOLT #4 | ONIONルーティング |
BOLT #5 | トランザクション |
BOLT #6 | (削除) |
BOLT #7 | アナウンスメント |
BOLT #8 | Noise Protocol |
BOLT #9 | Features |
BOLT #10 | mDNS |
BOLT #11 | 請求書 |
単位
Bitcoinの最小単位は、satoshi
です。
Lightning Networkのチャネルで扱う最小単位は、1000分の1satoshi=ミリsatoshiです。仕様書ではmsatoshi
やmsat
と略されることもあります。
識別子
Lightning Networkで使用する主な識別子(ID)について解説します。
ノードID (node_id)
Lightning Networkは、Lightning Networkノード(以下、LNノード)同士がネットワーク接続します。また、Bitcoinの送金はアドレスやスクリプトに対して行いますが、Lightning Networkの場合はLNノードに対して行います。
そのため、LNノードを指定するシーンがしばしばあります。
LNノードは、ノードIDという識別子を持ちます。
これは公開鍵に相当する値で、「02」や「03」で始まる数値です。
チャネルID (channel_id)
チャネルIDは、LNノード間で直接通信するときに相手を指定するIDです。
Lightning Networkメッセージ(後述)でチャネル間の通信を行うときに使用します。
IDの値は、チャネルをオープンするときのトランザクションIDと送金先indexを元にした32byteのデータです。
そのため、funding transactionの構成が決定した時点で値が確定します。
ショートチャネルID (short_channel_id)
チャネルIDと同様に、チャネルに対する識別子としてショートチャネルIDもあります。
ショートチャネルIDは、主に送金の転送経路を表すのに使われます。
IDの値はチャネルIDとは関係なく、以下の3つの情報を連結した8byteのデータです。そのため、8byteのデータとせず、3つの値として表すLNノードもあります。
- [3byte] チャネルオープン時に送金したトランザクション(funding transaction)がマイニングされたブロック番号
- [3byte] そのブロック中に含まれるトランザクションID列のうち、funding transactionのTXIDが入っているインデックス値
- [2byte] funding transactionのoutputで、チャネルのマルチシグに送金したインデックス値
funding transactionがブロックに入った後の情報を使うため、値が確定するのはチャネルIDよりも後になります。
計算例
下図は、testnet上でチャネルオープンした際のトランザクションです。
既にクローズした後ですが、
このトランザクションIDは90315ccd79564dc3087be399e7acf729f8ee47c8140a5749e90dbd6be432d768
です。
BitcoinではTXIDなどのハッシュ値を逆順に表現するルール(RPC Byte Order)になっているので、チャネルIDはハッシュ値をそのままの順番(Internal Byte Order)で並び替えた値に2-of-2送金インデックス値をXORした68d732e46bbd0de949570a14c847eef829f7ace799e37b08c34d5679cd5c3190
になります。
ショートチャネルIDについては、
- マイニングされたブロック(Included in Block)は
1352592
(16進数で14A390
) - ブロック内のTXIDインデックス値(Block Index)は
17
(16進数で000011
) - 2-of-2への送金したインデックス値は
0
(16進数で0000
)
ということから、それぞれを連結して16進数で14a3900000110000
となります。
ペイメントハッシュ(payment_hash)
直接目にすることはないかもしれませんが、送金に関してペイメントハッシュというIDがあります。
これは送金してほしいノード(payee)が請求書に載せる値で、送金するノード(payer)はペイメントハッシュを同梱して送ります。
ペイメントハッシュはpayeeが請求書を作成時に生成した乱数(payment_preimage)をSHA256でハッシュ計算した値です。よって、payment_preimageからペイメントハッシュを計算するのは簡単ですが、ペイメントハッシュからpayment_preimageは簡単に求められません。この性質を利用して、HTLCのBitcoinスクリプトにペイメントハッシュを使用し、payment_preimageを持つノードだけがスクリプトを解いてBitcoinを手に入れられるようにしています。
「直接目にすることはないかも」というのは、ペイメントハッシュは請求書(invoice)の中にエンコードされているため、気付かないかもしれないという意味です。
下図はstarblocksで発行された請求書の例ですが、この「lntb....」の中にペイメントハッシュ"b4ef8d6ec9a607b3152a01ce2308f2e5fbe763d7f570f1c5e0fc8dcfe33fa900"が含まれています。
通信
Lightning Networkノード(以下、LNノード)間は、TCP接続します。ポート番号はノードごとに決められますが、通常は9735を使います。この数値は、UNICODEのLIGHTNING
に由来しています。
接続するには、相手ノードのアドレスとポート番号、およびノードIDが必要になります。
仕様では接続する文字列を表示する方法に既定はありませんが、「ノードID
@アドレス
:ポート番号
」のように表現される場合が多いです(ポート番号は9735の場合、省略されることもあります)。
下図は、ACINQ社のLightning Explorer(TESTNET)での表記例です。
02212d3ec887188b284dbb7b2e6eb40629a6e14fb049673f22d2a0aa05f902090e@54.236.55.50:9735
暗号化
ノード間の通信データは、Noise Protocolで暗号化されています。Lightning Network BOLTの仕様では、BOLT#08に記載されています。
LNノード間がTCP接続した後でハンドシェイクして鍵交換を行い、成功したら暗号化したメッセージを送受信できるようになります。
ハンドシェイク
LNノードはTCP接続した後、相手が期待する接続先かどうかを確認するためハンドシェイクを行います。
ハンドシェイクでは、ノードIDの秘密鍵を使って、それ以降の通信で使用する暗号鍵を決定します(送信と受信で鍵は別)。
接続されるノードは、ハンドシェイクによって接続しに来たノードIDがわかるようになっています。
ノードIDの元データが正しくなければハンドシェイクに失敗するため、ノードIDだけ偽って接続することはできません。
Lightning Networkメッセージ
ハンドシェイク後、LNノード間の通信は暗号化します。
方式はChaCha20-Poly1305です(ChaCha20が暗号化で、Poly1305はメッセージ認証符号)。
メッセージ長とメッセージ本体のそれぞれを暗号化し、その2つを結合したものがLightning Networkメッセージです。
最初の暗号鍵はハンドシェイク時に決定しますが、それ以降は500メッセージごとに鍵を変更します。
LNノードが接続している間は、交換するデータが無い場合でも送信するメッセージがあります。そのため、長時間接続したままでも、同じ鍵を使い続けることにはなりません。
メッセージ種別
2018年7月の時点では、以下のメッセージ種別があります。個別のメッセージについては説明を省略します。
鍵
Lightning Networkでは、いろいろな鍵データが使用されます。ここでは、各チャネルで使用する鍵データについて説明します。
Bitcoinでは「秘密鍵(private key)」と呼びますが、Lightning Networkではsecret
と呼ぶことが多いため、ここでもsecret
と呼びます。
公開鍵はpubkey
(public keyの略)と呼ぶことにします。
Bitcoinウォレットの鍵管理
Bitcoinでは、以下はほぼ同じ意味です。
- アドレスの持ち主
- アドレスから送金できる
- アドレスの秘密鍵を持っている
そのため、「秘密鍵は絶対に人に見せてはいけないし、紛失してはいけない」と言われます。
Bitcoinは取引所やウォレットアプリが所有しているわけではなく、秘密鍵を持つ人が所有者になるからです。
送金を行う場合、普通は「送金する額」と「お釣り」が発生します。
通常はお釣りのために別のアドレスを作って、そこに送金します。
例えば、Aさんのアドレスに100BTC入っていて、そこからBさんに5BTC送金したい場合は、以下のトランザクションを作ってブロックチェーンに展開します。
- Bさんアドレス:5
- Aさんお釣りアドレス:100-5-(手数料)
お釣りをAさんの送金元アドレスと同じにすることもできますが、通常は新しくアドレスを作ります。
つまり、新しい秘密鍵を作ります。
送金してもらう場合も、そのたびに受信用アドレスを新しく作ってもらう場合が多いです。もし何らかの方法で秘密鍵が漏洩した場合でも、そのアドレスが既に送金済みであれば何もできないので、アドレスを新しく作ります。
このように鍵をどんどん作っていくことになりますが、こういうデータが際限なく増えていくと管理が大変です。そこで、「シード(seed)」という秘密鍵を生成する元データを使った「HDウォレット」という方式が主流になっています。
Bitcoinウォレットを最初に作ったとき、単語が並んだデータをバックアップするよう指示されると思いますが、これが「シード」です。
この「シード」とパラメータから秘密鍵を生成するため、ウォレットとしては「シード」だけバックアップしておけば、他の秘密鍵すべてを生成できます。
ウォレットアプリにはPINやパスワードなどありますが、それらはアプリケーションが保存した鍵やデータを保護するためのものです。
シードや秘密鍵が、Bitcoinとして管理するデータになります。
Lightning Networkのウォレットの鍵管理
Lightning Networkではブロックチェーンがないため、Bitcoinとは状況が異なります。
送金をその場で確定させるために、いつでもチャネルをクローズしてBitcoin量を2人に配分するトランザクション(commitment transaction)を作っています。
commitment transactionは、送金途中で相手がいなくなっても取り戻せるようにしたり、相手が裏切って送金を無かったことにしようとすると全額取り戻せるようにしたりするために、HTLCというBitcoinスクリプトへ送金します。
HTLCにpubkey
が複数入っており、該当するsecret
を持つ人しか使えないようにしています。
また面倒なことに、commitment transactionは自分が展開する場合と相手が展開する場合があります。
そのため、「自分用のcommitment transaction」と「相手用のcommitment transaction」の2種類があります。
自分用と相手用では条件が変わるので、HTLCの書き方も変わってきます。
例えば、自分にとっては「送金を行う」が相手にとっては「送金を受け取る」になるので、自分のcommitment transactionでは「相手が送金を受け取らずにタイムアウトしたら自分に戻る」という条件が「請求書のpreimage
を使えば自分に送金される」という条件になります。
よって、1つのcommitment transactionでは、HTLCで使うsecret
の管理が必要で、それが自分用と相手用の2つあることになります。
HTLCで使うpubkeyは最大で5個あります(remotepubkey
, delayedpubkey
, revocationpubkey
, local_htlcpubkey
, remote_htlcpubkey
)。
これが2つなので10個です。
これが「1つの」commitment transactionについてです。
commitment transactionは、送金のたびに作成します。
前回、送金は「HTLCの追加」「HTLCの反映」の2つに分かれると説明しましたが、これはcommitment transactionをそれぞれ作成するという意味です。
つまり、1回の送金完了では計4つのcommitment transactionを作成します(「HTLCの反映」ではHTLCが消えているので全部の鍵は使いません)。
では、最新の鍵だけ覚えておけば良いかといえば、そうではありません。
相手が裏切ったときの対策が必要です。
「相手が裏切る=古いcommitment transactionをブロックチェーンに展開する」です。
裏切った方が損をするのですが、そのアクションは裏切られた方が起こさないといけません。
展開された古いcommitment transactionと同じ時代のsecret
を使ってBitcoinを取り戻します。
すなわち、作成したcommitment transactionのsecret
はすべて保持しておかないといけません(相手の鍵については後述のper_commitment_secret
だけで良い)。
これは1つのチャネルに関しての話で、チャネルの数だけこのデータがそれぞれ必要です。
これでは、面倒さはともかく、そのまま保存したのではデータ量が膨れ上がってしまいます。
鍵の生成方法
そこで、Lightning Networkでも「シード」を使います。
ですが、BitcoinのHDウォレットとは使い方が異なります。
シード(32byteの乱数)と、インデックス値(初期値は281474976710655)を用意します。
このシードは、チャネルの作成時に決定して、自分だけが持っておきます。
そして、シードとインデックス値からper_commitment_secret
という値を作ります。
per_commitment_secret
は、インデックス値で表される世代のcommitment transactionで使用するsecret
たちのシードに相当します。
そのpubkey
を求めます(per_commitment_point
)。
それとは別に、HTLCで使用する鍵の元になるsecret
もチャネル作成時に決定します(payment_basepoint_secret
、delayed_basepoint_secret
、htlc_basepoint_secret
、revocation_basepoint_secret
)。
HTLCで使用するのは4個の自分で作成した鍵と1個の相手が作成した鍵なので、ここでは4個分作成します。
そして、そのpubkey
を求めます。
ここまでで、per_commitment_secret
とその公開鍵であるper_commitment_point
、各鍵の元になるbasepoint_secret
と公開鍵ができました。
そして、各鍵の元になるbasepoint_secret
から、各鍵のsecret
を求めます。
1種類だけですが、相手からもらうデータで生成する鍵もあります。取引を行って廃棄したcommitment transactionを使って相手がチャネルを閉じた場合に、すべて取り戻せるようにするための鍵なので、相手から廃棄したper_commitment_secret
をもらうことでsecret
が生成できるようになっています。
データと生成する鍵の関係は、下図のようになります。
非常に分かりづらかったと思いますが、シードとインデックス値と各basepoint_secret
が決定すれば、その世代のcommitment transactionで使用する鍵が求められる、ということさえわかれば良いと思います。
計算式は、BOLT#03をご確認ください。
鍵の保存
自分の鍵については計算して算出できるので良いのですが、HTLCには相手の鍵も使うため、その管理が必要になります。
当然ですが、相手のシードは教えてもらえないため、自分から見ると相手の鍵データには関連性が見えず、そのままデータとして保存するしかないように思ってしまいますが、そこも考慮されています。
BitcoinのHDウォレットはelkrem
という方式でしたが、Lightning Networkではshachain
という方式を採用することで、相手の鍵を効率的に保存できるようになっています。
先ほど、シードとインデックス値からper_commitment_secret
を作成すると書きましたが、その作成方法に従うことで、相手からのper_commitment_secret
をストレージに保存しても固定サイズで収まるようになっています。
おわりに
今回は、Lightning Networkのメッセージや鍵管理について説明しました。
次回は、送金について説明します。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Lightning Networkが動作する仕組み
- 人物を切り抜いて画面に表示するKinectサンプル
- ブロックチェーンの取引情報の管理とチェーンの維持
- Red Hat Cluster Suiteの紹介
- SecretもPulumiで使いこなしたい! PulumiのSecurityを試してみよう
- kustomizeやSecretを利用してJavaアプリケーションをデプロイする
- コンテナ上のマイクロサービスの認証強化 ~StrimziとKeycloak~
- Project Calicoのアーキテクチャを見てみよう
- JBoss Fuseを使い倒す その2:デザインパターン概要編
- クラスタの構築(前編)