Develop with pleasure!

福岡でCloudとかBlockchainとか。

Grinにおけるリプレイアタックとその対策

リプレイアタックというと、チェーンがハードフォークした際に、片方のチェーンで発生した送金Txが、もう一方のチェーンで意図せずブロードキャストされる=リプレイされる問題として2017年のフォークコイン誕生の際によく話題に挙がった。

ハードフォークとは関係なく、そんなリプレイアタックがGrinで可能であるとして今ホットなトピックになっている↓

forum.grin.mw

Grinにおけるリプレイアタック

GrinなどのMimmblewimbleを採用したブロックチェーンでは、UTXOは量を秘匿した以下のようなPedersen Commitmentとして認識される。

C = rG + vH

Gはベースポイント、Hは誰もその離散対数を知らない2つめのベースポイントで、rがブラインドファクター、vがコインの量。

Bitcoinの場合、使用するUTXOを指定する際はTXIDとアウトプットのインデックスを結合した36バイトのOutPointが使われるが、Mimblewimbleの場合は単純に↑のコミットメントCが使われる。どのTxの何番目のアウトプットという指定の方法ではない。そのような指定方法をするとMimblewimbleの特徴の1つでもあるトランザクションカットスルーが行えなくなる。

リプレイアタックのシナリオ

↑の特性を悪用し、アリスとチャーリーと共謀して(チャーリーはアリス自身でもOK)、ボブを騙すケースを考える。

  1. アリスはボブが提供する商品を購入するため、自身のUTXO Aをボブに送金する(送金先のアウトプットをBとする)トランザクション Tx1(A→B)をボブと協力して作成し、ブロードキャストする。
  2. ボブはアウトプットBを使ってチャーリーに支払いをするトランザクションTx2(B→C)をチャーリーと協力して作成し、ブロードキャストする。
  3. アリスは再度ボブから商品を購入するため、Tx3をボブと協力して作成するが、アリスはこの時Tx3をブロードキャストすることなく、代わりにTx1をリプレイする。
  4. ボブのウォレットは再度Bを受け取り残高として認識する。この時Tx3がウォレットで保留中などと表示されない場合、ボブは支払いを受け入れる。
  5. アリスは3で注文した長品が届いたら、今度はTx2をリプレイすることで3で支払った料金を回収する。

リプレイアタックの要因

↑のリプレイアタックが成功するのは以下の要因による:

  • GrinではUTXOの重複は許可していない(許可しようという提案はあるが)が、使用済みになれば同じコミットメントのUTXOを作ることができる。そのため、Tx1をブロードキャストした後であればUTXO Aを再度作れるし、Tx2をブロードキャストした後であれば、Tx1をリプレイできる(=UTXO Bを再度作れる)。
  • トランザクションカーネルの再利用
    トランザクションカーネルは、インプットのコミットメントの合計とアウトプットのコミットメントの合計の差分の公開鍵と、その公開鍵に対して有効な署名で構成される。つまりインプットのコミットメントの合計とアウトプットのコミットメントの合計の差分が同じトランザクションを構成することができれば、その署名は前のトランザクションカーネルにセットされている署名をそのまま利用することができる。これによりボブの協力なくBを使用するTx2をリプレイできる。

また、リプレイTxのインプットは必ずしも同じインプットでなければならないということもない。例えば↑のAのコミットメントがC = 3G + 5Hとかだった場合(実際にはもっと大きな数値だけど)、C1 = G + 2HC2 = 2G + 3Hとかにすることもできる(もちろんそういったUTXOを予め作っておく必要はあるけど)。

リプレイアタックへの対策

↑のリプレイアタックが発生した場合、ボブのウォレットが3でアリスとボブが新しい支払い用のTx3を共同で作っているので、それがブロードキャストされていない=ペンディング状態にあることをボブにちゃんと表示できていれば、Tx3が確認できるまで、商品を提供しないという選択をボブはすることができる。とはいえ、これはウォレットの実装によるし、ウォレットが破損したりリストアするような状況においては難しい。

プロトコルレベルで保護しようとするのであれば、最近提案もされているが、カーネルの重複を許可しない=一意なカーネル(この結果署名の流用はできなくなる)のルールをコンセンサスルールに組み込むというもの。ただこれを組み入れると、カーネルが重複していないかどうかのチェックのコストが追加で発生する。

その他、既存のコンセンサスルールの下で、仕組み的に↑のリプレイアタックを防ぐために以下のような方法が提案されている。

Anchorアウトプットの利用

PayJoins for Replay Protection · GitHub

リプレイアタックを防ぐために、使用することのないNS(Never-Spend)アウトプット=Anchorアウトプットを利用する。Anchorアウトプットは使用することがなくUTXOのまま存在するアウトプットなのでコインの量は0(コミットメントのv値がゼロ)でいい。

Anchorアウトプットの作成方法は単純で、例えばボブが作るのであれば、以下のような構成のトランザクションTを作ってブロードキャストすればいい。

inputs = [I_Bob]
outputs = [
  Bob_anchor,
  Bob_01,
]

ここでBob_anchorがAnchorアウトプットで、Bob_01は通常のMWアウトプット。このようなUTXOを予め作っておく。

PayJoinトランザクションの構築

リプレイアタックを防ぐために、支払いを受け取る際PayJoinトランザクションを構成する。PayJoinトランザクションBitcoinで「common input ownership」ヒューリスティックを崩しプライバシーを向上させるために提案されたトランザクションの構成方法で、送金の際に受信者のUTXOもインプットに含める方法(以前はP2EP言われてたもの)。

この時、ボブは↑のTで作成したBob_01をインプットとして提供し、支払いを受け取るためアリスと以下のようなTxを作成する。

inputs = [
  Alice_01,
  Bob_01
]
outputs = [
  Alice_output,
  Bob_output
]

支払いを受ける際、Anchorアウトプットと一緒に作成されたUTXO(↑ではBob_01)をインプットとして提供することで、アリスはこのTxをリプレイできなくなる。また、送信者であるアリスも同様にTのようなトランザクションを使ってAnchorアウトプットを作成し、一緒に作成したAlice_01をインプットとすることで、送信者側にもリプレイ保護を与えることができる。

Anchorアウトプットを生成したトランザクションTからのトランザクショングラフを使ってインプットを提供する限り、リプレイのリスクはなくなる。

なぜリプレイできないのか?

MWトランザクションをリプレイするためには、そのインプットとなるコミットメントを再作成できなければならない。つまりアリスの場合、Alice_01Bob_01を再作成しなければならない。Alice_01は自身が秘密鍵を知っているので再作成することができるが、Bob_01を再作成するためには、ボブが作ったTをリプレイしないといけない。ただ、Bob_anchorがUTXOとして存在しているのでTをリプレイすることはできない。

Bitcoinの開発者からすると、Bob_01は単なるアウトプットなので、同じアウトプットにアリスが送金するトランザクション作ればいいんじゃないか?と思うかもしれないが、Mimblewimbleではそういったことはできない。前述したように、MWのトランザクションの署名は、「インプットのコミットメントの合計とアウトプットのコミットメントの合計の差分の公開鍵と、その公開鍵に対して有効な署名で構成される」ため、Bob_01秘密鍵の知識なくBob_01へ送金するトランザクションの署名は作れない。

※ Mimblewimbleトランザクションの署名の仕組みについては↓のGBEC解説動画参照。

goblockchain.network

PayJoinトランザクションを使わない場合

↑のPayJoinトランザクションの構成は必ずしも必須条件ではない。ボブは支払いを受け際に、以下のトランザクションのように自身のAnchorアウトプットを追加するのでも良い。

inputs = [Alice_input]
outputs = [
  Alice_output,
  Bob_output,
  Bob_anchor
]

Bob_anchorは使用されないままUTXOとして残るので、UTXOの重複を許可しないGrinの場合、このトランザクションをリプレイすることはできない。