Develop with pleasure!

福岡でCloudとかBlockchainとか。

Inherited IDを使ったトランザクションの参照と新しいチャネルプロトコル2Stage

先月、Bitcoin-DevメーリングリストにJeremyによって代理投稿された、匿名の開発者(John Law)による投稿では↓

https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019470.html

現在提案されれているBIP-118(anyprevout)に代わり、Inherited IDという新しいトランザクションの識別子を導入し、eltooに代わる新しいチャネルプロトコルを提案している。具体的なペーパーは↓

https://github.com/JohnLaw2/btc-iids/raw/main/iids14.pdf

BIP-118は、トランザクション署名時に、署名対象のメッセージからインプットが参照するOutPointを除外することで、署名後に(同じscriptPubkeyとvalueを持つ)インプットを切り替えることができるようにするというもの。これを利用すると、現在のLNチャネルのペナルティの仕組みを廃止し、古いステートのコミットメントTxがブロードキャストされても後から最新のコミットメントTxに置き換えることができるeltooスタイルのLNチャネルが可能になる。この詳しい仕組みについては↓

techmedia-think.hatenablog.com

Inherited IDとは?

トランザクションのインプットでどのUTXOを使用するか指定する際に用いられるのがOutPointで、これはTXIDとそのトランザクションのアウトプットのインデックスで構成されている。TXIDはトランザクションハッシュ値である(正確にはハッシュ値エンディアンを逆にした値)。Bitcoinではトランザクションを指定する際に、このTXIDを使用している。

↑のペーパーでは、TXIDに代わってInherited ID(IID)という識別子を利用しようと提案している。このInherited IDというのは、親トランザクションの先頭のインプットのOutPointのハッシュ値である。例えば以下のような親子関係のあるトランザクションTx A, Tx B, Tx Cがあった場合、

f:id:techmedia-think:20211010132735p:plain

Tx Bのインプットが参照するTx AのUTXOは、通常OutPoint(TXID, index)で参照されるが、このTXIDの部分をIID=Tx Aの先頭のインプットが参照するOutPointのハッシュ=H(OutPoint(XXX, 1))に置き換える。さらのTx Cが参照するTx BのUTXOを参照する際のIIDは、Tx Bの先頭のインプットのOutPointのハッシュ、つまりH(OutPoint(H(OutPoint(XXX, 1), 0)になる。

このIIDを使ってトランザクションを参照すると、インプットが参照先のトランザクションのハッシュにコミットする必要がなくなる。つまり、インプットがUTXOを参照する際に、そのトランザクションのTXIDを知る必要がない。

これはanyprevoutと同じ効果を生む。ただし、anyprevoutでは同じscriptPubkeyとvalueを持つUTXOであれば何でも切替可能であったのに対し、IIDは親のインプットのOutPointにコミットしているため、anyprevoutのような切り替え(フローティング・トランザクション)はできない。anyprevoutのフローティングの仕組みは、同じ構成のアウトプットの作成により意図しないフローティングを生み出すのではないかという懸念があるが、IIDの場合その点のリスクはなくなる。

ペーパーでは、新たにsegwit version 2のアウトプットに対してこのIIDの適用を提案している。それ以外の従来のscriptPubkeyを持つトランザクションやコインベーストランザクションについては、そのトランザクションのIID = TXIDとするとしている。つまり↑のA, B, Cの関係で言うと、Aは非IIDトランザクションで、そのインプットは従来のTXIDを使ってトランザクションを参照している(OutPoint(XXX, 1))。

※ ちなみにこをそのまま実装するとハードフォークになる。ソフトフォークで実現するための仕組みもペーパーのAppendixに掲載されているみたい。

2Stage Channel

IIDによって、インプットが参照先のTXIDにコミットしないことは分かったが、フローティングはできないので、eltooプロトコルを実装することはできない。そのため既存のLightningチャネルとeltooを組み合わせたような2Stageと呼ばれる新しいチャネルプロトコルを提案している。トランザクションのステート管理は、nLocktimeを使用するeltooスタイルを採用しているので、プロトコルの理解のためには、事前にeltooの仕組みを理解しておいた方がいい(参考:GBEC動画ブログ記事)。

2Stageプロトコルで構成されるチャネルには、以下の種類のトランザクションが登場する。

  • Funding Tx
    両者のマルチシグにチャネルの資金をデポジットするトランザクション
  • Update Tx
    Funding Txをインプットするトランザクションで、両者のチャネル残高の配分を更新するトランザクション
  • Settlement Tx
    Update Txをインプットとして、チャネルをクローズする=チャネル残高を両者のアドレス宛に決済するトランザクションで、Update Txをブロードキャストしたユーザーがブロードキャスト可能。
  • Remedy Tx
    古いステートのUpdate Txがブロードキャストされた際に、Update Txをインプットとして資金を取り戻すためのトランザクションで、Update Txをブロードキャストしたのとは別のユーザーがブロードキャスト可能。
  • Closing Tx
    チャネル参加者が協力して最終残高でチャネルをクローズする際のトランザクション

Settlement TxとRedemy Txは競合関係にある。古いステートのUpdate Txがブロードキャストされた際に、Settlement Txでそれを回収する場合、タイムロックによる遅延が設定されている。つまり、Update Txがブロードキャストされると、それをインプットとするSettlement Txが有効になるまで遅延時間が設定されており、その間に不正されていればRemedy Txで相手が資金を取り戻せるという仕組み。

Funding Txに対して、Update Txをブロードキャストする第一段階と、Settlement TxおよびRemedy Txのどちらかがオンチェーンで有効にる第二段階で構成されることから2Stageプロトコルという名前になってるみたい。

トランザクションの構成を簡単に図示すると以下のようになる↓

f:id:techmedia-think:20211011103551p:plain

またメインのコントラクトとなるUpdate TxのscriptPubkeyは以下のようなものになる:

<state X> OP_CHECKLOCKTIMEVERIFY OP_DROP //子トランザクションがnLocktime ≥ state Xであることをチェック
OP_IF // Settlement Tx用のアンロック条件
  OP_DUP <pubkey A&B> OP_EQUALVERIFY OP_CHECKSIG // 公開鍵A&Bに対する署名検証
OP_ELSE // Y≧XのRedemy Tx用のアンロック条件
  OP_CODESEPARATOR // 以下の署名検証が上記の<state X>の値をカバーするのを防ぎ、Redemy Txの署名がXの値に依存しないようにする。
  OP_DUP <pubkey A&B> OP_EQUALVERIFY OP_CHECKSIG // 公開鍵A&Bに対する署名検証
OP_ENDIF
<OP_0 if Alice's, OP_1 if Bob's> // アリスのTxなら0をボブのTxなら1をプシュ。これはアリスのUpdate Txの子の署名とボブのUpdate Txの子の署名を区別するために含まれている
OP_DROP // 署名を区別するためだけに含まれていた、直前の0 or 1をスタックから削除

この2Stageチャネルには、以下の特徴がある。

  • ステート:
    オフチェーンのステートは、Update Txに設定され、それを使用するSettlement TxおよびRedemy TxのnLocktimeにはそれぞれのそのステート番号が設定される。これにより、ブロードキャストされたUpdate Txのステート番号より大きな番号のステートのSettlement TxおよびRedemy Txしかブロードキャストできない。この仕組みはeltooで採用されているのと似た仕組み。
  • Txの非対称性:
    eltooでは、両者が保持するTxには対称性があったが、2StageではUpdate Tx、Settlement Tx、Redemy Txに小さな違いがある。↑の図では簡易的に1つのステートに対して1セットの各Txしか書いてないが、実際には小さな違いのある各Txのセットが両者分ある。
  • ブロードキャスト可能なTx:
    基本的にUpdate Txをブロードキャストしたユーザーは、Settlement Txのみをブロードキャストでき、もう一方のユーザーはそれに対してRedemy Txのみをブロードキャストできるようになっている。
    • アリスがアリスのUpdate Txをブロードキャストした場合
      • Settlement Txをブロードキャストできるのはアリスのみ
      • Redemy Txをブロードキャストできるのはボブのみ
    • ボブがボブUpdate Txをブロードキャストした場合
      • Settlement Txをブロードキャストできるのはボブのみ
      • Redemy Txをブロードキャストできるのはアリスのみ
  • 古いUpdate TxのブロードキャストとIID:
    この場合、Update TxがIID Txになっているため、ブロードキャストされたユーザーは自分が持っているRedemy Txの内、ブロードキャストされたステートより、新しいステートのRedemy Txをピックアップしてブロードキャストする。この時、Redemy TxはIIDを使ってUpdate TxのUTXOの参照しているため、参照先のUpdate Txを切り替えることができる。
  • OP_CODESEPARATORの使用:
    ↑のUpdate Txのコントラクトを見るとコントラクト内に各Update Txのステート番号が含まれているのが分かる(まぁ、これでステート管理しているので当然)。IIDによってRedemy Txのインプットを差し替えたとしても、別のUpdate Txには事なるステート番号が含まれているので、そのままだとRedemy Txの署名は無効なものになってしまう。そのため、Redemy Txでアンロックする際の条件分岐にOP_CODESEPARATORが含まれている。OP_CODESEPARATORは、署名検証時に実行スクリプトを提供する際に、直前のOP_CODESEPARATORより前のスクリプトを除去する。そのため、この条件分岐の署名検証では、スクリプト内のステートは署名にコミットされなくなるので、Redemy Txが古いステートのUpdate Txにインプットを切り替えても、署名は有効なままとなる。ちなみにSettlement Txが使用する条件分岐にはOP_CODESEPARATORは無いので、このような切り替えはできない。
  • ペナルティ:
    仕組みはeltooと似ているが、ブロードキャストされたステートよりも新しいステートが複数ある場合、Redemy Txをブロードキャストするユーザーは自分にとって都合の良い(自分の残高が有利な)Txをブロードキャストすることができる。そういう意味では最新の残高を反映可能なeltooとは少し異なる。ただ、保持するデータ量を最小限に抑えたい場合は、最新のUpdate/Settlement/Redemy Txを保持するだけで良い。

Watchtowerいらず?

ペーパーでは、Watchtowerを排除できるという内容もあった。ただこれは、チャネルのライフタイムを定めるもので、↑のUpdate Txに対して、Funding Txから相対的なタイムロックを設定するというもの。

Update Txのブロードキャストにタイムロックを設定すると、チャネルのライフタイム中は特にチェーンを監視する必要はないし、任意のタイミングでクローズすることもできる。ただ、HTLCのようなマルチホップ決済はどう解決するんだろうか?