安土 茂亨(@techmedia_think)は株式会社ハウインターナショナルに所属する開発者で2ndレイヤー技術を中心に研究開発を行っている。Open Assets ProtocolのRuby実装”openassets-ruby”や、BitcoinプロトコルのRuby実装”bitcoinrb”を開発中。

Bitcoinのセキュリティに関する技術カンファレンス「Breaking Bitcoin」が9月9日から10日にかけてパリで開催された。コア開発者を含む多くのスピーカーがいる中、Purse.ioのCTOでありbcionの開発者であるChristopher JeffreyのBitcoin Coreの脆弱性に言及したプレゼンテーションが注目されている。

Breaking Bitcoinの動画（Christopher Jeffreyのセッションは2：30あたりから）https://www.youtube.com/watch?v=0WCaoGiAOHE

Bitcoin CoreのUTXOデータの管理方法と問題

公表されたBitcon Coreの脆弱性は、Bitcoin CoreのUTXOを管理するchainstateデータベースの設計に起因する。

もともとSatoshiのオリジナルの実装では、UTXOの管理を各UTXOの位置を示す12バイトの固定値をフラットファイルで管理しているだけのものだった。しかしBitcoin Coreは、0.8.0からパフォーマンスの改善のため、UTXOをデータベースに保存するようになった。この変更によりデータサイズが大幅に削減され、ノードはフルトランザクションインデックスを保持する必要がなくなり、bcoinやbtcd、NBitcoinなど他の実装でも一般的な実装方法として採用された。

このchainstateデータベースでは、ノードが新しいトランザクションを受信すると、そのトランザクションの全アウトプットをシリアライズしてデータベースの１つのレコードに保存するようになっている。

トランザクションを受信して、そのトランザクションが有効なトランザクションか検証する際、まずトランザクションのインプットが参照するUTXOのデータをロードする。UTXOのデータはトランザクション単位に保存されているため、１つのUTXOのデータをロードするということは、そのUTXOのトランザクションの全アウトプットを含むデータをロードすることになる。

実際に必要なUTXOはトランザクションの１つのアウトプットのみであっても、そのトランザクションの全アウトプットのデータがロードされる。また、ロードしたアウトプットは実際に使用されたアウトプットを除いて再度シリアライズされデータベースの同じレコードに書き戻される。

新しいブロックを受信した際は、そのブロックに含まれる全トランザクションのインプットが参照するトランザクションの全アウトプットがロードされ、再度シリアライズされ、データベースへの書き戻し処理が行われる。データベースへの書き戻しはLevelDBのアトミックなバッチ書き込みで行われるが、これは仕様上、書き込みが完了するまで全データをメモリ上に展開した状態になるため、ロードした全アウトプットデータがメモリ上に展開されることになる。そのためブロック内のトランザクションが、多くの別々のトランザクションを参照すればするほど、ブロック検証時のメモリ使用量は増えることになる。

Bitcoin Coreをクラッシュさせる方法

この設計の問題をついて以下のようなブロックを作ることで、Bitcoin Coreをクラッシュさせることができる。

約31,000個の誰でも使用可能なP2SHアウトプットを持つトランザクションを大量に（約24,000個）作る。（ブロックサイズの上限が1MBで、その上限いっぱいになるよう１トランザクションにP2SHのアウトプットを作るとその数は約31,000個になる） 1で作成したP2SHのアウトプットを参照するインプットを作成する場合、１ブロック内で作成可能なインプットは最大約24,000個になる。 1で作成したUTXOを参照する24,000個のインプットが含まれるブロックを作成する。 24,000個の各インプットが参照するUTXOのデータセットはそれぞれ約1MBなので、このブロックを検証すると、24,000 × 1MB = 24GBのデータがノード上でロードされ、再シリアライズされ、データベースに書き戻される。

このブロックを受信したノードでは、その検証時にメモリ上に約24GBのデータがロードされることになり、大半のノードはメモリ不足に陥りクラッシュする。24GB以上の大量メモリを積んだノードであればクラッシュすることなく動作するが、Bitcoinネットワークの大半のノードはそのような大容量のメモリを積んでいるとは考えにくいため、大半のノードがクラッシュするだろう。

上記のブロックを作成したregtestのノードをChristopher Jeffreyが用意しており、以下のコマンドで対象のノードに接続できる。

[code lang=text]$ bitcoind -regtest -connect=45.79.76.80:3002[/code]

起動するとブロックチェーンの同期が始まり、Bitcoin Core 0.14.2以下のバージョンであれば、しばらくするとbitcoindがメモリを大量に消費しだし、クラッシュするのが確認できる。

この問題は、UTXOをトランザクション単位でなく、それぞれ別々のデータベースレコードに保存することで解決できる。これによりインプットとUTXOのマッピングがよりシンプルになり、インプットが参照するアウトプットのみをロードすることができるようになる。またロードしたUTXOは後はデータベースから削除するだけで、再度シリアライズしたり大量のデータの書き戻しなども不要になる。

Bitcoin Coreも先日リリースされた 0.15.0で、chainstateのDBの管理方法がトランザクション単位からUTXO単位に変更されており、0.15.0ではこの問題は解決されている。

この問題はプロトコルのバグではないので、UTXOをCoreとは異なる方法で管理していたBitcoinJやlibbitcoin、Parity Bitcoinなどの実装は今回の問題の影響は受けていない。逆にCoreをフォークしたり同じ方法でUTXOを管理しているプロダクトは同様の脆弱性を持つ。

開発や実装の分散化の課題

Bitcoin Coreは公式クライアントとして現在Bitcoinネットワークを構成するノードの70%近くのシェアを持つが、今回のような攻撃がメインネットで実行されるとBitcoinネットワークを構成する大部分のノードがクラッシュする事態になる。

マイナーの集中化についてはよく問題視されるが、Christopher Jeffreyが指摘するのは開発や実装の集中化の問題だ。公式クライアントであるBitcoin Coreは優秀なコア開発者によって開発が続けられており、その貢献は大きいが、１つのソフトウェアに開発が集中し、ネットワーク上のノードの大半がその実装を利用している状況は、Bitcoinのような分散ネットワークを維持する上で今回のようなケースにおいてはリスクになる。

Bitcoin Core以外にもbcoinやbtcd、libbitcoinなどの代替実装が公開されており、Bitcoinプロトコルの実装は多様化してきている。こういった代替実装の開発が継続し、ネットワーク上で分散してシェアを持つようになれば、Bitcoinネットワークはより堅牢になる。