Develop with pleasure!

福岡でCloudとかBlockchainとか。

マイナーへの賄賂を利用したHTLCへの攻撃に対処するMAD-HTLC

ハッシュのプリイメージの提供とコインの入手をアトミックに行うHTLCの仕組みは、LNなどのマルチホップ支払いをサポートするPayment Channel NetworkやAtomic Swapなどいろんなアプリケーションで使われている。このHTLCの安全性に関する問題提起とその問題を回避するためのソリューションとしてMAD-HTLCと呼ばれる新しいHTLCプロトコルの提案が最近発表された↓

https://arxiv.org/pdf/2006.12031.pdf

HTLCに対する攻撃

アリスとボブは、アリスがシークレットを提供したらボブはコインを支払うといったコントラクトをHTLCを使って構築できる。このようなHTLCにロックされたコインを入手する方法は、以下のいずれかになる。

  • アリスがハッシュのプリイメージとなるシークレット値を提供する。
  • 予め指定された期間を過ぎてもアリスがシークレット値を提供しない場合、タイムアウトとなりボブがコインを取り戻す。

シークレットの提供とコインの入手がアトミックに行えるのがHTLCのメリットだが、ボブはマイナーに賄賂を送り結託して攻撃を仕掛けることができる。

アリスがシークレット値を提供してコインを入手するトランザクションを作成し、ブロードキャストしたとする。このトランザクションの手数料よりも高い額を賄賂としてボブとマイナーが結託する場合。マイナーとしては、アリスのトランザクションをブロックに入れずに、タイムアウト時間になるの待ち、ボブの払い戻しトランザクションをブロックに格納した方が実入りが良い。この場合、シークレット自体はアリスがトランザクションをブロードキャストした際に明らかになるので、ボブはシークレット値を得ながら、かつコインも払い戻され、アリスのみが損をすることになる。

マイナーのインセンティブが経済的な合理性のみである場合、↑のような賄賂を使った攻撃も考えられる。

MAD-HTLC

不正行為ががった場合に、その当事者にペナルティを与えることで↑の賄賂攻撃に対応しようというのがMAD-HTLCの提案。ちなみにMADという名前は、冷戦時に提唱された相互確証破壊(Mutual Assured Destruction = MAD)から来てるみたい。

不正行為というのはアリスがプリイメージを公開したにも関わらずボブがコインを取り戻す行為でこれに対応する。そのため↑のHTLCに対して、ボブがタイムアウトによりコインを取り戻す際にボブのみが知るプリイメージの公開を強制し、ボブのプリイメージとアリスのプリイメージの両方が揃った場合、コインを第三者が取得できるようにする。

具体的には以下のようはHTLC(MH-Dep)を構成する。

OP_HASH160
<H(アリスのプリイメージ)>
OP_EQUAL
OP_SWAP
OP_HASH160
<H(ボブのプリイメージ)>
OP_EQUAL
OP_IF
  OP_IF
    OP_1
  OP_ELSE
    <タイムロック時間> OP_CHECKSEQUENCEVERIFY OP_DROP <ボブの公開鍵> OP_CHECKSIG
  OP_ENDIF
OP_ELSE
  OP_VERIFY <アリスの公開鍵> OP_CHECKSIG
OP_ENDIF

このMH-Dep HTLCスクリプトにロックされたコインをアンロックするための方法は以下の3つ。

  • アリスがプリイメージと自身の秘密鍵で作成した署名を提供する。
  • ボブがタイムロック時間を超えてから、ボブのプリイメージと自身の秘密鍵で作成した署名を提供する。
  • アリスのプリイメージとボブのプリイメージの両方を提供する。

このスクリプトの場合、ボブがマイナーと結託しても、マイナーはアリスのプリイメージとボブのプリイメージを知っていれば、マイナー単独でMH-Dep HTLCスクリプトにロックされているコインを入手することができる。

ただMH-Dep HTLCのみだと、元々アリスのプリイメージが分かればボブはコインを支払う前提であったため、ボブはそのコインを自分が手にすることはなくても、マイナーと結託して、アリスがプリイメージを公開した後、自分のプリイメージをマイナーに公開してアリスに資金が渡らないという嫌がらせをすることが考えられる。こういったケースに対応するため、MAD-HTLCの提案では、MH-Depに加えて以下のような担保となるMH-Col HTLCスクリプトを合わせて使用する提案もしている。

<MH-Depと同じタイムロック時間>
OP_CHECKSEQUENCEVERIFY
OP_DROP
OP_HASH160
<アリスのプリイメージ>
OP_EQUAL
OP_IF
  OP_HASH160 <ボブのプリイメージ> OP_EQUAL
OP_ELSE
  <ボブの公開鍵> OP_CHECKSIG
OP_ENDIF

このMH-Col HTLCスクリプトにロックされたコインをアンロックするための方法は以下の2つ。

  • ボブがタイムロック時間を超えてから、ボブの秘密鍵で作成した署名を提供する。
  • アリスのプリイメージとボブのプリイメージの両方を提供する。

これはMH-Depの担保となるHTLCで、上記のようにボブがマイナーにボブのプリイメージを公開してアリスの資金の入手を妨害した際に、ペナルティとしてMH-Colにロックされたコインをマイナーによって没収されるというもの。

HTLCを上記のスクリプトで構成することで、マイナーへの賄賂を利用したHTLCへの攻撃を回避しようというのがMAD-HTLCの提案。

MAD-HTLCの課題

MAD-HTLCによって、賄賂に対する防御が可能になる反面、MH-Colまでセットで実現すると、ボブ側のユーザーにとってはMH-Colにロックする担保のコインとそのTxの手数料分のコストが余計にかかることになる。

また、アリスがマイナーであった場合、自分のプリイメージをボブに公開しないことで、MH-DepのコインとMH-Colを総取りすることができるという指摘もある

その他にも、アリスのプリイメージを公開したトランザクションがブロックに含まれない場合、アリスはRBFやCPFPによって本来受け取るコインを全て使ってでもボブに資金が渡らないような対抗が可能であるという指摘もある

現状MAD-HTLCが想定する攻撃は実際に見られていないのと、↑のような指摘もあて直ぐにこういった仕組みが組み込まれるということは無いかもしれないけど、興味深い提案だと思う。

Bitcoinの次のソフトフォークのデプロイ方法は?

Bitcoinへのソフトフォークは紆余曲折あった2017年8月のSegwitの導入以降、3年経とうとしているが新たに行われていない。次のソフトフォーク候補として有力なのがSchnorr署名およびTaprootの導入だが、BIP化はされたもののまだBitcoin Coreに実装はマージされていないし、具体的なデプロイ方法も決められていない。

Segwitのデプロイ方法は、OP_CSV導入時と同様BIP-9↓が採用された。ただ、このデプロイ方法はマイナーによる投票という捉え方がされSegwit導入には紆余曲折あった。

techmedia-think.hatenablog.com

その後、デプロイのプロトコルに問題があるとして、デプロイ期間の最終日が訪れると自動的にアクティベートされるよう仕様を変更したBIP-8が提案された↓

techmedia-think.hatenablog.com

ただし、BIP-8の仕組みではソフトウェアが対応しデプロイすれば(最終期限日を過ぎると)無条件にアクティベートできるという仕様は、開発者がルールを決めるという印象が強くなる懸念が開発者の中でも挙がった。そして、Segwit導入後も次のソフトフォーク候補は上がるものの(MASTとか)、未だそれがマージされる=新しいソフトフォークデプロイのトリガーを引く状況には至っていない。

そんな中、最近↑のBIP-8への変更の提案がマージされた↓(実はこのBIP-8、2020年2月にBIPのステータスがRejectedになってたが、今回の変更で再度Draftに戻った)

github.com

主な変更点は、

  • これまで、ソフトフォークが指定したタイムアウトまでにアクティベートされなかった場合、無条件にアクティベートされていたが、その振る舞いがlockinontimeoutというブール値のフラグで制御されるようになった。これがtrueに設定されると今までと同様の挙動で、falseに設定されているとタイムアウトするとアクティベートはされず、デプロイ失敗となる。
  • lockinontimeoutフラグの設定は、デプロイの初期段階で設定する必要はなく後からtrueに設定できる。ただし、一度設定するとクリアはできない。

ただ、lockinontimeoutのフラグは、

lockinontimeout should be set to true for any softfork that is expected or found to have political opposition from a non-negligible percent of miners. (It can be set after the initial deployment, but cannot be cleared once set.) 無視できない割合のマイナーからの政治的反対が予想されるもしくは発見されたソフトフォークに対してはtrueに設定しなければならない。

とあるように、開発者がルールを決めるという印象が強くなる懸念は残るように思える。

尚、BIP-8の実装はまだBitcoin Coreに導入されておらず、現在Bitcoin Coreがサポートしているデプロイ方法はBIP-9のみ。実際、次期ソフトフォーク候補のSchnorrやTaprootのデプロイ方法はどうなるのか?個人的にはBCHとの分離などもあり、今回はBIP-9のデプロイでもアクティベートされるんじゃないかと思うけど、どうかなー。

以下、更新されたBIP 8の意訳↓

概要

このドキュメントでは認識されている多数の間違いを修正するBIP-9の代替案を規定している。開始とタイムアウトにはPOSIXタイムスタンプではなくブロック高が使用される。さらに(ソフトフォークと呼ばれる)後方互換性のある変更のアクティベートを保証する追加のアクティベートパラメータが導入されている。

動機

BIP-9ではブロックのnVersionフィールドを再利用して、ソフトフォークの並行デプロイを行うための仕組みが導入された。アクティベーションの際は95%というハッシュレートのほぼ全ての通知が必要になっているが、これは非実用的で、少数のハッシュレートさえあればソフトフォークを拒否することができる。過半数のハッシュレートをベースにしたアクティベーショントリガーであれば、フルノードのアップグレードに代わって過半数のハッシュパワーが新しいルールを適用することでアクティベーションが可能。全てのコンセンサスルールはフルノードによって最終的に強制されるため、最終的に新しいソフトフォークがエコノミーによって強制される。この提案は、これら2つの側面を組み合わせて合理的な期間後にフラグデイによるアクティベーションを提供すると共に、フラグデイ以前でも大部分のハッシュレートによるアクティベーションを有効にする。

ブロック高ではなくタイムスタンプを使っているため、ハッシュレートが突然減少するとアクティベートが遅れる可能性がある。

ブロック時間についてはいささか信頼性が低く、意図的にもしくは意図せず不正確な時間になる可能性があるため、ブロック時間に基づく閾値は理想的ではない。第二に、BIP-9では指定した時間後の最初のretarget periodに基づいてトリガーがを指定しているが、これは直感的ではない。新しいブロックはそれぞれ1つずつ高さが増えていくので、ブロック高に基づく閾値の方がはるかに信頼性が高く直感的で、難しいretargetを正確に計算することができる。

仕様

パラメータ

各ソフトフォークのデプロイは、以下のチェーン毎のパラメータによって指定される(以下で詳しく説明)。

  1. nameは、ソフトフォークの非常に簡単な説明を示し、識別子としての使用に適している。単一のBIPで説明されているBIPの場合、「bipN」という名前の使用を推奨する。Nは適切なBIP番号。
  2. bitは、ブロックのnVersionフィールドのどのビットを使って、ソフトフォークのロックインおよびアクティベーションを通知するか決定するもので、{0, 1, 2, ..., 28}の中から選択される。
  3. startheightは、bitが意味を持つ最初のブロックの高さを指定する。
  4. timeoutheightは、マイナーの通知が終了するブロックの高さを指定する。この高さになった後、(このブロックのbitの状態を除いて)まだソフトフォークがロックインされていない場合、デプロイは全ての後続のブロックで失敗したとみなされる(ただし、FAILING状態の例外を参照)か、lockinontimeoutがtrueの場合はロックイン状態に遷移する。
  5. lockinontimeoutのブール値にtrueがセットされた場合、timeoutheightで状態がロックインもしくはアクティブでない場合、状態をロックインに遷移する。

ガイドラインの選択

ソフトフォークでこれらのパラメータを選択するために以下のガイドラインが推奨されている:

  1. nameは、2つのソフトフォークが並行もしくは別の方法で同じ名前を使用しないように選択する必要がある。
  2. bitは、2つのソフトフォークが同時に同じビットを使用することがないよう選択する必要がある。
  3. startheightは、ソフトフォークの実装を含むソフトウェアのリリース日から約30日後など、未来のブロック高を設定する必要がある。これによりリリース前のソフトウェアを実行している参加者の結果としてのトリガーを防ぎながら、ある程度のリリースの遅れを許容し、アクティベーション前に妥当な数のフルノードがアップグレードされることを保証する。単純にするため、retarget periodの開始となるブロック高に切り上げする必要がある。
  4. timeoutheightは、1年もしくはstartheightから52416ブロック(26 retargetインターバル分)にする必要がある。
  5. lockinontimeoutは、無視できない割合のマイナーからの政治的反対が予想されるもしくは発見されたソフトフォークに対してはtrueに設定しなければならない。(これは最初のデプロイ後に設定できるが、一度設定するとクリアできない)

startheightが前のソフトフォームのtimeoutheightもしくはアクティベーション後であれば同じビットを使ってデプロイすることは可能だが、必要となるまでは推奨しない。またバグのあるソフトウェアを検出するために、一時的に使用しないことを推奨する。

状態

各ブロックとソフトフォークで、デプロイの状態を関連付ける。可能な状態は以下の通り。

  1. DEFINEDは、各ソフトフォークが最初に始まる際の初期状態。ジェネシスブロックは定義上、各デプロイのこの状態にある。
  2. STARTEDは、startheightを超えたブロックに対する状態。
  3. LOCKED_INは、nVersionにセットされた関連ビットが閾値を満たしたSTARTEDブロックの最初のretarget periodの次のretarget periodの状態、もしくはlockinontimeoutがtrueに設定されタイムアウトとなった後の最初のretarget periodにおける状態。
  4. ACTIVEは、LOCKED_IN retarget peroidの後の全ブロックの状態。
  5. FAILINGは、LOCKED_INに達せずlockinontimeoutがfalseの場合に、タイムアウト後の最初のretarget periodの状態。
  6. FAILEDは、FAILING retarget periodの後の全ブロックの状態。

ビットフラグ

ブロックヘッダーのnVersionフィールドは、32ビットのリトルエンディアン整数として解釈され、ビットはこの整数内の値(1 << N)として選択される。Nはビット番号。

STARTED状態のブロックは、位置ビットのビットが1に設定されたnVersionを取得する。そのようなブロックの上位3ビットは001でなければならないため、実際に可能なnVersionの値の範囲は[0x20000000...0x3FFFFFFF]となる。

BIP 34, BIP 66, BIP 65によって課せられた制約により、利用可能なnVersionの値は、0x7FFFFFFBとなる。これにより同時展開可能なソフトフォークは最大30個に制限される。上位3ビットを001に制限することで、本提案ではその内の29個を得ることができ、将来異なる仕組みの2つのアップグレード(上位ビットが010と011)をサポートすることができる。ブロックのnVersionの先頭ビットが001でない場合、デプロイとしては全てのビットが0として扱われる。

マイナーはLOCKED_INフェーズのビットの設定を継続し、ソフトフォークの取り込みが認められ表示される必要がある。該当するビットが設定されていないブロックは、この期間中無効となる。柔軟性のため、このルールでは上位3ビットを特定の方法で設定する必要はない。

新しいコンセンサスルール

各ソフトフォークの新しいコンセンサスルールはACTIVE状態の各ブロックに適用される。

状態遷移

https://github.com/bitcoin/bips/raw/master/bip-0008/states.png

STARTED状態の間、lockinontimeoutがtrueに設定されている場合、timeoutheightに達すると状態がLOCKED_INに遷移する。

ジェネシスブロックは定義により、各デプロイに対してDEFINED状態を持つ。

State GetStateForBlock(block) {
        if (block.height == 0) {
                return DEFINED;
        }

1つのretarget period中のブロックは全て同じ状態となる。floor(block1.height / 2016) = floor(block2.height / 2016)の場合、デプロイ毎に同じ状態になることが保証される。

        if ((block.height % 2016) != 0) {
            return GetStateForBlock(block.parent);
        }

それ以外の場合、次の状態は前の状態によって異なる。

        switch (GetStateForBlock(GetAncestorAtHeight(block, block.height - 2016))) {

開始ブロック高になるまでは、初期状態のまま。

        case DEFINED:
            if (block.height >= startheight) {
                return STARTED;
            }
            return DEFINED;

STARTED状態の期間の後は、設定されたビットを集計し、過去の期間の十分な数のブロックがバージョン番号内にデプロイのビットを設定した場合はLOCKED_INに遷移する。閾値は1916ブロック以上(2016の95%)、testnetでは1512ブロック以上(2016の75%)。閾値に達しておらずタイムアウトを迎えた場合は、lockinontimeoutがtrueの場合LOCKED_INに遷移し、そうでなければFAILINGに遷移するかのどちらかだ。

ブロックの状態が自身のnVersionに依存することはなく、祖先のブロックのnVersionにのみ依存する。

        case STARTED:
            int count = 0;
            walk = block.parent;
            for (i = 0; i < 2016; i++) {
                walk = walk.parent;
                if (walk.nVersion & 0xE0000000 == 0x20000000 && (walk.nVersion >> bit) & 1 == 1) {
                    count++;
                }
            }
            if (count >= threshold) {
                return LOCKED_IN;
            } else if (block.height >= timeoutheight) {
                return (lockinontimeout == true) ? LOCKED_IN : FAILING;
            }
            return STARTED;

タイムアウト(またはlockinontimeout)によりLOCKED_INになっていない場合、全てのブロックの満場一致の通知により、デプロイがアクティブになる可能性がある1つのretarget periodがある。この状態はlockinontimeoutが後にtrueに設定された場合に、元のデプロイとの互換性を維持するために存在する。

        case FAILING:
            walk = block;
            for (i = 0; i < 2016; i++) {
                walk = walk.parent;
                if (walk.nVersion & 0xE0000000 == 0x20000000 && ((walk.nVersion >> bit) & 1) != 1) {
                    return FAILED;
                }
            }
            return ACTIVE;

LOCKED_INのretarge periodの後は、自動的にACTIVEに遷移する。

        case LOCKED_IN:
            return ACTIVE;

またACTIVEとFAILEDは最終状態で、そこに到達するとデプロイの状態はそのままになる。

        case ACTIVE:
            return ACTIVE;
        case FAILED:
            return FAILED;
        }
}

実装

状態はブロックチェーンの分岐に沿って維持されるが、再編成が発生した際に再計算が必要になる場合があることに注意すること。

特定のブロック/デプロイメントの組み合わせの状態は、現在のretarget period前の祖先(つなりblock.height - 1 - (block.height % 2016)を持つ祖先まで)によって完全に決定される。親によってインデックスされた2016の倍数のブロックの結果の状態をキャッシュすることで、上記の仕組みを効率的かつ安全に実装できる。

警告の仕組み

アップグレードの警告をサポートするため、暗黙のビットmask = (block.nVersion & ~期待するバージョン) != 0を使って、未知のアップグレードが追跡できる。nVersionに予期しないビットが設定されると、maskは0以外の値になる。未知のアップグレードに対するLOCKED_INが検出される度に、ソフトフォークが行われることを大きな警告をする必要がある。次のretarget periodになると(未知のアップグレードがACTIVE状態の場合)、さらに大きな警告をする必要がある。

getblocktemplateの変更

template requestオブジェクトが新しいアイテムを含むよう拡張される。

template request

キー 必須 タイプ 定義
rules No Stringの配列 nameを使ったサポートするソフトフォークのデプロイのリスト

templateオブジェクトも拡張される。

template

キー 必須 タイプ 定義
rules Yes Stringの配列 nameを使ったアクティブ状態のサポートするソフトフォークのデプロイのリスト
vbavailable Yes オブジェクト サポートするソフトフォークのデプロイの保留中のセット。それぞれがキーとしてnameを使い値としてソフトフォークビットを使う。
vbrequired No 数値 送信でサーバーが有効にする必要があるソフトフォークのデプロイビットのビットマスク

テンプレートの「version」キーは保持され、サーバーのデプロイ設定を示すのに使用される。versionbitsが使用されている場合、「version」は必ず[0x20000000...0x3FFFFFFF]のversionbitsの範囲内である必要がある。マイナーは特別な「mutable」キーなしで、ブロックのバージョンをクリアもしくは設定することができるが、それらがテンプレートの「vbavailable」にリストされているか、(クリアの場合は)「vbavailable」にリストされていない必要がある。サーバーは生成されたブロックが有効であることを確認するために、LOCKED_INのデプロイに対して「vbrequired」ビットを設定しなければならない。

「rules」もしくは「vbavailable」のキーとしてリストされているソフトフォークのデプロイ名の前に「!」を付与することができ、このプレフィックスが無い場合、GBTクライアントはルールがテンプレートの使用に影響を与えないことを想定している可能性がある。この典型的な例は、BIP 16,65, 66, 68, 112, 113のように以前は有効だったトランザクションが有効でなくなった場合。クライアントがプレフィックス無しのルールを理解しない場合、マイニングにそれらを変更せずに使用する可能性がある。一方、このプレフィックスが使われている場合、ブロックの構造や生成するトランザクションに対する変更を示す。この例は、(コインベースの構成を変更した)BIP-34や(tixdのハッシュの変更および生成するトランザクションへのコミットを追加した)BIP 141だ。「!」のプレフィックスルールを理解しないクライアントは、テンプレートを処理してはならないし、変更されていない場合もそれをマイニングに使ってはならない。

参照実装

https://github.com/bitcoin/bitcoin/compare/master...luke-jr:bip8

BIP 9との比較

  • lockinontimeoutフラグが追加された。BIP 9ではタイムアウトに達するとFAILED状態に遷移するだけ。
  • デプロイのクロックとして、median-time-pastではなくブロック高が使用される。
  • 新しく最後の努力が行えるFAILING状態が追加され、初期デプロイ後にlockinontimeoutが安全に設定できるようになった。

後方互換

BIP 8およびBIP 9によるデプロイでは、有効なデプロイビットを共有してはならない。BIP 9のみを実装しているノードは、ハッシュパワーの閾値がtimeoutheightまでに到達していない場合、BIP 8ソフトフォークをアクティベートしないが、アクティベートしたノードが生成したブロックは受け入れる。

デプロイ

BIP 8を使ったデプロイ提案の現在のリストはこちら

WTXIDを使ったP2Pリレーの仕様を定義したBIP-339

Bitcoinトランザクションをリレーする際は、接続中のピアに対してinvメッセージでトランザクションを通知し、相手のピアがそのトランザクションを持っていない場合、getdataメッセージでそのトランザクションデータを要求する。この際のトランザクションの識別子にはtxidが使われている。この振る舞いはSegwit導入後も同じまま。

Segwitトランザクションでは、インプットの署名データはwitnessと呼ばれる領域に移され、txidを計算する際にこのwitnessのデータは含まれない。つまりtxidはwitnessのデータにコミットしていない。このtxidとは別にwitnessのデータも含めて計算しているのがwtxidトランザクションデータの内、唯一改変が可能なデータがインプットのscriptSigのデータだったのが、SegwitによりscriptSigは空になったので、txidのmalleabilityは排除されたが、wtxidにはmalleabilityは存在する。

この状況では、Segwitトランザクションをリレーする際、受信したトランザクションがポリシー違反であったり無効な場合でも、そのトランザクションをダウンロードする必要がある。この時、同じtxidトランザクションが再度通知されても、実はwitnessのデータが変わっていて、その場合正しいトランザクションかもしれないので、再度ダウンロードしてチェックする必要がある。txidだけではwitnessに変更があるのかどうか判断できないためだ。こういった妨害の懸念を解消するために、トランザクションリレーにwtxidを使おうという提案が、BIP-339の内容↓

https://github.com/bitcoin/bips/blob/master/bip-0339.mediawiki

新しくwtxidrelayというペイロードが空のメッセージを追加し、ハンドシェイク時に送信することでinvgetdataで使う識別子をwtxidにする模様。以下、BIPの意訳。

概要

このBIPは、トランザクションのtxidではなく、BIP-141のwtxidをベースにしたトランザクションリレーをサポートするためのP2Pプロトコルの2つの変更を記述する。

動機

歴史的に、BitcoinP2Pネットワーク上でトランザクションを通知するために送信されるINVメッセージは、そのトランザクションのハッシュであるtxidによってトランザクションを参照していたが、これはwitnessを含まないトランザクションのハッシュである(BIP-141参照)。これはSegregated Witness(BIP 141/143/144)がネットワークで採用されてからも同様だ。

witnessにコミットしないトランザクションの通知は、非効率性を生む。トランザクションのwitnessはtxidを変更することなく変更できるため、ノードが受け入れないwitnessトランザクションを受信したノードは、他のピアによって通知された場合、通常は同じトランザクションをダウンロードする。これは、そのtxidでトランザクションを拒否した後で、特定のtxidをダウンロードしないという代替手段は、第三者トランザクションwitnessを悪用し、結果無効なトランザクションをノードに通知することで、トランザクションリレーを妨害し、有効なトランザクションのリレーも同様に妨害するからである。

トランザクションの通知や取得時にwtxidを使用することでこの懸念を解消することができる。

仕様

  1. 新しくwtxidrelayメッセージを追加する。これはpchCommand == "wtxidrelay"の空のメッセージとして定義される。
  2. このBIPを実装するノードのプロトコルバージョンは70016以上に設定する必要がある。
  3. wtxidrelayメッセージは、プロトコルバージョンが70016以上のピアからのVERSIONメッセージへの応答でVERACKメッセージを送信する前に送信する必要がある。
  4. INVメッセージおよびGETDATA要求の両方で使用するため、参照されるハッシュがトランザクションのwtxidであることを示すため、新しいinvタイプMSG_WTX (0x00000005)が追加された。GETDATA要求の場合、MSG_WTXは要求されているトランザクションが同様にBIP-144で定義されるwitnessでシリアライズされるべきであることを意味する。
  5. ノードがピアとの間でwtxidrelayメッセージを送受信した後は、ノードはそのピアに対するトランザクションの通知や、そのピアから通知されたトランザクションを要求する際、MSG_WTXinvタイプを使用することが求められる。

後方互換

wtxidベースのトランザクションリレーは、両方がサポートするピア間でのみ有効になるため、この変更後も古いクライアントは完全に互換があり、相互運用可能だ。

実装

https://github.com/bitcoin/bitcoin/pull/18044

BIP-32のキーチェーンから決定論的にエントロピーを導出する仕様BIP-85

Bitcoinの鍵を管理するウォレットでは、BIP-32のHDウォレットの仕様↓をサポートし、マスターシードから取引に使用する個別の鍵を導出するようになっているのがほとんど。

techmedia-think.hatenablog.com

さらにBIP-39の仕様を利用してマスターシードを12個、24個の単語(ニーモニック)にして、ユーザーにバックアップさせることが多い。

これらの仕様により鍵のバックアップは随分簡単になったけど、

  • Bitcoin CoreのウォレットはHDウォレットはサポートしているけど、BIP-39のニーモニックはサポートしていない。
  • Bitcoinのウォレットでも例えばElectrumはBIP-39との互換性がないなど、ウォレット間で必ずしも同じ仕様に準拠していない。
  • さらに暗号通貨が異なると採用されているニーモニックの導出仕様も違う。

といった課題があり、複数のウォレットや暗号通貨を利用する場合、結局異なるウォレットで別のマスターシードを作成して、ニーモニックもそれぞれ別に管理するといった不便な状況が発生する。

この課題について、エントロピーからマスターシードを導出する部分はどのウォレットや暗号通貨でも変わらないので、BIP32のキーチェンの仕様をエントロピーを導出する仕様に使うことで、↑の課題を解決しようと新しく提案されたのがBIP-85↓

https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki

このBIPではBIP-32のキーチェンの仕組みを使って、各アプリケーション用に(以下は現在定義されているアプリケーション)、エントロピーを導出できるようにしている。

そのためユーザーはBIP-85のマスタールートキーのみをバックアップしておけば(直接保存しても、これをBIP-39のニーモニックで保存してもOK)、各アプリケーション用のエントロピーを復元することができる。

※ なお、元となる初期エントロピーの生成方法については、このBIPでは特に規定していない。

以下、BIPの意訳↓(ちなみにパスに出てくる83696968という数字は、SEEDという文字列のASCIIコード表現) 2021/02/12時点の内容で更新

概要

「1つのシードですべてを支配し、1つの鍵がすべてを見つけ、1つのパスがすべてをもたらし、暗号でそれらを縛る」

いろんなウォレットで使用される全てのキーチェーンについて、単一の(ニーモニックのような)シードバックアップを維持することは、互換性のないいろんな規格があるため不可能だ。複数のウォレットにまたがったシードの共有はセキュリティ上の理由から望ましくない。そして複数のシードを物理的に保管することは、必要とされるセキュリティや冗長性に応じて困難だ。

HDキーチェーンは基本的に初期エントロピーから導出されるので、この提案では、ウォレットが初期ニーモニックシードやルートキーを導出するために使用することができるエントロピーをキーチェーンから導出する方法を提供する。

定義

世間で使われているキーチェーンに関する用語は多岐にわたっている。例えば、「シード」はいろんな意味を持っている。この文書では、以下の用語を定義する。

  1. BIP32ルートキーは、BIP32におけるキーチェーンのトップルートとして表されるルート拡張秘密鍵である。
  2. BIP39ニーモニックは、エントロピーから計算されたニーモニックフレーズで、BIP39でニーモニックのハッシュの前に(ニーモニックシードを算出するのに)使われる。
  3. BIP39ニーモニックシードは、BIP39ニーモニックシードををハッシュした結果である。

動機

ほとんどのウォレットは、BIP32ルートキーを使ってキーチェーンを導出する方法を定義しているBIP32を実装している。その結果、BIP32ルートキーのバックアップだけで、そこから導出した全ての鍵が含まれていることになる。BIP32では、BIP32ルートキー(もしくは通常BIP32拡張鍵と言われる)を人間が使いやすいようシリアライズする機能は持っていないため、紙でのバックアップや手入力で鍵を復元するとエラーが発生しやすくなる。BIP39はこの問題を解決するために設計されたが、BIP32ルートキーをシリアライズするのではなく、「シードニーモニック」にエンコードされたエントロピーを受け取り、これをハッシュしてBIP32ルートキーに変換することができるBIP39シードを導出する。BIP39ニーモニックを保存すれば、BIP32キーチェーン全体を再構築するのに十分だが、BIP32ルートキーをBIP39ニーモニックに戻すことはできない。

ほとんどのウォレットはBIP39を実装しているため、初期化や復元の際にはBIP39ニーモニックを使わなければならない。ほとんどのウォレットはBIP32拡張秘密鍵をサポートしていないので、各ウォレットは同じBIP39ニーモニックを共有するか、全く別のBIP39ニーモニックを持つ必要がある。どちらのケースもセキュリティ上の理由から満足できるものではない。例えば、スマートフォン上で動作するホットウォレットやJoin Marketサーバー、Lightning Networkノードのように本質的に低セキュリティなウォレットがある。複数のシードを持つことは、特に地理的に異なる場所にある分割した鍵や冗長バックアップに依存している人にとっては望ましくない。シードを追加するのにハードルがあるため、後の鍵の追加ほど怠惰になる可能性があり、ユーザーがセキュリティを損なうか鍵の損失につながるような可能性がある。

他の規格を実装しているウォレットはや、まったく規格のないウォレットでは、さらに複雑さが増す。Bitcoin CoreウォレットはhdseedとしてWIFを使っているが、他のウォレットではBIP32ルートキーを導出するのにElectrumのように異なるニーモニックスキームを使用しているケースもある。Moneroのような他の暗号通貨も、まったく異なるニーモニックスキームを使っている。

最終的に、すべてのニーモニック/シードスキームは、ニーモニック/シードを導出するための「初期エントロピー」から始まって、ニーモニックをBIP32ルートキーもしくは秘密鍵にする。我々は、BIP32自体を使って「初期エントロピー」を導出し、ターゲットとなるウォレットの特定のアプリケーション標準に従って、同じニーモニックやシードを再作成することができる。BIP44のような分類を使って、ターゲットアプリケーションタイプに応じた鍵の導出を確実にすることができる。

仕様

我々は単一のBIP32マスタールートキーを想定する。この仕様では、それがどうやって導出されたか(直接なのか、BIP39などのニーモニックスキームを介してなのかなど)には関係しない。

独自のウォレットを必要とする各アプリケーションに対して、完全な強化導出パスを使ってBIP32マスタールートキーから一意の秘密鍵が導出される。結果として得られた秘密鍵(k)は、続いてHMAC-SHA512で処理される。なお、この時に使用される鍵はbip-entropy-from-kで、メッセージペイロード秘密鍵kを使う、つまりHMAC-SHA512(key="bip-entropy-from-k", msg=k)。結果、512 bitのエントロピーが生成される。各アプリケーションは、操作に必要なbit数を使用し、残りは切り捨てる。

HMAC-SHA512関数はRFC 4231で定義されている。

Test Vectors

テストケース1

入力:

  • マスターBIP32ルートキー:
    xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
  • PATH: m/83696968'/0'/0'

出力:

  • 導出鍵:
    cca20ccb0e9a90feb0912870c3323b24874b0ca3d8018c4b96d0b97c0e82ded0
  • 導出されたエントロピー
    efecfbccffea313214232d29e71563d941229afb4338c21f9517c41aaa0d16f00b83d2a09ef747e7a64e8e2bd5a14869e693da66ce94ac2da570ab7ee48618f7
テストケース2

入力:

  • マスターBIP32ルートキー:
    xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
  • PATH: m/83696968'/0'/1'

出力:

  • 導出鍵:
    503776919131758bb7de7beb6c0ae24894f4ec042c26032890c29359216e21ba
  • 導出されたエントロピー
    70c6e3e8ebee8dc4c0dbba66076819bb8c09672527c4277ca8729532ad711872218f826919f6b67218adde99018a6df9095ab2b58d803b5b93ec9802085a690e

BIP85-DRNG

BIP85-DRNG-SHAKE256は、決定論的な出力を必要とするが、その関数への入力がBIP85のHMAC出力によって提供される64バイト以上を必要とする暗号関数用の決定論的な乱数生成器である。BIP85-DRNG-SHAKE256は、(SHA-3標準の)SHAKE256ストリームをシードするためにBIP85を使用する。入力は正確に(BIP85のHMAC出力からで)64バイトでなければならない。

RSA鍵生成は64バイト以上のランダムな入力を必要とする機能の一例。さらに関数が完了するまで、必要なランダム入力の量を事前に計算することはできない。

drng_reader = BIP85DRNG.new(bip85_entropy)
rsa_key = RSA.generate_key(4096, drng_reader.read())

Test Vector

入力:xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb

  • マスターBIP32ルートキー:m/83696968'/0'/0'

出力:

  • 導出鍵:cca20ccb0e9a90feb0912870c3323b24874b0ca3d8018c4b96d0b97c0e82ded0
  • 導出されたエントロピー:efecfbccffea313214232d29e71563d941229afb4338c21f9517c41aaa0d16f00b83d2a09ef747e7a64e8e2bd5a14869e693da66ce94ac2da570ab7ee48618f7
  • DRNG(80バイト):b78b1ee6b345eae6836c2d53d33c64cdaf9a696487be81b03e822dc84b3f1cd883d7559e53d175f243e4c349e822a957bbff9224bc5dde9492ef54e8a439f6bc8c7355b87a925a37ee405a7502991111

参照実装

他の実装

アプリケーション

アプリケーション番号は、エントロピーが後続のプロセスでどのように使用されるかを定義する。以下に基本的な例を示す。

導出パスm/83696968/' + /app_no' + /index'を使用する場合、app_noはアプリケーションのパスで、indexはアプリケーション内のインデックスを表す。

m/83696968'/39'/0'/12'/0'は12個の英単語を持つBIP39ニーモニックの(最初のインデックスの)パスで、次のキーのパスはm/83696968'/39'/0'/12'/1'

BIP39

アプリケーション番号: 39'

エントロピーの末尾(最下位)のバイトについては、関連する単語の長さにマップするのに必要なビット数に切り詰める。12単語の場合は128 bit、24単語の場合は256 bit。

導出パスのフォーマットは:m/83696968'/39'/{language}'/{words}'/{index}'

12英単語のBIP39ニーモニックの最初のインデックスのパスはm/83696968'/39'/0'/12'/0'で、次の鍵のパスはm/83696968'/39'/0'/12'/1'

言語表

単語リスト コード
English 0'
Japanese 1'
Korean 2'
Spanish 3'
Chinese (Simplified) 4'
Chinese (Traditional) 5'
French 6'
Italian 7'
Czech 8'

単語表

単語|エントロピー|コード 12単語|128 bit|12' 18単語|192 bit|18' 24単語|256 bit|24'

12英単語

BIP39の12英単語ニーモニックシード

12単語のニーモニックを導出するためBIP39への入力として128 bitのエントロピーが必要

入力:

  • マスターBIP32ルートキー:
    xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
  • パス: m/83696968'/39'/0'/12'/0'

出力:

  • 導出鍵: 6250b68daf746d12a24d58b4787a714b
  • 導出されたBIP39ニーモニック: girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose
18英単語

BIP39の18英単語ニーモニックシード

18単語のニーモニックを導出するためBIP39への入力として196 bitのエントロピーが必要

入力:

  • マスターBIP32ルートキー:
    xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
  • パス: m/83696968'/39'/0'/18'/0'

出力:

  • 導出鍵: 938033ed8b12698449d4bbca3c853c66b293ea1b1ce9d9dc
  • 導出されたBIP39ニーモニック: near account window bike charge season chef number sketch tomorrow excuse sniff circle vital hockey outdoor supply token
24英単語

BIP39の24英単語ニーモニックシード

24単語のニーモニックを導出するためBIP39への入力として256 bitのエントロピーが必要

入力:

  • マスターBIP32ルートキー:
    xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
  • パス: m/83696968'/39'/0'/24'/0'

出力:

  • マスターBIP32ルートキー:
    xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
  • パス: m/83696968'/39'/0'/18'/0'

出力:

  • 導出鍵: 938033ed8b12698449d4bbca3c853c66b293ea1b1ce9d9dc
  • 導出されたBIP39ニーモニック: near account window bike charge season chef number sketch tomorrow excuse sniff circle vital hockey outdoor supply token
  • 導出鍵: ae131e2312cdc61331542efe0d1077bac5ea803adf24b313a4f0e48e9c51f37f
  • 導出されたBIP39ニーモニック: puppy ocean match cereal symbol another shed magic wrap hammer bulb intact gadget divorce twin tonight reason outdoor destroy simple truth cigar social volcano

WIF形式のHDシード

アプリケーション番号: 2'

256 bit*1エントロピーをsecret exponentとして秘密鍵を導出し、Bitcoin Coreウォレットのhdseedとして使用される圧縮WIFとしてエンコードする。

BIP32より、parse256(IL) ≥ n もしくは ki = 0の場合、結果の鍵は無効で、iを次の値に進める必要がある(これは1/2127 よりも低い確率)。

パスフォーマット: m/83696968'/2'/{index}'

入力:

  • マスターBIP32ルートキー:
    xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
  • パス: m/83696968'/2'/0'

出力:

  • 導出鍵: 7040bb53104f27367f317558e78a994ada7296c6fde36a364e5baf206e502bb1
  • 導出されたWIF: Kzyv4uF39d4Jrw2W7UryTHwZr1zQVNk4dAFyqE6BuMrMh1Za7uhp

XPRV

アプリケーション番号: 32'

(BIP32の拡張ルート秘密鍵の導出)HMACダイジェストの64バイトについて、最初の32バイトがchaincode、後半の32バイトがBIP32 XPRV値の秘密鍵となる。子どもの番号、深さ、親のfingerprintは強制的に0になる。

パスフォーマット: m/83696968'/32'/{index}'

入力:

  • マスターBIP32ルートキー:
    xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
  • パス: m/83696968'/32'/0'

出力:

  • 導出鍵: ead0b33988a616cf6a497f1c169d9e92562604e38305ccd3fc96f2252c177682
  • 導出されたWIF:
    xprv9s21ZrQH143K2srSbCSg4m4kLvPMzcWydgmKEnMmoZUurYuBuYG46c6P71UGXMzmriLzCCBvKQWBUv3vPB3m1SATMhp3uEjXHJ42jFg7myX

HEX

アプリケーション番号: 128169'

導出パスフォーマット: m/83696968'/128169'/{num_bytes}'/{index}

16 <= num_bytes <= 64

num_bytesの後のエントロピーの末尾(最下位)のバイトは切り捨てる。

入力:

  • マスターBIP32ルートキー:
    xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb
  • パス: m/83696968'/128169'/64'/0'

出力:

  • 導出されたエントロピー
    492db4698cf3b73a5a24998aa3e9d7fa96275d85724a91e71aa2d645442f878555d078fd1f1f67e368976f04137b1f7a0d19232136ca50c44614af72b5582a5c

RSA

アプリケーション番号: 828365'

導出パスフォーマット:m/83696968'/828365'/{key_bits}'/{key_index}'

RSA鍵生成器は、入力のRNG関数としてBIP85-DRNGを使用する必要がある。

RS GPG

RSA-GPG用に割り当てられた鍵は、以下のスキームを使用する:

- Main key <code>m/83696968'/828365'/{key_bits}'/{key_index}'</code>
- Sub keys:  <code>m/83696968'/828365'/{key_bits}'/{key_index}'/{sub_key}'</code>
  • key_indexはCERTIFY機能の親鍵
  • sub_key 0' は、暗号化鍵として使われる。
  • sub_key 1' は、認証鍵として使われる。
  • sub_key 2' は、署名鍵として使われる。

タイムスタンプに関する注意:

生成されたRSA鍵は、鍵のFingerprintが作成日に影響されるため、作成日をUnixエポックタイムスタンプ1231006505(Bitcoinジェネシスブロックの時間'2009-01-03 18:05:05' UTC)に固定しなければならないGPG鍵を作成するのに使用することができる(古い鍵のGNUPG実装ではレガシーな動作のため、エポックタイムスタンプ0は選択されなかった)。さらに、GNUPGにおいて鍵の下にあるサブキーをインポートする差異には、インポートする前に(faketimeなどを使って)システム時刻を同じタイムスタンプに固定しなければならない。

スマートカード/ハードウェアデバイスのGPG鍵機能に関する注意:

GPG対応のスマートカードは次のようにロードする必要がある。暗号化スロットはENCRYPTION対応の鍵でロードされるべきで、認証スロットはAUTHENTICATION対応の鍵でロードされる必要がある。署名可能なスロットにはSIGNATURE対応の鍵をロードする必要がある。

しかし、スマートカード上の利用可能なスロットおよび好ましいポリシーによっては、CERTIFY対応の鍵はCERTIFYおよびSIGNATURE機能でフラグが立てられ、SIGNATURE対応のスロットにロードされることがある(例えば、スマートカードに3つのスロットしかなく、同じカードにCERTIFY機能が必要な場合など)。この場合、CERTIFY対応の鍵が二重で目的を果たすため、SIGNATURE対応のサブキーは無視される。

後方互換

この仕様については、他の既存の仕様は存在しないため、後方互換性はない。

この仕様はBIP32に依存しているが、BIP32ルートキーの導出方法には依存していない。そのため、この標準はBIP39やElectrumウォレットスタイルのニーモニックのような初期化スキームでウォレットの導出を可能にしている。

ディスカッション

HMAC-SHA512を使って鍵を導出し、必要に応じて使用しない後続バイトを削除する理由は、導出鍵(k)が危殆化した場合に親ツリーが漏洩するのを防ぐため。仕様ではこれを防ぐために強化導出の使用を要求しているが、強化導出を強制することはできない。ただしこの方法であれば導出されたエントロピーが確実に強化される。また意味的な観点からも、目的は秘密鍵を導出することではなくて、エントロピーを導出することなので、子鍵を変換する必要がある。これは二重の目的でkを使用する必要がある場合(nonce hash(k)を含めると望ましくない予期せぬ作用が発生する可能性がある)、不要な副作用を防ぐために十分な注意を払って動作する。

*1:ゼロもしくは曲線の位数より大きい無効な鍵を作成する可能性は僅かで、この場合ソフトウェアは完全に失敗し、ユーザーは次のインデックスまで繰り返す必要がある。

Segwit Bugとは何なのか?

Segwit Bugというタイトルでニュースが出ており

decrypt.co

Trezorでも脆弱性対応のパッチが公開されていた↓

https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd

ので、どんな脆弱性なのか見てみよう。

脆弱性の内容

まず、この脆弱性には、Segwit導入時に一緒に導入された署名時のメッセージダイジェストの生成方法の変更BIP-143が関係する↓

techmedia-think.hatenablog.com

非Segwitのトランザクションについて、TrezorはインプットのUTXOの量を確認することで、ユーザーの意図しない額の送金を防ぐ。Segwitトランザクションでは署名を作成する際に、BIP-143により署名対象のメッセージダイジェストにインプットが参照するコインの量が含まれることになる。そのため署名デバイスはオフラインでも使用するコインの量を必ず知ることができる。もし攻撃者がコインの量を騙そうと偽の量を署名デバイスに渡しても、署名デバイスが作成した署名は無効な署名になる。そういう意味では、BIP-143によるコインの量へのコミットは、オフラインの署名デバイスのセキュリティを向上する仕組みである。

今回、Saleem Rashidによって報告された上記を回避する脆弱性の実行手順は以下の通り。

  1. 被害者は15 BTCと20 BTCのSegwit UTXOを持っている。
  2. マルウェアが被害者に以下のインプットを持つ合計20.00000001 BTC使用するトランザクションを提示し、承認を求める。
    • インプット1:15 BTC のUTXO
    • インプット2: 5.00000001 BTC のUTXO
  3. 被害者が内容を承認した後、マルウェアはエラーを発生させ再度トランザクションを承認するよう求める。
  4. この時マルウェアはインプットを以下のインプットに置き換えたトランザクションにする。アウトプットは先程と同じ。
    • インプット1: 0.00000001 BTC
    • インプット2: 20 BTCのUTXO
  5. 被害者が確認するも、送金額は先程と同じ20.00000001 BTCであり、被害者は承認する。
  6. この後マルウェアは、トランザクションのインプットを以下のようにすげ替えたトランザクションを作成し、ブロードキャストする。
  7. 結果インプットの合計は15 BTC + 20 BTCになり、15 BTCちょっとが手数料として徴収される。

まとめると、2つ以上のインプットを持つトランザクションで、送金額の値は同じだけど金額の組み合わせが異なるUTXOのセットを2つ用意して、エラーを理由にインプットの組み合わせを変えて二度署名させ、最終的に送金額が最大になる組み合わせのUTXOを選択して、ユーザーの意図する額以上の送金トランザクションを作成させる攻撃。

最初↑の手順を見たときは、最初のトランザクションと2つめのトランザクションでUTXOが変わってるので、つまりインプットが参照するOutPointの値も変わるので、最後のマルウェアが作るトランザクションの署名は無効になるのでは?と思ったけど、最初と2つめのトランザクションのインプットのOutPointは両方とも最初から15 BTCと20 BTCのOutPointで、コインの量だけ変えてるだけか。デバイスがOutpointの値を実際に確認したらバレる。

Trezorの対応

対応は単純でSegwitのトランザクションについても、非Segwitトランザクションと同様、UTXOの金額の検証を行うようにしたと。

オフライン署名デバイスの安全性を向上させるためのメッセージダイジェストの仕様BIP-143だったけど、↑のようなケースを考えるとオフラインデバイスでも従来のトランザクションと同様の検証が求められると。

Trezorは対応したものの、Trezorと連携するアプリ側はまだSegwitトランザクションの場合UTXOの情報を提供しない仕様になっていることが多いので、そういったサードパーティのツールと連携している場合は注意が必要。

特にマルチパーティ間でトランザクションデータを連携する際の標準仕様であるPSBTをサポートしている場合、Segwitトランザクションの場合UTXOが参照するトランザクション情報は保持しない仕様になっているので、PSBT単体で評価せずにオンラインデバイスの場合はUTXOのトランザクション情報を確認するようにした方がいい。

Schnorr/Taprootによる改善

現在導入が検討されているSchnorr/Taprootでは、Schnorr署名のメッセージダイジェストの作成方法がBIP-143とは異なり、新しくBIP-341で定義されている。BIP-341ではメッセージダイジェストに、全インプットが参照するUTXOのコインの量が含まれるようになるため(↓のsha_amounts)、↑のような攻撃はできなくなる。

techmedia-think.hatenablog.com

BCHもBIP-143をサポート

ちなみにBitcoin Cashもハードフォークの際にリプレイプロテクションを導入するにあたって、BIP-143の内容を参考にしたメッセージダイジェストを生成するようになってる↓

https://github.com/bitcoincashorg/bitcoincash.org/blob/a86ce75cbdce8b14fa90b2315ebc7f0d78e6c9cb/spec/replay-protected-sighash.md

このためBCHのトランザクションもコインの量に署名でコミットするようになっている。ただ、BCH側も影響があるかどうかは、Trezorの確認方法がBitconのSegwitトランザクションと同様の確認方法になっていたかどうかによる。非Segwitトランザクションと同様のUTXOが参照するトランザクション情報の提示を求める仕様になっているのであれば影響はないが、同じ方法で検証しているのであれば影響する。

まとめ&所感

  • 影響があるのはBIP-143の仕様を採用し、UTXOのトランザクション情報を要求しないハードウェアウォレット。
  • 同様にPSBTを採用しているツールは、Segwitトランザクションの場合UTXOのトランザクション情報はPSBTフォーマットにはないので追加のチェックが必要。
  • 問題となったのはSegwit導入時に一緒に導入された新しい署名ダイジェストの計算仕様BIP-143のオフラインデバイスでの使用。
  • こうなるとPSBTの仕様についても、Segwitトランザクションでも(PSBT_IN_NON_WITNESS_UTXOキーみたいに)UTXOのトランザクションを保持するような設計変更が必要では?