LNDのWatchTowerを検証してみた
Lightning NetworkでLNノードを運用する場合、相手が古いチャネル状態の取引データ(commitment_tx)をブロードキャストしていないか常にビットコイン・ブロックチェーンを監視している必要があります。もし自分がオフライン状態の時に、相手が古いチャネル状態でチャネルを閉鎖すると自分が損をする場合があります。例えば、最新のチャネル状態における互いの残高が自分=0.5、相手=0.5だとして、1つ古いチャネル状態の残高が自分=0.3、相手=0.7の場合、相手はこの古いチャネル状態でチャネルを閉鎖することで、支払ったはずの0.2コインをまき戻して自身のものにできてしまいます。幸い、LNではこのような取引データをブロードキャストしても、ペナルティとして相手の残高すべて(この場合、0.7コイン)を没収するような仕組みになっています。ただし、このペナルティによる残高の没収をするには、自分のLNノードが常にオンラインでいて、ブロックチェーンを監視している必要があります。もしある一定期間オフラインでいると、この不正な取引データから資金を奪われてしまいます。
基本的にLNノードは常にオンラインですが、障害時など一定期間オフラインでい続けたり、またモバイルウォレットは常にオンラインでいることができないので、相手が不正をするか常に監視し続けることが難しい場合があります。そこで、この不正な取引データ(breach_tx)がブロックチェーンへ送信されていないか監視し、不正データが見つかれば、相手の資金を没収する取引データ(justice_tx)を送信してくれる仕組みがWatchTowerです。
LNDにはこのWatchTower機能が搭載されているので、今回はその機能を使って実際に不正な取引データから資金を没収できるかTestnet環境で試してみました。本稿でみるWatchTowerの詳細はこちらより確認できます。
以下はシステム構成図です。LNDが3つあり、1つがWatchTowerの役目をする独立した機関です。残りの2つは通常のLNDで、ノードAとノードBの間にはチャネルが開設されています。
以降では以下の図の順で進めていきます。
0.WatchTower サーバーの設定
まず初めにLNDのWatchTowerを有効化させます。LND_WatchTowerのlnd.confに以下を追記し、LNDを再起動します。
[Watchtower]
watchtower.active=1
以下のコマンド実行してWatchTowerのアドレスを確認します。
$ lncli tower info
{
"pubkey": "03d8ea83735c35b58fe34f2e2f836acb7237c132626bb8e4fe9dcccbb8745f68db",
"listeners": [
"[::]:9911"
],
"uris": [
]
}
また、以下のコマンドを実行して、ログをリアルタイムで表示するようにしておきましょう。
$ tail -f -n 1000 ~/.lnd/logs/bitcoin/testnet/lnd.log | grep WTWR
1.WatchTowerの登録
次にノードAのWatchTowerClientを有効化させます。LND_Aのlnd.confに以下を追記し、LNDを再起動します。
[Wtclient]
wtclient.active=1
その後、WatchTowerサーバーの登録をします。
$ lncli wtclient add 03d8ea83735c35b58fe34f2e2f836acb7237c132626bb8e4fe9dcccbb8745f68db@212.24.106.109:9911
以下のコマンドで登録したWatchTower情報を確認します。
$ lncli wtclient tower 03d8ea83735c35b58fe34f2e2f836acb7237c132626bb8e4fe9dcccbb8745f68db
{
"pubkey": "03d8ea83735c35b58fe34f2e2f836acb7237c132626bb8e4fe9dcccbb8745f68db",
"addresses": [
"212.24.106.109:9911"
],
"active_session_candidate": true,
"num_sessions": 1,
"sessions": [
]
}
これでWatchTowerの登録・設定は完了です。
2.送金
次にノードBからノードAへ送金をしますが、現在のノードBのチャネル状態を後で使うのでchannle.dbのバックアップを取ります。
$ cp ~/.lnd/data/graph/testnet/channel.db ~/.lnd/data/graph/testnet/channel_old.db
バックアップを取得したら以下のコマンドでノードBからノードAへいくらか送金します(事前にノードAからインボイスを生成すること)。
$ lncli payinvoice lntb10u1pss0rwtpp54avguhvfjtyhwr3meklrj8mf35h9gghv8h7egkhl36ahxkz3cn6qdqqcqzpgxqyz5vqsp5359dk9rs63mpmxnad80g3nqgsazgp64le3yceyd89y5ul3z3mhcs9qyyssqrulhn89m3xh9xhylwgj5vralfvl7kx2gt4r886n9tx0r7mlt2fy3rrsdwmkazpezuafp7wgf5rcwg402vzt4eq8u7kxwhadaeepsutsq0fuzv
3.justice_txの送信
ノードBから送金ができたら、ノードAで以下のコマンドを実行してみてください。num_backupsの値が増えていることが確認できると思います。これは受金した時に、相手の資金を没収する取引データ(justice_tx)をWatchTowerへ送信したことを表しています。上記の図の「3.justice_txの送信」に該当する箇所です。ノードAはコインの送受信をする度にこのjustice_txをWatchTowerへ送信します。
$ lncli wtclient tower --include_sessions 03d8ea83735c35b58fe34f2e2f836acb7237c132626bb8e4fe9dcccbb8745f68db
{
"pubkey": "03d8ea83735c35b58fe34f2e2f836acb7237c132626bb8e4fe9dcccbb8745f68db",
"addresses": [
"212.24.106.109:9911"
],
"active_session_candidate": true,
"num_sessions": 1,
"sessions": [
{
"num_backups": 10,
"num_pending_backups": 0,
"max_backups": 1024,
"sweep_sat_per_byte": 10,
"sweep_sat_per_vbyte": 10
}
]
}
また、LND_WatchTowerのログを見てみましょう。以下のログは07:22にノードAからjustice_txを受け取ったことを表しています。
2021-08-02 07:22:58.830 [INF] WTWR: Accepted incoming peer 02b68d50354ae757d577e78c4b9c477dc23d16034c1fdda77cfe4e9a649404
2021-08-02 07:22:59.092 [INF] WTWR: Releasing incoming peer 02b68d50354ae757d577e78c4b9c477dc23d16034c1fdda77cfe4e9a64940
4.古いチャネル状態で強制チャネル閉鎖
送金が完了したら、ノードAのLNDを停止してオフライン状態にします。その後、ノードBのLNDも停止して、取得したchannle.dbのバックアップから古いチャネル状態へチャネルを復元させます。
$ mv ~/.lnd/data/graph/testnet/channel_old.db ~/.lnd/data/graph/testnet/channel.db
その後、ノードBで以下のコマンドを実行してチャネルを強制閉鎖します。これでノードAがオフライン中に、ノードBは古いチャネル状態(送金する前の残高状態)でチャネル閉鎖をすることができます。
$ lncli closechannel --force 77bfbbf6724464674bdc9fac33e023df208bd04b9f096d53c0e160b7f0db06cd 0
以下がその強制チャネル閉鎖のtxidで、アウトプットが2つあります。1つはノードA(0.00009607 tBTC)へ、もう1つはノードB(0.00010210 tBTC)へのアドレスなのですが、、、
https://mempool.space/testnet/tx/dbe94fb382f15d228273dfee14940f2dbf1f2a4fc1ccb968860ccea1ff88dcbc
5.ペナルティによる資金の没収
この時点でノードBが不正な取引データをブロックチェーンへ送信しました。しかし、WatchTowerはブロックチェーンを監視して、不正データを発見すると、ノードAから受け取ったjustice_txをブロックチェーンへ送信して、ノードBの資金を全額没収します。以下がそのログになります。07:22にノードAからjustice_txを受け取っており、07:31に不正データが発見されました。そしてすぐに、justice_txを送信しているのが読み取れます。
2021-08-02 07:22:58.830 [INF] WTWR: Accepted incoming peer 02b68d50354ae757d577e78c4b9c477dc23d16034c1fdda77cfe4e9a649404
2021-08-02 07:22:59.092 [INF] WTWR: Releasing incoming peer 02b68d50354ae757d577e78c4b9c477dc23d16034c1fdda77cfe4e9a64940
2021-08-02 07:31:00.573 [INF] WTWR: Found 1 breach in (height=2062748, hash=000000000000000ef140a8c2c06a888514f9e85c236d3
2021-08-02 07:31:00.573 [INF] WTWR: Dispatching punisher for client 02b68d50354ae757d577e78c4b9c477dc23d16034c1fdda77cfe4968860ccea1ff88dcbc
2021-08-02 07:31:00.575 [INF] WTWR: Publishing justice transaction for client=02b68d50354ae757d577e78c4b9c477dc23d16034c1e72367f73eaacbaa4016fd8db8
2021-08-02 07:31:01.077 [INF] WTWR: Punishment for client 02b68d50354ae757d577e78c4b9c477dc23d16034c1fdda77cfe4e9a6494045ccea1ff88dcbc dispatched
WatchTowerは不正な取引データを検知したので、justice_txを送信しました。この取引データでは、ノードBへの資金(0.00010210 tBTC)を没収して、ノードAのアドレスへ送金するものになっています。以下がそのtxidになります。
https://mempool.space/testnet/tx/558065d8f689fae9bd46060e53d912c8786c24e72367f73eaacbaa4016fd8db8
まとめ
以上より、WatchTowerで不正データを検出して資金の没収をすることが検証できました。このWatchTowerは外部サービスとして活用することもできますが、その場合、信頼ポイントができてしまいます。そこで、WatchTowerへ少額課金するなどインセンティブを与えることで、トラストモデルを緩和できるかもしれません。また、自身のLNノードやモバイルウォレットのバックアップとしてWatchTowerを運用するのもリスク低減に役立つと思います。
watchtowerの振る舞いを知りたかったところ大変参考になりました。
インセンティブがわかっていなかったのですが、まとめの部分で方向性が掴めました。ありがとうございます。