先月中旬、ライトニングネットワークの新たな脆弱性が発表されたことが話題になりました。Replacement Cycling Attackと呼ばれるこの手法は特定の条件下において中継ノードが資金を盗まれるというもので、ライトニング開発者の中にも一時的に悲観的な意見が出る程度には根本的なものでした。

実際に現在のライトニングでは根本的な対策が困難な脆弱性ではあるものの、Replacement Cycling Attackを理解するにはビットコイントランザクションやライトニングの仕組みについて深い理解が必要なため、「修正不可能な脆弱性でライトニング終了」のような表面的でセンセーショナルなヘッドラインが独り歩きした印象があります。弱点を容易に突かせないためにリスクを軽減する手法はいくつも存在し、現時点で対策済みのものもあります。

センセーショナルなツイートが拡散されライトユーザーが「ライトニングって全然だめだな」と思うことでライトニングのアダプションが遅れることがあれば非常に残念に思うので、今日は読者の皆さんが理解できるようにReplacement Cycling Attackを噛み砕き、どれくらいのリスクなのか、その対策としてどのようなポリシーや変更が考えられるかについて解説します。

  • HTLCの強制決済の仕組みに穴
  • 攻撃に必要な条件と対策
  • ライトニングはもうだめなのか?

今回の記事で使用する図解はMononaut氏のスレッドから引用したものです。

HTLCの強制決済の仕組みに穴

ライトニングチャネルの実態はチャネルの残高を分配する「コミットメントトランザクション」で、常にの最新状況を示すよう二者間で更新されています。チャネル上をライトニング送金が流れるたびに、Aliceの取り分、Bobの取り分とは別に「HTLC」と呼ばれるコントラクトにロックされた出力がコミットメントTXに追加されます。

これは仮決済のようなもので、通常は宛先側からハッシュロックに対応するプリイメージというデータを使って決済され、コミットメントトランザクションがオフチェーンのまま更新されますが、何らかの理由で制限時間内(タイムロック期間内)に正常に決済されない場合、送金者側のノードがプリイメージなしでもオンチェーンで回収できます。

Image
AliceとBob、BobとCarolの間のコミットメントトランザクション。Alice→Bob→Carolへと送金を中継するとき、それぞれに同じハッシュロックを用いたHTLCが追加される。正常な決済時には宛先側からプリイメージが伝えられるため、宛先側のほうが先にタイムアウトする。(仮にAlice→BobのHTLCがタイムアウトするとAliceはHTLCの残高をオンチェーンで回収して送金を巻き戻せる)

Replacement Cycling Attackは正常に決済されなかったHTLCの資金を回収できないようにしてしまう攻撃です。具体的にはマルチシグアドレスからの入力を「トランザクションから外してしまう」ことで、被害者ノードの知らないところで正常に決済してしまうのです。どういうことでしょうか。

Transaction Replacementという、複数の入力をもつトランザクションの特定の入力を排除するテクニックが以前からあります。単純な例を出すと、AとBの2つのコイン(UTXO)を消費するトランザクションを配信したあと、RBFなどによってAのみを消費するトランザクションをより高い手数料で配信します。するとそれを受け取ったノードは以前のトランザクションを捨てるため、Bが入力にあるトランザクションのことは忘れてしまいます。(キリがないのでReplaceされたトランザクションはノードから消去されます)

RBFについては過去記事をご覧ください。

未承認トランザクションを置き換え可能にするRBFが既定ではない理由と、既定にするメリット
先月、bitcoin-devメーリングリストで「Bitcoin Core 24.0にFull RBFを導入しよう」という提案が話題になりました。これは機能自体の歴史的経緯のほかにも、レイヤー2の設計やコンセンサスの変更 (ソフトフォーク・ハードフォーク)とは異なる「ポリシールールの変更」がネットワーク利用者に及ぼしうる影響という面で興味深い提案です。 今日はRBFとはなにか、なぜ現状ではすべてのトランザクションがRBFではないのか、そしてすべてのトランザクションがRBFになるメリットをまとめます。 RBFとは RBFとはReplace-by-Feeの略で、あるトランザクションがブロック…

今回の場合は入力がマルチシグなのでもう少し複雑ですが、実質的に同じことを行います。Mononaut氏の図解が非常に助かります。まず、先程の例でAliceとCarolが共謀してBobから資金を盗もうとしていると考えます。(実は同一人物かもしれません)

まずCarolからプリイメージが返ってこず、Bob→Carolへと中継したHTLCがタイムアウトしてしまいます。Bobはコミットメントトランザクションをオンチェーンに流し、HTLCから資金を回収するhtlc-timeoutトランザクションも配信します。

Image
BobはCarolに送ったがタイムアウトしてしまった資金を取り返すため、ライトニングチャネルを閉鎖しhtlc-timeoutトランザクションで資金の回収を試みる。

ところが実は攻撃者Alice/Carolはこのときに備えて準備をしていました。全く無関係そうなトランザクションcycle parent、cycle childを準備していたのです。これは先程と同様なTransaction Replacementに使用するものです。

Image
Alice/CarolはTransaction Replacementに使用する別トランザクションを準備していた。

Bobのhtlc-timeoutトランザクションが成功するとBobからの資金盗難は失敗に終わってしまいます。そこでAlice/CarolはBobがhtlc-timeoutトランザクションを配信すると即座に「HTLCのプリイメージを公開して決済するhtlc-preimageトランザクション」にcycle parentからの出力を入力として加えたものを配信します。手数料率がhtlc-timeoutやcycle childより高くなるように設定することで、それらのトランザクションは各ノードのメモリプールから破棄されます。

Image
Carolはhtlc-timeoutトランザクションを各ノードのメモリプールから破棄させることを目的に、プリイメージでHTLCを決済するhtlc-preimageトランザクションを配信する。cycle parentからの入力を使い、htlc-timeoutトランザクションより高い手数料率を設定することで目的が達成される。

しかし、このままでは攻撃者はBob→Carolの送金を受け取ることはできますが、もしBobのノードがhtlc-preimageトランザクションに示されるプリイメージを用いてAliceからのHTLCを正常に決済してしまうと攻撃は失敗となります(送金が中継されただけ)。攻撃者の目標はBobにプリイメージを渡さずにAlice→Bobをタイムアウトさせて、Bob→Carolの送金を受け取りつつ、AliceがBobへ送った資金を回収することです。

そのためにはBobがプリイメージを知ってAliceからのHTLCを決済したり、htlc-timeoutトランザクションを成功させない必要があります。その手段として、cycle parentをさらに置き換え、htlc-preimageトランザクションごとメモリプールから消去させます。

Image
Alice/CarolはBobにプリイメージが知られないように、cycle parentを置き換えてhtlc-preimageトランザクションを各ノードのメモリプールから消去させる。

ここまでくるとBob→CarolへのHTLCを回収するトランザクションはメモリプールに存在しません。htlc-timeout、htlc-preimageトランザクションも見当たりません。Bobがhtlc-timeoutトランザクションを再試行するたびに攻撃者は上記のサイクルを繰り返してメモリプールからの破棄を狙います。

Image
上記のサイクルが完了するとBob→CarolのHTLCは宙に浮いた状態のままになる。Bobがhtlc-timeoutの再試行を試みると、Alice/Carolが上記のサイクルをもう1度繰り返してこの状態に戻る。

さて、HTLCのタイムアウトは宛先側でのトラブルを解決する時間を与えるために送金者側のほうが数ブロック長いようにできています。これをcltv_deltaといい、ノード実装により既定値は様々ですが24時間(144ブロック)というのが一般的です。もし攻撃者がこの手法で144ブロックの間Bobのhtlc-timeoutトランザクションを妨げ続ければ、次はAliceがhtlc-timeoutトランザクションで資金を回収できます。

これ以降にCarolがhtlc-preimageトランザクションによってBobからのHTLCを決済すれば、Bobはプリイメージを知ることができてもすでにAliceに返金済みなためどうしようもありません。Bobが一方的にCarolに資金を盗られてしまいます。

攻撃に必要な条件と対策

この攻撃方法が発見されたのは2022年の暮れだったそうですが、実際には今のところ実行された様子はありません。というのも、様々な理由からかなり実行難易度が高い攻撃だからです。

まず、(被害者) Bob→Carol (攻撃者)へと流動性のあるチャネルが必要になります。Carolが一方的にチャネルを開設しただけでは流動性がCarol側に偏っているため、一旦Bobを経由して送金する必要がありコストがかかります。

また、攻撃時も様々な手数料を加味して採算が取れそうな金額が中継できるかという問題、cltv_deltaの期間中ずっとhtlc-timeoutを試され続けてもhtlc-preimageを検知されずにTransaction Replacement Cyclingできるか問題などがあります。

逆に、攻撃経路に十分な流動性があればそこそこ大きな被害額につながる可能性もあるといえるでしょう。攻撃者はあくまでリスクとリターンを天秤にかけて実行するので、例えば1 BTCが得られる見込みがあれば手数料水準によっては何百回でも攻撃を試行する価値があるでしょう。(そのためには攻撃者→中間ノード→被害者→攻撃者のすべてのホップで1BTCの送金を中継する流動性が必要で、いささか非現実的ですが…)

実際にこれまでに実装された対策としてはこれらの条件を達成しにくくするものがいくつかあります。例えばサイクルが完了すると各ノードは再びBobのhtlc-timeoutを受け付けられるようになるので、cltv_deltaを長くしてこのトランザクションを頻繁に再配信して攻撃者の負担を大きくする方法、メモリプールをより注意深く監視して毎サイクル配信されるhtlc-preimageの検知確率を高める方法などです。

これらは根本的な対策ではなく、成功確率こそ低下すると考えられますが攻撃者側のコスト(リスク)に対して得られる可能性のあるリターンが大きい状況は変わりません。攻撃被害に遭う可能性のある条件として送金の中継を行うルーティングノードであることが必須なため、心配ならライトニングノードを使ったルーティング(送金中継)を行わないのが一番かと思われます。Lndなどを使って自前のライトニングノードを実行している場合はrejecthtlc=trueという設定が必要です。

根本的な対策の1つとしてOP_EXPIREというオペコードをソフトフォークで導入しよう、というものがあります。これは特定の時間経過後にコインの使用条件を有効にするCLTVとは逆に、特定の時間経過後に条件を無効にするオペコードで、タイムアウト後はhtlc-preimageトランザクションを使えなくするという提案です。ビットコイン自体にソフトフォークを導入する難しさがあるので実現可能性は高くなさそうですが、汎用性はありそうなので個人的には悪くない提案だと思います。(現状ないことには何か歴史的経緯があるかもしれませんが)

ちなみにAliceとCarolが同一人物である必要は実はなくて、Aliceに悪意も必要ありません。Carol→…→Alice→Bob→Carolというふうに、Carolが自身発の送金をBobからCarol自身へと中継させれば単独で攻撃できます。(Aliceがhtlc-timeoutを試みるのはLNプロトコル通りの合理的行動であるため)

ライトニングはもうだめなのか?

さて、長い記事になってしまいましたがここまで読んでくださった方はなんとなく攻撃手法を理解できていれば幸いです。「ライトニングはだめだ」という意見が暴論であることと、でも実際にある程度深刻な脆弱性であることがおわかりいただけたでしょうか。

個人的にはライトニングノードはホットウォレットに付随するリスクの上にルーティングという金融取引のリスクも乗っかっているため、軽い気持ちで取り組むのは難しいと考えています。(3年以上rejecthtlc=trueチームなので今回の攻撃手法には影響されません。) その条件下でも安心を目指すなら信用できるノードとのみチャネルを開設し、ハイリスクと思われる送金の中継には相応の対価を求めるようになっていくのが合理的でしょう。というか、超大手のルーティングノードの多くはすでに接続先を(主に品質管理の観点から)ホワイトリスト方式で絞っています。

ライトニング送金の手数料市場は現在は未成熟ですが、ひょっとすると長期的には様々なリスクを鑑みて現在より高めの相場に落ち着くこともありえます。ビットコインと円満に付き合っていくには柔軟な思考を持って「現状とどう付き合っていくか、対応していくか」という心持ちで取り組み続けることが重要に思います。