【CTO Tech blog】 btcd v0.24.2未満に存在したコンセンサスバグの内容
- CTO Tech blog

先日公開されたbtcd v0.24.2未満に存在したコンセンサスバグについて↓
CVE-2024-38365 public disclosure (btcd `FindAndDelete` bug) – Implementation – Delving Bitcoin
署名検証時の動作
バグの原因となっていたのは、Segwit導入前のレガシーなトランザクションの署名検証時に、署名対象のメッセージを構築する際の処理。実際に署名検証時に行われる操作は↓
https://en.bitcoin.it/wiki/OP_CHECKSIG#How_it_works
- スタックから公開鍵と署名が取り出される
- 実行中のインプットで実行されるスクリプトを
scriptCodeと呼び、そこからsubScriptを作成する。scriptCodeのスクリプト内のOP_CODESEPARATORを考慮したものがsubScript。具体的な仕組みは、以前の記事のsubScriptとscriptCodeセクション参照参照。 subScript内に署名データがある場合は、それをsubScriptから削除する(これがFindAndDeleteと呼ばれる処理)- 残っている
OP_CODESEPARATORをsubScriptから削除する - 署名の最終byteのSigHashタイプを削除
- 現在処理中のトランザクションのコピー(
txCopy)を作成 txCopyの全トランザクションインプットのスクリプトを空(1 byteの0x00)にするtxCopyの実行中のインプットのscriptSigに↑のsubScriptをセットする- SigHashタイプを考慮してトランザクションを調整し、TxをシリアライズしたデータにSigHashタイプを加えた結果のdouble-SHA256を計算
- ハッシュ値をメッセージダイジェストとして、スタック上の公開鍵を使用して署名検証を実行する。
問題となったのは、3のFindAndDeleteの処理。Bitcoin CoreのFindAndDeleteでは、署名と完全一致するデータのみをsubScriptから削除する。
ただ、scriptPubkeyやP2SHのredeemScript内に署名が含まれるようなケースはほぼないので、実際にFindAndDeleteで署名が削除されるのは特殊なケース。Bitcoin Coreのテストケースで登場するのは、
OP_CHECKSIGVERIFY 30450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01
というOP_CHECKSIGVERIFYと署名データで構成されるredeemScriptのP2SHを使用するトランザクション↓
010000000169c12106097dc2e0526493ef67f21269fe888ef05c7a3a5dacab38e1ac8387f1581b0000b64830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0121037a3fb04bcdb09eba90f69961ba1692a3528e45e67c85b200df820212d7594d334aad4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e01ffffffff0101000000000000000000000000
この場合、scriptCodeとして上記のredeemScriptがセットされ、FindAndDeleteで署名値が削除される。
※上記のトランザクションは特殊なトランザクションで、現状UTXOのredeemScript内にそれを使用する際の署名を入れることはできない。これは、そのような署名を作るためには、そのUTXOを含むトランザクションのtxidが必要になるが、txidを計算するためには署名が必要という循環依存問題が発生するため*1。↑では、そのような制約があるなかで署名検証をパスするように、署名データから公開鍵を復元し、その公開鍵をscriptSigにセットするというやり方で作られている。
※ なお、FindAndDelete処理が行われるのはレガシーなUTXOに対してのみで、segwitではこのような処理は行われない。
btcdの実装
btcdの実装において、↑のFindAndDelete相当の処理をしているのがremoveOpcodeByData関数。
署名データをsigとすると、
<sig> <sig||foo>
のように1つのsigのデータプッシュとsigとfooを連結した値のデータプッシュがscriptCode内にある場合、
- FindAndDeleteは、完全一致する
sigのみを削除する - removeOpcodeByDataは、
sigとsig||fooの両方を削除する
この結果、Bitcoin Coreでは有効と判定される署名を、btcdでは無効と判定するようになる(その逆もしかり)。原因となったコードが導入されたコミットが76339ba。
Bitcoin Coreでは、現在FindAndDeleteにより署名の削除が発生するようなスクリプトの場合、非標準トランザクション扱いになりリレーされないけど、sig||fooのように署名に別のデータが付与されている場合、削除対象にならないので、そういうredeemScriptを持つP2SHを使用するトランザクションは標準トランザクションとしてリレーされる。そのため、上記のようなトランザクションを構成すればbtcdのチェーン分岐を促す攻撃自体は簡単にできる。もし、まだv0.24.2にアップグレードしていない場合は、早急にアップグレードした方がいい。
*1:ANYPREVOUTが導入されれば回避できる


