Develop with pleasure!

福岡でCloudとかBlockchainとか。

Tapscript上で限定的に署名済みトランザクションのインプットの参照先をリバインド可能にする提案 bip-anyprevout

bip-taprootbip-tapscriptが定義されたが、これに関連してLightning Networkのeltooを実現するための提案も出てきてるので見てみる↓

https://github.com/ajtowns/bips/blob/bip-anyprevout/bip-anyprevout.mediawiki

eltooは、LNにおいて旧状態がブロードキャストされた際に、既存のPoon-Dryja形式のペナルティ型ではなく、チャネルの最新状態を適用するタイプのペイメントチャネルの提案。eltooの仕組みについては以前GBECで解説した動画が分かりやすいと思う↓。

goblockchain.network

このeltooを実現するためには、トランザクションの署名時にトランザクションダイジェストがインプットが参照する前のUTXOのOutPointへのコミットをしないようにするSIGHASH_NOINPUTの導入が前提として必要で、これはBIP-118として提案されている↓

techmedia-think.hatenablog.com

この未導入のSIGHASH_NOINPUTの提案を、以下のように変更した新しいフラグSIGHASH_ANYPREVOUTおよびSIGHASH_ANYPREVOUTANYSCRIPTの導入がbip-anyprevoutの提案内容。

  • SIGHASH_NOINPUTフラグはSegwitのversion 1以降で有効になるフラグであったが、bip-anyprevoutは、bip-schnorrおよびbip-taproot、bip-tapscriptの導入後に適用される。その際、ANYPREVOUTが適用できる公開鍵は限定され、bip-tapscriptの現時点では未定義の公開鍵タイプを導入することで適用される。この時の公開鍵タイプは0x000x01
  • NOINPUTフラグはスタック上の任意の公開鍵の署名検証に対して利用可能であるが、ANYPREVOUTはtaproot利用時にtapscripを利用した署名検証にのみ利用可能ので、tapscriptではなくtaprootの内部キーを使ったケースでは使用できず、利用シーンが限定される。
  • NOINPUTは無条件にscriptCodeにもコミットしない形になっていたが、bip-anyprevoutの場合SIGHASH_ANYPREVOUTANYSCRIPTは同様の振る舞いをするが、SIGHASH_ANYPREVOUTは使用するscriptPubkeyとtapscriptにコミットする。
  • ANYPREVOUT利用時にはChaperone署名を適用する=別途ANYPREVOUT以外にインプットの参照先にコミットする署名検証がされることを保証する必要がある。つまり2-of-2のようなマルチパーティ間の署名はANYPREVOUTを使用して、それだとOutPointへの参照がコミットされないので、1-of-2のような署名をもう1つ用意してそっちでANYPREVOUTでないOutPointへコミットする署名を要求する。
所感

実際、bip-anyprevoutが導入されるか、また別の提案が出てくるかはまだ分からないけど、

  • Chaperone署名あたりは署名が余分にもう1つ必要になり複雑かつサイズ増になる気がするんだけど、そこまでの保護が必要か?
  • インプットの参照先をダイナミックに変更できるようになるとeltooみたいないユースケースが可能になる反面、Segwitで解消されたtxidのmalleabilityがまた発生するようになるのでよく注意してプロトコル設計する必要がある。
  • まだ慣れてないかつデプロイされてない&bip-tapscriptの仕様上の話になるけど、tapscript上に未定義の公開鍵タイプを使って拡張するの、新しいスクリプトルールを適用できるという意味で拡張の選択肢は広がるものの、結構仕組みとして複雑な構造にシフトしてるように思える。

あたりが気になるところ。

以下、bip-anyprevoutの内容。

概要

このBIPではtapscript(bip-tapscript)トランザクションのための新しい公開鍵タイプについて定義する。これらの新しいタイプの公開鍵、使用するアウトプットについてコミットしなくても良いようになる。アウトプットが互換性のあるスクリプトである場合、これによりトランザクションを異なるUTXOにバインドすることが可能になる。

動機

オフチェーンプロトコルは、オンチェーンで決済する最終状態を再調整するためBitcoinネットワークにブロードキャストされていないトランザクションを利用する。多くの場合、オンチェーン上のトランザクションに対して、別のトランザクション形式で所定の反応を返すのが望ましい。オンチェーンで確認できる可能性がある様々な異なるトランザクションに対して同じ反応が望まれることがよくあるが、アプリケーションは固有の応答トランザクションを作成する必要がある。応答トランザクションのインプットの署名は対応するトランザクションに正確にコミットしているため、そのようなトランザクションを作るためには署名用の秘密鍵を必要とする。

この提案では、署名の作成と検証に使われるトランザクションダイジェストアルゴリズムの振る舞いを、前のアウトプット(オプションでwitness script)へのコミットメントを除外することで修正する新しい公開鍵タイプを導入する。このコミットメントの削除は、署名済みのトランザクションのインプットの参照先を、同じ鍵による認可を必要とする同じコインの量を持つ別のアウトプットへ動的に再バインドすることを可能にする。

動的な再バインドは別の公開鍵タイプを使うことでオプトインされるもので、異なる公開鍵を使ったり、署名内で使用するスクリプトにコミットすること、UTXO間で異なる金額を使用すること、使用するトランザクション内で異なるnSequenceの値を使用すること、codeseparator opcodeを使ってスクリプトの位置にコミットすることなどで、さらに制限される。

仕様

このBIPは、先頭バイトが0x000x01である公開鍵のbip-tapscriptの署名opcode(CHECKSIGCHECKSIGVERIFYおよびCHECKSIGADD)の動作を変更する。これらの鍵はbip-anyprevout keyと呼ばれる。

署名opcodeのルール

署名opcodeのbip-tapscriptのルールを次のように変更する。未知の公開鍵タイプのリストから先頭バイト0x000x01の鍵を削除し、未知の公開鍵タイプを処理する前に以下のルールを追加する。

  • 公開鍵の先頭バイトが0x00もしくは0x01の場合それはbip-anyprevout公開鍵とみなされる。
    • 公開鍵が33バイトではなく、単一バイト0x01でもない場合、スクリプトは失敗しすぐに終了しなければならない。
    • 署名が空のベクターでない場合、署名は、以下に定義されている公開鍵および許容されるhash_type、修正されたトランザクションダイジェストを使ってbip-tapscriptの署名検証ルルールに従って検証される。

公開鍵

bip-schnorrの公開鍵は0x02もしくは0x03で始まる33バイト列として定義されているため、(0x00もしくは0x01で始まる)bip-anyprevoutの公開鍵は署名検証に使う前に変換する必要がある。変換の手順は以下のとおり:

  • bip-anyprevoutの公開鍵が単一バイト0x01であった場合、bip-taprootの署名検証ルールに使われる公開鍵はtaprootの内部キーになる(つまりbip-taprootの表記でいうとbytes(P))。
  • bip-anyprevoutの公開鍵が33バイトの場合、検証に使われる公開鍵の先頭バイトが0x02もしくは0x03になるように先頭バイトのbit-1をセットすることで、bip-schnorr互換の公開鍵に変換される。残りの32バイトはスタック上のbip-anyprevout公開鍵と一致する。

hash_type

bip-taprootで許可されているhash_typeの値に加えて、bip-anyprevout公開鍵では、値0x41, 0x42, 0x43, 0xc1, 0xc2,0xc3が有効になる。hash_typeのビット6と7を使って以下の定数を定義する。

  • 0x00 SIGHASH_ALLINPUT
  • 0x80 SIGHASH_ANYONECANPAY
  • 0x40 SIGHASH_ANYPREVOUT``
  • 0xc0 SIGHASH_ANYPREVOUTANYSCRIPT``

そして、例えばhash_type & 0xc0 == SIGHASH_ANYPREVOUTの場合、SIGHASH_ANYPREVOUTがセットされていると言える。

トランザクションダイジェスト

署名opcodeの署名検証のメッセージとして、bip-anyprevout公開鍵のトランザクションダイジェストは、bip-tapscriptと以下が異なる。

  • 全てのケースにおいて、key_version0x02の代わりに定数値0x00が使われる。
  • SIGHASH_ANYPREVOUTがセットされている場合、outpointがダイジェストに含まれていないことを除いてSIGHASH_ANYONECANPAYがセットされた場合と同じように計算される。
  • SIGHASH_ANYPREVOUTANYSCRIPTがセットされている場合、outpointscriptPubKeyおよびtapleaf_hashがダイジェストに含まれていないことを除いてSIGHASH_ANYONECANPAYがセットされた場合と同じように計算される。

SIGHASH_ANYPREVOUTがセットされている場合、トランザクションダイジェストへのバイト単位の入力は、対応するSIGHASH_ANYONECANPAYトランザクションダイジェストより36バイト短くなる(outpointのサイズ分)。SIGHASH_ANYPREVOUTANYSCRIPTがセットされている場合、トランザクションダイジェストへの入力は、SIGHASH_ANYPREVOUTがセットされている場合と比べてさらに68もしくは56バイト短くなる(tapleaf_hashの32バイト分と、scriptPubKeyの36バイトもしくは24バイト分)。

Chaperone署名

ANYPREVOUT署名が使われる場合、固定のprevout署名(つまり非ANYPREVOUTの署名)で保護されなければならない。

これを実現するため、スクリプトの実行開始時に3つのフラグv0_anyprevoutv0_fixedprevoutおよびv2_fixedprevoutが導入され、最初はすべてfalseがセットされている。これらは以下の場合にtrueになる。

  • 公開鍵の先頭バイトが0x02もしくは0x03で、nullでない署名の署名opcodeが成功した場合、フラグv2_fixedprevoutにtrueがセットされる。
  • bip-anyprevout公開鍵とnullでない署名の署名opcodeが成功した場合:
    • SIGHASH_ANYPREVOUTもしくはSIGHASH_ANYPREVOUTANYSCRIPTがセットされている場合、フラグv0_anyprevoutにtrueがセットされる。
    • それ以外の場合(SIGHASH_ALLINPUTもしくはSIGHASH_ANYONECANPAYがセットされている場合)、フラグv0_fixedprevoutにtrueがセットされる。

スクリプトの実行終了時に、v0_anyprevoutにtrueがセットされていても、v2_fixedprevoutv0_fixedprevoutがfalseのままの場合、スクリプトは失敗しなければならない。

安全性

署名のリプレイ

SIGHASH_ALLINPUTおよびSIGHASH_ANYONECANPAYの署名と比べて、SIGHASH_ANYPREVOUTおよびSIGHASH_ANYPREVOUTANYSCRIPTは、同じ署名が違うトランザクションで再利用される署名リプレイの可能性をもたらす。

SIGHASH_ALLINPUTSIGHASH_ANYONECANPAYの署名はどちらも1つ以上のインプットにコミットすることで署名のリプレイを防ぐため、同じインプットを複数回使用できる場合のみ署名のリプレイが可能になるが、BIP 30およびBIP 34以降のBitcoinブロックチェーンでは不可能だ。SIGHASH_ANYPREVOUTによる署名のリプレイは、同じscriptPubKeyと同じコインの量を持つ異なるUTXOに対して可能だが、SIGHASH_ANYPREVOUTANYSCRIPTによる署名のリプレイは同じコインの量を持つ異なるUTXOに対して可能で、潜在的スクリプトの1つで同じbip-anyprevout公開鍵を再利用する。

その結果、署名のリプレイが資金の消失や他の望ましくないケースの原因になる場合、プロトコルの設計者とウォレットはANYPREVOUT署名を使用する際、同じアドレスを再利用しないことを保証しなければならず、SIGHASH_ANYPREVOUTANYSCRIPTを使って署名する場合はスクリプトでbip-anyprevout公開鍵を再利用しないことを保証しなければならない。

Malleability

SIGHASH_ANYPREVOUTまたはSIGHASH_ANYPREVOUTANYSCRIPTを使うと追加のmalleabilityが導入される可能性がある。

特に、ANYPREVOUT署名のみを使って承認されたトランザクションは、署名を満たす代替インプットを提供できる人すべてに対しmalleabilityがある。この方法で変更されたインプットは、同じ受信者に対して支払いをする有効なトランザクションであるがtxidが異なるという新しいトランザクションを生成する。インプットへの変更によって、(一部のインプットが共有されたままの場合)これは元のトランザクションと競合する可能性があり、または(そうでない場合)受信者への二重支払いになる可能性がある。

さらに、(稀な失敗のケースとしてeltooで想定されているように)同じscriptPubkeyとコインの量を使い、ANYPREVOUT署名のみで承認されるトランザクションのチェーンでは、特に中間のトランザクションを省略することで、第三者秘密鍵にアクセスすることなく、トランザクション(およびそのtxid)を細工する可能性がある。

この形式の細工は、ANYPREVOUT署名を使用する子トランザクションでも対処できる。親トランザクションに細工された場合、その子はインプットとして細工された新しいtxidを参照するように単純に調整でき、ANYPREVOUT署名は有効のままだ。

ただし、SIGHASH_ALLINPUTもしくはSIGHASH_ANYONECANPAYの署名によって承認された子トランザクションでは、そのインプットがこの方法で細工された場合、新しい署名が必要になる。このリスクは、ANYPREVOUTで承認されたUTXOをSIGHASH_ALLINPUTもしくはSIGHASH_ANYONECANPAYで使用する前に、BIP 68/112の相対的タイムロックを使うことで、いくらか軽減できる。相対的なタイムロックは、インプットに十分承認されていることを保証し、大規模なブロックの再編成が起きた場合のみそれらを置き換えることができる。このアプローチには欠点があることに注意すること:相対的なタイムロックはchild-pays-for-parentによる手数料のバンプを妨げ、タイムロックが期限切れになるまで資金を一時的に使用不可能にするという明らかな欠点がある。

Chaperone署名の効果

Chaperone署名の導入は2つの方法で分析できる。

まず、ANYPREVOUT署名とChaperone署名によって署名されたトランザクションの安全性は、Chaperone署名のみで署名されたトランザクションよりも悪くない。これにより、アドレスの秘密鍵が共有されている場合に、トランザクションが既に可能であったよりも悪いセキュリティ特性を持つことがなくなる。二重使用や二重支払いは、秘密鍵の所有者が複数の署名をする場合にのみ可能であり、秘密鍵の所有者が細工されたトランザクションに署名することを選択した場合にのみトランザクションは細工される。Chaperone署名の秘密鍵がマルチシグアドレス用の既存の秘密鍵よりも基本的に安全性の低い方法で共有されていない場合、ANYPREVOUT署名によって承認された支払いを受け取るBitcoinユーザーは他の支払いと比べて追加のセキュリティ対策を講じる必要は無い。

第2に、ANYPREVOUT署名とChaperone署名を使ってトランザクションに署名をする効率は、ANYPREVOUT署名のみを使った場合よりもそれほど悪くはない。特に、ANYPREVOUT署名の秘密鍵は、トランザクションを発行することを承認されたすべての人の間で共有される鍵を介して1-of-Nのマルチシグ要件として扱うことができる。トランザクション公開される準備ができるまでに全てのインプットが既知でなければならないので、Chaperone署名は生成できる。これにより、トランザクション自体とトランザクションを公開するノードの両方に追加のオーバーヘッドが発生することに注意すること。また、トランザクションの複数の潜在的な発行者がそれぞれ異なるChaperone署名を生成する可能性があるため、witnessのmalleabilityに関する別のベクトルも導入される。これにより同じtxidにも関わらず、書く署名に対して異なるwtxidが生成される。これは(64バイトの署名ではなく65バイトの署名を使うことにより)手数料レートを僅かに変更する可能性があり、(ブロックはある署名を持っているが、そのブロックがリレーされたノードのメモリプールには異なる署名がある)ブロックリレーのパフォーマンスを低下させる可能性がある。

そのため、プロトコルの設計者は、Chaperone署名に対して既知の秘密鍵を使用せず、安全な方法でこれらの秘密鍵を生成し、その秘密鍵の配布を制限する必要がある。さらに署名者は、bip-schnorrで定義されているように、Chaperone署名を生成するために安全で決定論的な方法を使用する必要がある。

プライバシーに関する考慮事項

ANYPREVOUT署名は実際にはめったに使用されないことが予想される。プロトコルおよびウォレットの設計者は、トランザクションweightが低いという効率上の理由だけでなく、第三者が他のプロトコルトランザクションとこのトランザクションを区別できないようにするためにも、できる限りTaprootのキーパスを使うようにする必要がある。

そのため、ANYPREVOUTを使ったトランザクションは、協調が不可能であったことを潜在的に含むトランザクションに関する情報または、(スクリプトの詳細により)使用しているプロトコル、ソフトウェアに関する情報が明らかになる。

プライバシーを最大限にするため、プロトコルの設計者は少なくとも1つのANYPREVOUT署名を使って使われるコインのスクリプト内でbip-anyprevout公開鍵のみを使用し、ANYPREVOUT署名なしで承認可能な使用にはキーパスもしくはマークルツリー内の代替スクリプトを使用することを推奨する。この推奨事項に従うと、追加のスクリプトブランチが必要になる場合がある。つまり推奨事項を無視すると、状況によってはコストとプライバシーの間のトレードオフが向上する可能性がある。

論拠

  1. どうして新しい公開鍵タイプを作るのか? tapscriptの新しい公開鍵タイプは、bip-tapscriptで指定されている未知の公開鍵タイプに対して新しいルールを指定することでソフトフォークで導入できる。これは既存の署名opcodeに制限を加えるだけで良いためだ。可能な代替アプローチは、新しいscript opcodeを定義すること、異なるtaprootのleaf versionを使用すること、もしくはbip-taprootで定義されるものとは異なるsegwitアウトプットを使用することくらいだ。しかし、これらのアプローチはすべてより複雑で、これらのアプローチが提供する追加の柔軟性が実際に必要とされる他のアップグレードのために予約される。今回のケースでは、新しいトランザクションダイジェストを定義するが、同じ楕円曲線と署名アルゴリズム(つまりsecp256k1とbip-schnorr)を維持する。
  2. どうしてwitness scriptへコミットするのか(しないのか) eltooのペーパーは、witness scriptへコミットすることが必ずしも適切でない例を掲載している。署名を非対称に作成するためスクリプトトランザクションnLocktimeを使用する。そのため、より早い番号の署名を持つトランザクションは、より後の番号の署名を持つトランザクションで使用できるが、後の番号の署名を持つトランザクションを、それより早い番号の署名を持つトランザクションで使用することはできない。結果として、さらに後の3番めの単一の署名を持つ後のトランザクションは、前の2つのトランザクションが例え異なるtaprscriptを持っていたとしても、そのトランザクションを使用できなければならない。一方、これらのケースでは、スクリプトにコミットするオプションがある正当な理由もある:各トランザクションには新しいスクリプトがあるため、スクリプトへコミットするとこれらのトランザクションの1つに正確に適用される署名を作成できる。eltooの場合、これにより、以前の任意の更新トランザクションに適用できる更新トランザクションの署名と、対応する更新トランザクションにのみ適用される決済トランザクションの署名が可能になり、両方に同じ鍵を使用する。結果、スクリプトをよりコンパクトにできる。
  3. キーパスの使用はどうなる? この提案は、スクリプトパスを介したANYPREVOUTの使用のみをサポートし、キーパスを使った使用へのANYPREVOUTの使用はサポートしていない。これには3つの理由がある:最初に、キーパスの使用をサポートしないことで、bip-taprootおよびbip-tapscriptに含まれるコアな変更から独立させることができる。第二に、キーパスでChaperone署名を要求するのは不可能であるということ。第三に、アドレスがANYPREVOUTサポートをオプトインもしくはオプトアウトできるようにすると、使用される前に見分けがつかない。
  4. 0x00と0x01の使用 プレフィックスには125個のフリーペア(トータル128で、0x02/0x03は既に使われ、0x04および0x060x07は使用不可)があるので、最初に利用可能なものを選択した。これによりtaprootの内部キーの1バイトのプッシュにOP_1を使用することもできる。(1バイトのpush opcodeは、0x08/0x09, 0x0a/0x0b, 0x0c/0x0d, 0x0e/0x0f,0x10, OP_8 - OP_16, 0x81,OP_1NEGATE`にも使用可能)。
  5. taprootの内部キー taprootの内部キーを使って署名することは、taprootのキーパスを使って署名することを意味するが、SIGHASH_ANYPREVOUTANYSCRIPT署名の場合、これはtaprootアウトプットの鍵もしくはscriptPubkey計算する、もしくは全てのスクリプトを知る前に行うことができる。このキーのショートカットとして単一バイトの0x01を使用すると、単一バイトのOP_1を介してプッシュできるので、スクリプトエンコードするのが非常に効率的になる。
  6. なぜkey_versionを変更するのか? key_versionを変更することで、同じ秘密鍵を使ってbip-tapscriptの鍵とbip-anyprevout鍵の両方を生成した場合、 bip-tapscriptの鍵の署名はbip-anyprevoutの鍵の署名に対して有効ではなくなる(その逆もしかり)。
  7. Chaperone署名は必要? 署名のリプレイや、追加のmalleabilityのリスクが単にウォレットや2ndレイヤーのプロトコルレベルではなく、コンセンサスレイヤーで実際の影響を与えるか、あるいは防止が必要かどうかについてはいくつかの論争がある*1。この提案が採用する設計の哲学は、コンセンサスの変更は、物事を危険にする証拠が不十分であるという否定的な議論ではなく、物事が安全でなくなることはないという肯定的な議論と一緒に検討すべきだ。Chaperone署名を追加しない限り、ANYPREVOUTの安全性に対する肯定的な議論はない。実際には、ANYPREVOUT署名のみでも安全かもしれないが、それに対して理論的な証明も、実際にそれを実証する大規模な経験もない。安全性のセクションの議論は、ChaperoneANYPREVOUT署名の安全性が現在の署名と実質的に同等であることを示すのを目的にしている。つまり物事がそれほぞ安全でないことを示している。
  8. 常に固定のprevoutを必要とする署名にしないのはなぜ? 別のアプローチは、ANYPREVOUT署名が使われているかどうかに関わらず、常に固定のprevout署名を要求するというもので、つまりスクリプトの実行終了時にv2_fixedprevoutがtrueになり、v0_anyprevoutv0_fixedprevoutをまったく追跡しないというものだ。これにより強い保証が提供される。トランザクションを検証するどんなノードもそれが認識する方法でインプットが署名されていることを確認するか、(将来の署名アップグレードが使われいるなら)トランザクションを完全に妨げられないと考えるだろう(アップグレードを有効にするOP_SUCCESSxの存在があるため)。欠点は、このような将来の変更にはbip-tapscript署名を伴う必要があるため、未知の公開鍵タイプを使って新しいトランザクションダイジェスト、もしくは潜在的に新しい署名アルゴリズム(別の楕円曲線への変更など)を導入する能力を大幅に制限する点だ。対照的に、Chaperone署名を必要とするかどうか選択するのは、新しい公開鍵タイプもしくは安全でないことが知られている署名を導入する場合のみである。つまり何か新しいものを導入する際に、我々が安全だと確認しているなら、不要なChaperone署名を要求することは強制されない。

展開

これは、bip-scnorr、bip-taproot、bip-tapscriptの展開と同時もしくはその後にソフトフォークとして展開できる。

後方互換

ソフトフォークとして、古いソフトウェアは変更なく動作し続ける。bip-taprootをサポートするためにアップグレードしていないノードは全てのtaprootのwitness programを誰でも使用可能なスクリプトとして認識し、bip-taprootおよびbip-tapscriptをサポートするためにアップグレードしたがこのBIPには対応していないノードは単にbip-anyprevout公開鍵に対して空でない署名を有効なものとして扱う。ただし、bip-tapscript公開鍵に対するものである場合は、Chaperone署名を検証する。そのため、新しい公開鍵タイプの署名を完全に検証するためにはアップグレードすることを強く推奨する。

アップグレードされていないウォレットは、segwit version 0 programや従来のpay-to-pubkey-hashを使って、アップグレードされていないウォレットおよびアップグレードされたウォレットからBitcoinを送受信できる。実装によってはアップグレードされていないウォレットも、BIP 173 のBech32アドレスの送金をサポートしている場合、segwit version 1プログラムへの送金が可能である。アップグレードされていないウォレットはBIP 16のP2SHでネストされているsegwit version 1 programを使って、アップグレードされたウォレットにBitcoinを送金できる。

BIP 118との違い

segwit v0ではなくTaprootに基づいていることを除けば、BIP 118の主なセマンティクスの違いは以下のとおり:

  • BIP 118 NOINPUT署名は、scriptPubkeyもしくはredeem/witness scriptのいずれかでアウトプットの使用条件にコミットしない。この提案は、SIGHASH_ANYPREVOUTANYSCRIPTが使用される場合は同じ振る舞いをするが、SIGHASH_ANYPREVOUTが使用される場合は、scriptPubkeyとtapscriptにコミットする。
  • スクリプト内のOP_CODESEPARATORは、この提案のSIGHASH_ANYPREVOUTおよびSIGHASH_ANYPREVOUTANYSCRIPTの両方の署名に影響するが、BIP 118のNOINPUT署名には影響しない。
  • BIP 118は、(展開がBIP 141のP2WPKHおよびP2WSHと同様の方法で具体化されている仮定した場合)直接的な公開鍵の使用に対して有効に機能するはずだが、この提案では、tapscriptを介した署名にのみ適用され、キーパスを使った直接的な使用には適用されない。
  • この提案ではChaperone署名を必要とするとが、BIP 118では必要としない。
  • この提案では、NOINPUTではなくANYPREVOUTという名称を使用する。これは前のアウトプットのコインの量は署名に使われる、つまり前のアウトプットのvalueやインプットのnSequenceの値および(オプションで)使用条件など、インプットのいくつかはまだコミットされるためである。

TaprootのアウトプットのスクリプトTapscriptに適用される新しいスクリプトルールを定義したBIP-342

TaprootのBIPドラフトについて書いた↓

techmedia-think.hatenablog.com

ので、続いてそのTaprootのアウトプットで使用されるスクリプト = Tapscriptに適用されるスクリプトの新しいルールの提案BIP-342↓について見てみる。

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

主な変更点は

  • OP_CHECKSIGOP_CHECKSIGVERIFYがECDSAの代わりにSchnorr署名の検証をするようになる。
  • OP_CHECKMULTISIGOP_CHECKMULTISIGVERIFYは無効化され、新しいopcode OP_CHECKSIGADDを使ったマルチシグポリシーを追加。
  • 現在未使用のopcode(80, 98, 126-129, 131-134, 137-138, 141-142, 149-153, 187-254)にOP_SUCCESS80...OP_SUCCESS254を割り当て、OP_SUCCESS系のopcodeを追加する。OP_SUCCESSが実行されるとスクリプトはその時点で成功する。これは将来のソフトフォークで新しいopcodeを割り当てるのに使われる。従来OP_NOP系のopcodeが拡張用に用意されてたけど、これだとスタックから要素をプッシュができないなどの制約があるので、より柔軟にopcodeを割り当て可能なOP_SUCCESSを利用可能にすると。
  • 署名の際に用いるメッセージであるトランザクションダイジェストの計算方法の変更
  • 署名opcodeの制限がsigopから、トランザクションインプット単位のwitnessのweightベースの制限に変更。
  • MINIMALIFルールがコンセンサスとして適用。MINIMALIFルールというのは、OP_IFOP_NOTIFがスタック要素からTrue/Falseを判定するが、その際のmalleabilityを排除するため、その要素は空ベクター0x01のどちらかでなければならないというルール。

以下、BIPドラフトの内容(追記:2020.03.19時点の内容で以下更新)。

イントロダクション

概要

BIP-341の下での最初のスクリプトシステムのセマンティクスを規定している。

動機

BIP-341はスクリプトの構造のみを改善することを提案しているが、その目標のいくつかはスクリプト言語自体の中の特定のopcodeのセマンティクスと両立しない。これらを別々の改善で扱うことは可能だが、BIP-341自体と同時に対応されない限り、それらの影響は保証されない。

具体的には、目標はSchnorr署名、バッチ検証、およびsignature hashの改善を、スクリプトシステムを使用するコインの使用にも利用できるようにすることだ。

設計

これらの目標を達成するために、署名opcode、OP_CHECKSIGおよびOP_CHECKSIGVERIFYは、BIP340で指定されたSchnorr署名を検証し、BIP-341の共通メッセージ計算に基づく署名メッセージアルゴリズムを使用する。tapscriptの署名メッセージは、OP_CODESEPARATORの処理を単純化し、より効率的にする。

非効率な、OP_CHECKMULTISIGおよびOP_CHECKMULTISIGVERIFY opcodeは無効になる。代わりに新しいopcode OP_CHECKSIGADDが導入され、バッチ検証可能な方法で同じマルチシグポリシーを作成できるようになる。Tapscriptは、トランザクション weightを使用した複雑なやりとりを修正するための新しいよりシンプルな署名opcodeの制限を使用する。さらに潜在的なmalleability ベクトルはMINIMALIFを必要とすることで排除する。

Tapscriptは、例えば新しいhash_typesや署名アルゴリズムを追加するために、未知のキータイプを定義することでソフトフォークを介してアップグレードできる。さらに新しいTapscript OP_SUCCESS opcodeを使用すると、OP_NOPを使用するよりもクリーンに新しいopcodeを導入できる。

仕様

以下のルールは、以下のすべての条件があてはまるトランザクションインプットを検証する場合にのみ適用される。

  • 使用するアウトプットはsegregated witnessのアウトプットである(すなわち、scriptPubKeyはBIP 141で定義されているようなwitness programを含む)。
  • そのアウトプットはBIP-341で定義されているtaprootアウトプットである(つまり、witness version 1で、witness programは32バイト。ただしP2SHでラップされていないこと)。
  • BIP-341で定義されたスクリプトパスを使用している(つまり、witnessスタックからオプションのannexを削除後、2つ以上のスタック要素が残ること)。
  • リーフバージョンは0xc0(オプションのannexを削除後、最後のwitness要素の先頭バイトが0xc0もしくは0xc1である)、つまりTapscriptの使用としてマークされている。

そのようなインプットの検証は、指定された順序で以下の手順を実行することと同等である必要がある。

  1. インプットがBIP 141もしくはBIP 341に対して無効な場合、失敗する。
  2. BIP 341で定義されているスクリプト(つまりオプションのannexを削除後の最後から2番めのwitnessスタック要素)は、tapscriptと呼ばれ、1つずつopcodeにデコードされる。
    1. opcode番号80, 98, 126-129, 131-134, 137-138, 141-142, 149-153, 187-254が検出された場合、検証は成功する(以下のルールはいずれも適用されない)。tapscriptの後のバイトがそれ以外でデコードに失敗したとしても、これは適用される。これらのopcodeはOP_SUCCESS80...OP_SUCCESS254に名前が変更され、まとめてOP_SUCCESSxと総称される。
    2. 任意のpush opcodeのデコードに失敗すると、tapscriptの終わりを超えて広がるため、失敗する。
  3. BIP 341で定義されている初期スタックが(つまり、オプションのannexおよび最後から2つのスタック要素を両方削除した後のwitnessスタックのサイズ)、リソース制限(スタックサイズ、スタック内の要素のサイズ、「リソース制限参照」)に違反する場合、失敗する。このチェックはOP_SUCCESSxを使ってバイパスできることに注意すること。
  4. tapscriptは、初期スタックをインプットとして次のセクションのルールに従って実行される。
    1. 何らかの理由で実行に失敗した場合、失敗する。
    2. 実行した結果、スタック上の要素が正確に1つで、それがCastToBool()でtrueとして評価される要素以外が発生した場合、失敗する。
  5. 失敗すること無くこのステップに到達すると、検証は成功となる。

スクリプトの実行

tapscriptの実行ルールは、BIP 65やBIP 112で定義されているOP_CHECKLOCKTIMEVERIFYOP_CHECKSEQUENCEVERIFY opcodeを含めBIP 141に準拠したP2WSHルールがベースになっているが、以下の点が変更されている。

  • 無効化されたスクリプトopcode 次のscript opcodeはtapscriptでは無効になっている:OP_CHECKMULTISIGおよびOP_CHECKMULTISIGVERIFY。無効化されたopcodeは、実行されるとすぐに終了し、未実行ブランチで見つかっても無視するという点でOP_RETURNと同じように動作する。
  • コンセンサスによるMINIMALIFの強制 MINIMALIFルールは、P2WSHの標準ルールに過ぎないが、tapscriptではコンセンサスとして適用される。つまり、OP_IFおよびOP_NOTIF opcodeへの入力引数は、厳密に0(空のベクター)または1(値1の1バイトのベクター)でなければならないことを意味する。
  • OP_SUCCESSx opcode 上記のようにいくつかのopcodeがOP_SUCCESSxという名前に変更され、無条件にスクリプトを有効とするようになる。
  • 署名opcode OP_CHECKSIGおよびOP_CHECKSIGVERIFYは、ECDSAに代わってSchnorrの公開鍵および署名(bip-schnorr参照)で動作するように変更され、新しいopcode OP_CHECKSIGADDが追加された。
    • opcode 186 (0xba)がOP_CHECKSIGADDという名前になる。

署名opcodeのルール

以下のルールがOP_CHECKSIGおよびOP_CHECKSIGVERIFYOP_CHECKSIGADDに適用される。

  • OP_CHECKSIGVERIFYおよびOP_CHECKSIGの場合、公開鍵(1番上の要素)と署名(上から2番めの要素)がスタックからポップされる。
    • スタックの要素が2要素未満の場合、スクリプトは失敗し直ちに終了しなければならない。
  • OP_CHECKSIGADDの場合、公開鍵(1番上の要素)とCScriptNum n(上から2番めの要素)および署名(上から3番めの要素)がスタックからポップされる。
    • スタックの要素が3要素未満の場合、スクリプトは失敗し直ちに終了しなければならない。
    • nが4バイトより大きい場合、スクリプトは失敗し直ちに終了しなければならない。
  • 公開鍵のサイズが0の場合、スクリプトは失敗し直ちに終了しなければならない。
  • 公開鍵のサイズが32バイトの場合、BIP 340の公開鍵とみなされる。
    • 署名が空のベクターでない場合、公開鍵に対して、検証される(以下のセクション参照)。検証に失敗するとスクリプトは失敗し直ちに終了しなければならない。
  • 公開鍵のサイズが0でも32バイトでもない場合、公開鍵は未知の公開鍵タイプであり、実際の署名検証は適用されない。署名opcodeのスクリプト実行中、署名検証が成功したとみなされることを除き、既知の公開鍵タイプと同じように動作する。
  • 公開鍵のタイプに関係なく、この手順の前にスクリプトが失敗し終了しなかった場合は:
    • 署名が空のベクターの場合
      • OP_CHECKSIGVERIFYの場合、スクリプトは失敗し直ちに終了しなければならない。
      • OP_CHECKSIGの場合、空のベクターがスタックにプッシュされ、次のopcodeへ実行が続く。
      • OP_CHECKSIGADDの場合、値nCScriptNumがスタックにプッシュされ、次のopcopdeへ実行が続く。
    • 署名が空のベクターでなく、sigopsバジェットにカウントされる
      • OP_CHECKSIGVERIFYの場合、スタックのこれ以上の変更なく実行は継続する。
      • OP_CHECKSIGの場合、1バイトの値0x01がスタック上にプッシュされる。
      • OP_CHECKSIGADDの場合、値n + 1CScriptNumがスタックにプッシュされる。

署名検証

公開鍵pで署名sigを検証するには、

  • 次のデータの連結で構成されるtapscriptメッセージ拡張extを計算する。
    • tapleaf_hash(32):BIP 341で定義されているtapleaf hash
    • key_version(1):tapscript内の署名opcode実行における公開鍵の現在のバージョンを表す定数値0x00。
    • codesep_pos(4):現在実行中の署名opcodeの前に最後に実行されたOP_CODESEPARATORの位置でリトルエンディアンで表現(OP_CODESEPARATORが実行されていない場合は0xffffffff)。スクリプト内の最初のopcodeの位置は0。マルチバイトのpush opcodeはプッシュされるデータのサイズに関係なく1 opcodeとしてカウントされる。
  • sigの長さが64バイトの場合、Verify(p, hashTapSigHash(0x00 || SigMsg(0x00, 1) || ext), sig)の結果を返す。このVerifyはBIP 340に定義されている。
  • sigの長さが65バイトの場合、sig[64] ≠ 0x00で、Verify(p, hashTapSighash(0x00 || SigMsg(sig[64], 1) || ext), sig[0:64])の結果を変えす。
  • それ以外の場合、失敗。

要約すると署名検証のセマンティクスは以下の除いてBIP 340と同じだ。

  1. 署名メッセージにはtapscript固有のデータkey_versionが含まれる。
  2. 署名メッセージは、scriptCodeではなくleaf versionとスクリプトを含むtapleaf_hashを介して実行されたスクリプトにコミットする。これはこのコミットメントがOP_CODESEPARATORの影響を受けないことを意味する。
  3. 署名メッセージには、最後に実行されたOP_CODESEPARATORのopcode位置が含まれる。

リソース制限

多数のopcodeのセマンティクスを変更することに加えて、リソース制限にもいくつかの変更がある。

  • スクリプトサイズの制限:最大スクリプトサイズ10000バイトは適用されない。サイズはブロックのweight制限によってのみ暗黙的に制限される。
  • 非プッシュopcodeの制限スクリプトあたり201の非プッシュopcodeの最大数制限は適用されない。
  • Sigops制限:tapscript内のsigopsはブロック全体の制限である80000にはカウントされない。代わりに、スクリプト毎のsigopsバジェットがある。バジェットは50 + (CCompactSizeプレフィックスを含む)トランザクションインプットwitnessの合計シリアライズサイズ。空でない署名を使って署名opcode(OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKSIGADD)を実行するとバジェットが50減る。バジェットが0未満になるとスクリイプとはすぐに失敗する。未知の公開鍵タイプと空で無い署名を持つ署名opcodeもカウントされる。
  • スタックとアルトスタックのカウント制限:全てのopcode実行後のスタックとアルトスタックの要素の最大数が1000の既存の制限は残り、初期スタックのサイズにも適用されるよう拡張される。
  • スタック要素サイズの制限:初期スタックとプッシュopcodeの両方で、スタック要素あたり最大520バイトの制限は残る。

論拠

  1. OP_SUCCESSxOP_SUCCESSxスクリプトシステムのアップグレードのための仕組み。ソフトフォークによって意味が定義される前にOP_SUCCESSxを使用することは安全ではなく、資金消失に繋がる。スクリプトOP_SUCCESSxを含めると無条件にコインが渡される。さまざまなエッジケースを指定する際の難しさを回避するため、スクリプト実行ルールに先行する。例えば、入力スタックが1000要素を超えるスクリプト内のOP_SUCCESSx、もしくは署名opcodeが多すぎた後のOP_SUCCESSxOP_ENDIFが内条件付きスクリプトなど。スクリプト内のどこかにOP_SUCCESSxが存在するだけで、このようなケースのパスが保証される。OP_SUCCESSxは初期のBitcoin(v0.1からv0.3.5まで)のOP_RETURNに似ている。元々のOP_RETURNはスクリプトの実行を即座に終了し、終了した時点で一番上のスタック要素に基づいて成功、失敗を返す。これは、scriptSigにOP_RETURNを配置することで無条件に第三者が窃盗できてしまうので、元々のBitcoinプロトコルの主な設計上の欠陥の1つだった。OP_SUCCESSxスクリプトの一部であり(したがって、taprootアウトプットによってコミットされる)コインの所有者の同意を意味し、第三者が検証プロセスにOP_SUCCESSxを挿入することはできないため、これは本提案では懸念事項にならない。OP_SUCCESSxはさまざまなアップグレードの可能性に使用できる。
    • OP_SUCCESSxは、ソフトフォークを介して機能的なopcodeに変換できる。スタックへのread-onlyアクセスのみを持つOP_NOP系のopcodeとは異なり、OP_SUCCESSxはスタックに書き込むこともできる。OP_SUCCESSxを含むスクリプトに対するルール変更は、有効なスクリプトのみを無効なスクリプトに変えることができ、これはソフトフォークで常に達成可能だ。
    • OP_SUCCESSxは初期スタックやプッシュopcodeのサイズチェックに先行するため、520バイトを超えるスタック要素を必要とするOP_SUCCESSxから派生したopcodeは、ソフトフォークの制限を引き上げる可能性がある。
    • OP_SUCCESSxは、既存のopcodeの動作を再定義し、新しいopcodeと連携できるようにすることもできる。例えば、OP_SUCCESSxから派生したopcodeが64bit整数と機能する場合、同じスクリプト内の算術opcodeでも同じことが可能になる。
    • OP_SUCCESSxにより解析不能スクリプトが渡される可能性があるため、マルチバイトopcodeを導入したり、特定のOP_SUCCESSxopcodeをプレフィックスとして使用してまったく新しいスクリプト言語を導入することができたりする。
  2. OP_CHECKMULTISIGとOP_CHECKMULTISIGVERIFYが無効になり、OP_SUCCESSxにならないのはなぜ?:これはTaprootで誤ってOP_CHECKMULTISIGを使い続けている人がすぐに問題に気付くようにするための予防策だ。また、コンテキスト依存になるスクリプトの逆アセンブラの複雑さを回避する。
  3. MINIMALIFをコンセンサスにするのはなぜ? こうすることで、スタックからブランチ情報を取得するmalleabilityの無いコードを書くのがかなり簡単になる。
  4. OP_CHECKSIGADD このopcodeはバッチ検証とは互換性のないOP_CHECKMULTISIGのようなopcodeが無くなるのをカバーするために追加されている。OP_CHECKSIGADDは機能的にはOP_ROT OP_SWAP OP_CHECKSIG OP_ADDと同等だが1バイトしか必要ない。OP_ADDCScriptNum関連の振る舞いはすべてOP_CHECKSIGADDにも適用できる。
  5. CHECKMULTISIGの代替:TaprootおよびTapscriptを使って閾値署名k-of-nポリシーを実装する方法は複数ある。
    • 単一のOP_CHECKSIGADDベースのスクリプト:witnessに0 <signature_1> ... <signature_m>を持つCHECKMULTISIGスクリプトm <pubkey_1> ... <pubkey_n> n CHECKMULTISIGは、witnessに<w_n> ... <w_1>を持つ<pubkey_1> CHECKSIG <pubkey_2> CHECKSIGADD ... <pubkey_n> CHECKSIGADD m NUMEQUALスクリプトに書き換えることができる。各witness要素w_ipubkey_iに対応する署名から空のベクトルのいずれか。NUMEQUALNUMEQUALVERIFYに置き換えることで、CHECKMULTISIGVERIFYスクリプトと同様に変換できる。このアプローチには、既存のOP_CHECKMULTISIGベーススクリプトと非常によく似た特性がある。
    • 全ての組み合わせのk-of-kスクリプトを使用する:k-of-nポリシーは、スクリプトをマークルツリーのいくつかのリーフに分割し、それぞれが<pubkey_1> CHECKSIGVERIFY ... <pubkey_(n-1)> CHECKSIGVERIFY <pubkey_n> CHECKSIGを使ってk-of-kポリシーを実装することで実装できる。これは参加する公開鍵のみが公開されるため、以前のアプローチよりもプライバシー上の理由で望ましい場合がある。またkが小さい値の方が費用対効果が高い。さらにここでの署名は、ブランチにコミットする。つまり署名者は他の署名者が参加することを認識するか、ツリーのリーフ毎に署名を作成する必要がある。
    • 全ての組み合わせに集約公開鍵を使用する:全てのリーフがk個の公開鍵で構成されるツリーを作成する代わりに、MuSigを使って各リーフはk個の鍵の単一の集約鍵を含むツリーを構築できる。このアプローチははるかに効率的だが、(単一の)署名を共同で生成するため3ラウンドの対話型署名プロトコルを必要とする。
    • ネイティブのSchnorr閾値署名:マルチシグポリシーは検証可能な秘密分散法を使って閾値署名で実現することもできる。これにより単一の鍵の支払いと区別できない出力と入力が得られるが、送信先のアドレスを決める前に対話型プロトコル(および関連するバックアップ手順)が必要になる。
  6. 未知の公開鍵タイプにより、ソフトフォークを介して新しい署名検証ルールを追加できる。ソフトフォークはスクリプをパスするか失敗させすぐに終了させる実際の署名検証を追加できる。この方法で新しいSIGHASHモードを追加できる他、NOINPUTタグ付き公開鍵や署名検証のためにtaprootの内部キーに置き換え可能な効果鍵定数を追加できる。
  7. 署名メッセージがkey_versionにコミットするのはなぜ?これは未知の公開鍵タイプを定義する将来の拡張のためで、署名をある鍵タイプから別の鍵タイプに移動できないようにする。
  8. 署名メッセージに最後に実行されたOP_CODESEPARATORの位置が含まれるのはなぜ?これにより引き続き、OP_CODESEPARATORを使って、スクリプトの実行パスに署名できる。codeseparator_positionはハッシュへの最後の入力であるため、SHA256 midstateは単一のスクリプト内の複数のOP_CODESEPARATORに対して効率的にキャッシュ可能。対照的にBIP143のOP_CODESEPARATOR処理は最後に実行されたOP_CODESEPARATOR以降からのみ実行されたスクリプトにコミットするため、スクリプトの不必要な再ハッシュが必要になる。既知のOP_CODESEPARATORユースケースは、2つのコードブランチ間で最初のプッシュを共有することで2つめの公開鍵のプッシュを保存する場合、各ブランチを異なるtaprootリーフに移動することでより安価に表現出来る可能性が高いことに注意。
  9. スクリプトのサイズ制限が不要になったのはなぜ?scriptCodeがsighashに直接含まれないため、署名チェックに使われるCPU時間は実行されるスクリプトのサイズに比例しなくなる。
  10. opcode数の制限が不要になったのはなぜ?opcodeの制限は、実行中にデータ構造が無制限に大きくなるのを防ぐことができる範囲でのみ役立つ(メモリ使用量と、それらの構造のサイズに比例して大きくなる可能性があるため)。スタックとアルトスタックのサイズは既に独立して制限されている。ここで実装されているようにOP_IF、OP_NOTIF、OP_ELSE、およびOP_ENDIFに対してO(1)ロジックを使用することにより、他の唯一のインスタンスも回避できる。
  11. tapscriptのsigops制限:署名opcodeの制限は、過度に多くの署名操作のために検証が遅いスクリプトから保護するためのもの。tapscriptでは、署名opcodeの数はBIP 141または従来のsigop制限にカウントはされない。古いsigop制限により、ブロック構築におけるトランザクション選択は、weightに加えて2つめの制約であるため、不必要に難しくなる。代わりにtapscript署名opcodeの数は、witnessのweightによってのみ制限される。さらに、制限はブロックでっはなくトランザクションインプットに適用され、実際に実行された署名opcodeのみがカウントされる。tapscriptの実行により、50 witness weight毎に1つの署名opcodeと1つの無料の署名opcodeが許可される。
  12. sigops制限のパラメータ選択:通常のwitnessはそのweightが公開鍵と(SIGHASH_ALLの)署名のペアで構成されそれぞれ33 + 65 weightの単位であるため、制限の影響を受けない。これは署名のweightが65か66であるため、スクリプト内で公開鍵が再利用される場合にも当てはまる。ただし、この制限により追加のweightが必要になるため、署名(および公開鍵)が重複する異常なスクリプトの手数料は高くなる。sigop係数50あたりのweightはBIP141のブロック制限の比率(4 MB weightを80,000sigopsで割った値)に対応する。トランザクションインプットの非witness部分のweightに対応するため、制限によって許可される無料の署名opcodeが存在する。
  13. なぜ署名opcodeのみが場ジェッドにカウントされ、例えばハッシュopcodeなどの高価な操作はカウントされないのか?署名チェックopcode最大密度で構成されるスクリプトの検証のためのwitnessバイト辺りのCPUコストは、ハッシュopcodeやOP_ROLLを含む他のopcodeが詰め込まれたスクリプトのコストに非常に近いことが分かる。ただし、この構造は非常に柔軟で、CHECKSIGFROMSTACKなどの新しい署名opcodeを追加して、ソフトフォークを介して制限にカウントできる。将来、通常のスクリプトコストを変更する新しいopcodeが導入されたとしても、witnessに無意味なデータを詰め込む必要はない。代わりに、taproot annexを利用して、実際のwitnessのサイズを増やすこと無くwitnessにweightを追加できる。

BitcoinにTaprootを導入するBIPドラフトbip-taproot(BIP-341)

先日、Bitcoinの開発者MLでPieter WuilleがTaproot、Schnorr署名およびマークルブランチをベースとしたコインの使用ルールである新しいSegwitバージョン1のアウトプットタイプを提案した↓

https://github.com/sipa/bips/blob/bip-schnorr/bip-taproot.mediawiki

Taprootの概要については以前書いた↓参照。

techmedia-think.hatenablog.com

これを具体的にBitcoinに取り込むための仕様を定義したのが今回のbip-taprootで、Taprootアウトプットの構成方法や使用時のルール(BIP 143とは異なる新しいトランザクションダイジェストアルゴリズムも導入)などを定義している。尚、このドラフトではTaprootの構造的な仕様を定義しており、Taprootのアウトプット内のスクリプトがどのように動作するかのルールについては別のドラフト(bip-tapscript)に定義されている。

以下、BIP-341訳。追記:2020/08/04 時点の内容で以下の内容を更新

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

概要

このドキュメントではTaproot、Schnorr署名やマークルブランチに基づく使用ルールを使った新しいSegwit version 1のアウトプットタイプを提案する。

動機

この提案は新しいセキュリティ仮定を追加することなく1Bitcoinスクリプト機能のプライバシーや効率性および柔軟性を改善することを目的としている。具体的には、アウトプットの作成時もしくは使用時にオンチェーンで明らかになるトランザクションアウトプットの使用可能条件に関する情報を最小限に抑え、マイナーではあるが長年の課題を修正する多くのアップグレーの仕組みを追加しようとしている。

設計

Bitcoinスクリプト機能を改善するための多くのアイディアがこれまで提案されてきた。Schnorr署名(BIP340)やマークルブランチ(MAST, BIP114, BIP117)、新しいsighashモード(BIP118)、CHECKSIGFROMSTACKのような新しいopcode、TaprootGraftrootG'rootインプットを跨いだ署名の集約など。

これらすべてのアイディアを単一の提案にまとめるのは大きな変更であり、レビューをするのが難しく、そうでなければ途中で実現されていたかもしれない新しい発見を見逃す可能性がある。またすべてが同様に成熟しているわけではない。例えば、インプットを跨いだ署名の集約は、アップグレードの仕組みと複雑な方法で相互作用し、その解決策はまだ流動的だ。一方、すべての個々の独立した提案に分割すると、効率とプライバシーの利点が減少し、ウォレットやサービスプロバイダーが多くのインクリメンタルな更新をしない傾向がある。したがって機能とスコープクリープのトレードオフに直面している。この設計では、Taprootとマークルブランチによって提供される構造スクリプトの改善と、それらを使用可能かつ効率的にするために必要な変更にフォーカスすることでバランスをとる。sighashやopcodeのようなもにに対しては、既知の問題に対する修正を含めるが、欠点がなく独立して追加できる新しい機能については除外する。

結果として、以下の技術の組み合わせを選択する。

  • スクリプトを実行することができるすべての方法を開示するのとは対照的に、マークルブランチではスクリプトの実際に実行された部分のみをブロックチェーンに公開することができる。これを実装するのにさまざまな既知の仕組みがある中で、マークルツリーをスクリプトの構造の一部にする方法が、1番直接スペースを節約することができるので、そのアプローチが選択される。
  • その上にTaprootを使うと、従来別々だったpay-to-pubkeyポリシーとpay-to-scripthashポリシーをマージし、全てのアウトプットは鍵もしくは(オプションで)スクリプトのいずれかで使用でき、互いに区別できなくすることができる。鍵ベースの使用パスが使われている限り、同様にスクリプトパスも許可されているかどうかが明らかにされることはなく、その結果、スペースの節約と使用時のスクリプトのプライバシーの向上がもたらされる。
  • Taprootの利点は、ほとんどのアプリケーションがすべての参加者が同意することで使用可能になるアウトプットを必要とするという仮定の下で明らかになる。Schnorr署名が入ってくると、鍵の集約が可能になるため、公開鍵は複数の参加者の公開鍵から構成することができ、それに署名するには全ての参加者の協力が必要になる。このようなマルチパーティの公開鍵や署名は、シングルパーティのものと区別がつかない。これは、taprootを使用するとほとんどのアプリケーションが効率的でプライベートな鍵ベースの使用パスを使用できることを意味する。Schnorr署名は閾値署名をサポートしているため、より複雑なセットアッププロトコルが必要になるが、任意のM-of-Nポリシーに一般化できる。
  • Schnorr署名はバッチ検証を可能にし、複数の署名を個別に検証するよりも効率的に複数の署名をまとめて検証できる。設計の全ての部分がこれと互換性があることを確認している。
  • 上記の変更の結果、未使用のビットが現れた場合、それらは将来の拡張のための仕組みで予約されている。その結果、マークルツリー内の各スクリプトは関連するバージョンを持ち、このバージョンによりBIP 341との互換性を維持しながら、ソフトフォークで新しいスクリプトバージョンを導入できるようになる。さらに、将来のソフトフォークでは、witness内で現在未使用のannexを利用することができる(BIP-341参照)。
  • この提案では、署名ハッシュアルゴリズムのコアセマンティクスは変更されていないが、いくつかの改善点が含まれている。新しい署名ハッシュアルゴリズムは、署名メッセージににamountscriiptPubkeyを含めることで、オフライン署名デバイスの検証機能を改善し、不要なハッシュを回避し、タグ付きハッシュを導入し、デフォルトのSIGHASHバイトを定義する。
  • 公開鍵のハッシュやスクリプトのハッシュをアウトプット内に格納する典型的な以前の構成とは対照的に、公開鍵はアウトプットに直接含まれる。これは送信者にとって同じコストであり、鍵ベースのパスが使用される場合、全体的にスペース効率が高くなる2

略式に、結果の設計は次のとおり。新しいwitness versionが追加され(version 1)、そのwitness programあ点Qの32バイトエンコーディングで構成される。Qは公開鍵PについてQ = P + hash(P || m)Gとして計算され、mはリーフがバージョン番号とスクリプトで構成されるマークルツリーのルート。このようなアウトプットは、Qに対して有効な署名を提供することで直接使用するか、間接的にP、スクリプトとリーフバージョン、スクリプトを満たす入力とQがそのリーフにコミットしたことを証明するマークルパスを明らかにすることを使用できる。この構造のすべてのハッシュ(PからQを計算する際のハッシュ、マークルツリーの内部ノードのハッシュ、使用されるsignature hash)は、ドメインの分離を保証するためタグ付けされる。

仕様

このセクションではTaprootのコンセンサスルールを指定する。有効性は除外によって定義される:ブロックまたはトランザクションは、失敗を示す条件が存在しない場合、有効である。

以下の表記はBIP-340の表記に従う。これには、SHA256(SHA256(tag) || SHA256(tag) || x)を指すhashtag(x)表記が含まれる。著者の知る限り、Bitcoinにおいて2つの単一のSHA256出力で始まるメッセージが投入されるSHA256の使用はこれまでないため、ハッシュタグと他のハッシュとの衝突はほぼ起こらない。

Scriptの検証ルール

Taprootのアウトプットはバージョン番号1、32バイトのwitness programを持つネイティブSegwitアウトプットである。以下のルールは、そのようなアウトプットが使用される場合にのみ適用される。32バイト以外の長さを持つバージョン1のアウトプットや、P2SHでラップされたバージョン1アウトプットの場合3、関係ないままだ。

  • qをBIP340における公開鍵を表すwitness program(scriptPubkey内の2つめのプッシュ)を含む32バイトの配列とする。
  • witness stackが0の場合失敗する。
  • 少なくとも2つのwitness要素があり、最後の要素の先頭バイトが0x50の場合4、この最後の要素はannex a 5と呼ばれwitness stackから削除される。annex(およびその欠如)は、常に署名によってカバーされ、トランザクションのweightに寄与するが、それ以外はtaprootの検証中無視される。
  • witnessスタックに正確に1つだけ要素が残っている場合、コインを使用するのに鍵パスが使用される:
    • 単一のwitnessスタック要素は署名として解釈され、公開鍵q(次のサブセクション参照)とメッセージとしてTaprootのトランザクションダイジェスト(後述)に対して有効でなければならない。
  • witnessスタックに少なくとも2つの要素が残っている場合、コインを使用するのにスクリプトパスが使用される:
    • 最後から2つめのスタック要素sスクリプトである。
    • 最後のスタック要素をコントロールブロックcと呼び、33 + 32mの長さでなければならない。mの値は0〜128 6までの整数。そのような長さでない場合、スクリプトの評価は失敗する。
    • p = c[1:33]、P = point(p)としBIP340における点とされる。もしこれが曲線上の有効な点でない場合、スクリプトの評価は失敗する。
    • v = c[0] & 0xfeとし、リーフバージョン7と呼ぶ。
    •  {k_0 = hash_{TapLeaf}(v || compact_size(size of s) || s)}とし、これをtapleaf hashと呼ぶ。
    • For j in [0,1,...,m-1]の各jについて
      •  {e_j = c[33+32j:65+32j]}とする。
      •  {k_{j+1}}は、(辞書順で8 {k_{j} < e_j}かどうかで算出方法が変わる:
        •  {k_j < e_j}の場合、 {k_{j+1} = hash_{TapBranch}(k_j || e_j)}となる9
        •  {k_j ≧ e_j}の場合、 {k_{j+1} = hash_{TapBranch}(e_j || k_j)}となる。
    •  { t = hash_{TapTweak}(p || k_{m})}とする。
    • t ≥ 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141(secp256k1の位数)の場合、スクリプトの評価は失敗する。
    • Q = P + int(t)Gとする。
    • q != x(Q)もしくはc[0] & 1 ≠ y(Q) mod 2の場合、失敗する。
    • 適用可能なスクリプトルールに従い10スクリプトs、control block c、ある場合はannex aを除外したwitnessスタック要素を初期スタックとしてスクリプトを実行する。

qはTaprootアウトプットキーと呼ばれ、pはTaproot内部キーと呼ばれる。

署名検証ルール

最初に再利用可能な共通の署名メッセージ計算関数を定義し、次に鍵パスの使用で使用される実際の署名検証を定義する。

共通の署名メッセージ

SigMsg(hash_type, ext_flag)関数は署名されるメッセージをバイト列として計算する。この関数は暗黙的に支出トランザクションと支出アウトプットの関数でもあるが、表記を簡単にするために、これらはリストアップされていない。

hash_typeパラメータは8bitの符号なしの値。SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_ANYONECANPAYを含む従来のスクリプトシステムのSIGHASHエンコーディングが再利用される。さらに、SIGHASH_ALLと同様にトランザクション全体が署名されるhash_type値0x00がデフォルトとして追加される。以下の制限に違反すると検証エラーになる:

  • (0x00, 0x01, 0x02, 0x03, 0x81, 0x82, 0x83以外の)未定義のhash_typeを使ってはならない11
  • 対応するアウトプット(検証されるインプットと同じインデックスを持つアウトプット)が無いのに、SIGHASH_SINGLEを使用してはならない。

ext_flagパラメータは0-127の範囲の整数で、メッセージの最後12に拡張が追加されていることを示すのに使われる。

パラメータが許容値の場合、メッセージは以下のデータを順番に連結したものになる。2,4また8バイトの数値はリトルエンディアンでエンコードされる(カッコ内の数値は要素のバイトサイズを表す)。

  • Control:
    • hash_type(1)
  • トランザクションデータ:
    • nVersion(4): トランザクションnVersion
    • nLockTime(4): トランザクションnLockTime
    • hash_type & 0x80がSIGHASH_ANYONECANPAYと等しくない場合、
      • sha_prevouts (32): 全てのインプットのOutPointをシリアライゼーションした値のSHA256値
      • sha_amounts (32): 全てのインプットの量をシリアライゼーションした値のSHA256値
      • sha_scriptpubkeys (32): 使用する全てのアウトプットのscriptPubKeyをシリアライゼーションした値のSHA256値
      • sha_sequences (32): 全てのインプットのnSequenceをシリアライゼーションした値のSHA256値
    • hash_type & 3がSIGHASH_NONESIGHASH_SINGLEと等しくない場合、
      • sha_outputs (32): 全てのアウトプットのCTxOutフォーマットの値をシリアライゼーションした値のSHA256値
  • インプットに関するデータ:
    • spend_type (1): (ext_flag * 2) + annex_presentと等しい。annexが存在しない場合はannex_presentは0、存在する場合(元のwitnessスタックには2つ以上のwitness要素があり、最後の要素の先頭バイトが0x50)は1。
    • hash_type & 0x80がSIGHASH_ANYONECANPAYと等しい場合:
      • outpoint (36): このインプットのCOutPoint(32バイトのハッシュ+4バイトのリトルエンディアン)。
      • amount (8): このインプットが使用する前のアウトプットのvalue(コインの量)。
      • scriptPubKey (35): このインプットによって使用される前のアウトプットのscriptPubkey。CTxOut内のスクリプトとしてシリアライズされ、そのサイズは常に35バイト。
      • nSequence (4): このインプットのnSequence。
    • hash_type & 0x80がSIGHASH_ANYONECANPAYと等しくない場合
      • input_index (4): トランザクションインプット内のこのインプットのインデックス。最初のインプットのインデックスは0。
    • annexがある場合(spend_typeの最下位ビットがセットされている場合):
      • sha_annex (32): (compact_size(size of annex) || annex)のSHA256値。annexは必須の0x50プレフィックスを含む。
  • アウトプットに関するデータ:
    • hash_type & 3がSIGHASH_SINGLEと等しい場合:
      • sha_single_output (32): インプットに対応するアウトプットのCTxOut形式のSHA2256値。

SigMsg()の最大長は206バイト13。これには同じトランザクションの署名に間でキャッシュされる可能性があるsha_prevoutsなどのサブハッシュのサイズは含まれないことに注意すること。

まとめると、BIP143のshghash typeのセマンティクスからの変更点は以下のみ。

  1. リアライゼーションの方法と順序の変更14
  2. 署名メッセージは使用されたアウトプットのscriptPubkeyにコミットし、SIGHASH_ANYONECANPAYフラグがセットされていない場合、メッセージはトランザクションで使用された全てのアウトプットのscriptPubkeyにコミットする15
  3. SIGHASH_ANYONECANPAYフラグがセットされていない場合、メッセージは全てのトランザクションインプットの量にコミットする16
  4. SIGHASH_NONEもしくはSIGHASH_SINGLEがセットされている場合(SIGHASH_ANYONECANPAYがセットされていない限り)、署名メッセージは全てのインプットのnSequenceにコミットする17
  5. 署名メッセージにはtaroot固有のデータ、spend_typeおよび(あれば)annexへのコミットメントが含まれる。
Taprootの鍵パスを使った署名検証

公開鍵qで署名sigを検証するには:

  • sigが64バイトの場合、Verify(q, hashTapSighash(0x00 || SigMsg(0x00, 0)), sig)18の結果を返す。VerifyはBIP340で定義されている。
  • sigが65バイトの場合、sig[64] ≠ 0x0019および、Verify(q, hashTapSighash(0x00 || SigMsg(sig[64], 0)), sig[0:64])の結果を返す。
  • それ以外の場合は失敗20

Taprootアウトプットの構築と使用

このセクションでは、Taprootアウトプットの構成方法および使用方法について説明する。これは受信と送金を実装することを選択したウォレットのみに影響し、コンセンサスクリティカルではない。

概念的に、各Taprootアウトプットは、単一の公開鍵条件(内部キー)とツリーに編成されたスクリプト内にエンコードされた0個以上の条件の組み合わせに対応している。これらの条件のいずれかを満たすとアウトプットを使用できる。

初期ステップ

最初のステップは、内部キーと残りのスクリプトの構成を決めることだ。詳細はおそらくアプリケーション依存になるが、ここではいくつかの一般的なガイドラインを示す。

  • (OP_IFなどの)条件付きのスクリプトを決定し、それらを複数のスクリプトに分割する(1つ1つが元のスクリプトの実行パスに対応する)際は、通常後半の選択を推奨する。
  • 単一の条件が複数の鍵の署名を要求する場合は、MuSigのような鍵集約技術を使ってそれらを単一の鍵に集約することができる。詳細はこのドキュメントの範囲外だが、この方法は署名手順を複雑にする可能性があることに注意すること。
  • 1つ以上の使用条件が(集約後の)単一の鍵のみで構成されている場合、最も使用する可能性が高いものを内部キーにする必要がある。そのような条件がない場合は、全てのスクリプトに参加している全ての鍵を集約したもので構成される鍵を追加するのを推奨する。これにより全員が同意するというブランチを効果的に追加する。それが許容できない場合は未知の離散対数を持つ点を内部キーとする。そのような点の例の1つは、 H = point(0x0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) で、これはsecp256k1ジェネレータGの標準的な非圧縮エンコードのハッシュをx座標として取得することで構築できる。キーパスの使用が不可能であるという情報の漏洩を防ぐため、0..n-1の範囲内の整数rをランダムに選択し、内部キーとしてH+rGを使用することを推奨する。rを検証者に明らかにすることで、この内部キーがGに関して既知の離散対数を持たないことを証明し、検証者は内部キーの作成方法を再構築できる。
  • 使用条件がスクリプトパスを必要としない場合、アウトプットキーはスクリプトパスを持たないのではなく、使用できないスクリプトパスにコミットする必要がある。これは、アウトプットのキーポイントを {Q = P + int(hash_{TapTweak}(bytes(P)))G} と計算することで実現できる21
  • 残りのスクリプトは、二分木のリーフに編成する必要がある。スクリプトの各条件が同じようにリーフに対応する場合、ツリーはバランスがとれたツリーになる。各条件の確率が分かっている場合は、ツリーをハフマン木として構築することを検討してほしい。

アウトプットスクリプトの計算

使用条件が内部キーinternal_pubkeyとリーフが(leaf_version、スクリプト)のタプルである二分木に分割されると、以下のPython3 アルゴリズムを使ってアウトプットスクリプトを計算できる。このアルゴリズムは整数変換や点の乗算、タグ付きハッシュを利用するためBIP340のreference.pyのヘルパー関数を使用する。

最初に32バイトのBIP340の公開鍵のバイト列に対して、taproot_tweak_pubkeyを定義する。この関数は公開鍵のバイト列に加えて、調整された公開鍵のY座標を示すビットを返す。このパリティビットはアウトプットをスクリプトパスを使って使用する場合に必要になる。鍵パスで使用できるようにするため、taproot_tweak_seckeyを定義し、調整された公開鍵に対応する秘密鍵を計算する。任意のバイト文字列hに対してtaproot_tweak_pubkey(pubkey_gen(seckey), h)[0] == pubkey_gen(taproot_tweak_seckey(seckey, h))が成立する。

def taproot_tweak_pubkey(pubkey, h):
    t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
    if t >= SECP256K1_ORDER:
        raise ValueError
    Q = point_add(point(pubkey), point_mul(G, t))
    return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q))

def taproot_tweak_seckey(seckey0, h):
    P = point_mul(G, int_from_bytes(seckey0))
    seckey = SECP256K1_ORDER - seckey0 if not has_square_y(P) else seckey
    t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h))
    if t >= SECP256K1_ORDER:
        raise ValueError
    return (seckey + t) % SECP256K1_ORDER

次の関数taproot_output_scriptはscriptPubkeyのバイト配列を返す(BIP141参照)。ser_scriptは先頭CCompactSizeでエンコードされた長さを付与する関数を指す。

def taproot_tree_helper(script_tree):
    if isinstance(script_tree, tuple):
        leaf_version, script = script_tree
        h = tagged_hash("TapLeaf", bytes([leaf_version]) + ser_script(script))
        return ([((leaf_version, script), bytes())], h)
    left, left_h = taproot_tree_helper(script_tree[0])
    right, right_h = taproot_tree_helper(script_tree[1])
    ret = [(l, c + right_h) for l, c in left] + [(l, c + left_h) for l, c in right]
    if right_h < left_h:
        left_h, right_h = right_h, left_h
    return (ret, tagged_hash("TapBranch", left_h + right_h))

def taproot_output_script(internal_pubkey, script_tree):
    """Given a internal public key and a tree of scripts, compute the output script.
    script_tree is either:
     - a (leaf_version, script) tuple (leaf_version is 0xc0 for [[bip-0342.mediawiki|BIP342]] scripts)
     - a list of two elements, each with the same structure as script_tree itself
     - None
    """
    if script_tree is None:
        h = bytes()
    else:
        _, h = taproot_tree_helper(script_tree)
    output_pubkey, _ = taproot_tweak_pubkey(internal_pubkey, h)
    return bytes([0x51, 0x20]) + output_pubkey

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

この図は内部キーPと5つのスクリプトリーフで構成されるマークルツリーからtweakを入手するハッシュ構造を示している。A,B,CおよびEはDと同様のTapLeafハッシュで、ABはTapBranchハッシュ。EはCDより小さいのでCDEを計算刷る際最初にEがハッシュされることに注意すること。

スクリプトDを使ってこのアウトプットを使用する場合、Control Blockに以下のデータがこの順番で含まれる。

<control byte with leaf version and parity bit> <internal key p> <C> <E> <AB>

次にTapTweakは以下のように計算される。

D = tagged_hash("TapLeaf", bytes([leaf_version]) + ser_script(script))
CD = tagged_hash("TapBranch", C + D)
CDE = tagged_hash("TapBranch", E + CD)
ABCDE = tagged_hash("TapBranch", AB + CDE)
TapTweak = tagged_hash("TapTweak", p + ABCDE)

鍵パスを使ったコインの使用

Taprootアウトプットは、internal_pubkeyに対応する秘密鍵を使って使用することができる。そのためには、witnessスタックは上記で定義されたsignature hashに対し、上記スニペット内と同じhを使って調整された秘密鍵で作成したBIP340署名という単一の要素で構成する。以下のコードを参照:

def taproot_sign_key(script_tree, internal_seckey, hash_type):
    _, h = taproot_tree_helper(script_tree)
    output_seckey = taproot_tweak_seckey(internal_seckey, h)
    sig = schnorr_sign(sighash(hash_type), output_seckey)
    if hash_type != 0:
        sig += bytes([hash_type])
    return [sig]

この関数は、必要なwitnessスタックと、上記で定義したsignature hashを計算するためのsighash関数を返す(簡単にするため、上記のスニペットトランザクション、インプットの位置などの情報をsighashのコードに渡していない)。

スクリプトの1つを使ったコインの使用

Taprootアウトプットは、その構築に使用されたスクリプトのいずれかを満たすことで使用できる。そのためには、スクリプトへの入力に加えて、スクリプト自体とControl Blockで構成されるwitnessスタックが必要になる。以下のコードを参照:

def taproot_sign_script(internal_pubkey, script_tree, script_num, inputs):
    info, h = taproot_tree_helper(script_tree)
    (leaf_version, script), path = info[script_num]
    output_pubkey_y_parity, _ = taproot_tweak_pubkey(internal_pubkey, h)
    pubkey_data = bytes([output_pubkey_y_parity + leaf_version]) + internal_pubkey
    return inputs + [script, pubkey_data + path]

安全性

Taprootは、アウトプットを使用する際に全ての条件を明らかにするのではなく、満たされた使用条件のみを公開すれば良いので、Bitcoinのプライバシーを向上させる。理想的には、アウトプットは観察者がコインの使用条件を知ることがないキーパスを使ったコインの使用である。キーパスを使ったコインの使用は、単一もしくは複数の署名のウォレットからの通常の支払いか、隠れたマルチパーティコントラクトの共同決済のいずれかである。

スクリプトパスを使ったコインの使用は、スクリプトパスが存在すること、(例えば関係者が合意に達しなかったなどで)キーパスが使われなかったことをリークすることになる。さらに、マークルルートのスクリプトの深さは、ツリーの最小の深さを含む情報をリークする。これはアウトプットを作成したウォレットソフトウェアを示唆し、クラスタリングを助けることになる。したがって、したがってスクリプトを使用する際のプライバシーは、リーフに対する確率分布により最適なツリー構造から逸脱することでプライバシーを改善する。

他の既存のアウトプットタイプと同様に、プライバシー上の理由からTaprootアウトプットはキーを再利用すべきではない。これはアウトプットを使用する際に使われた特定のリーフだけでなく、アウトプットにコミットされたすべてのリーフにも適用される。リーフが再利用されると、別のアウトプットを使用する際にマークルプルーフで同じマークルブランチが再利用されるといったことが起きる可能性がある。新しいキーを使用することで、Taprootで使われているブランチソートのマークルツリー構造によってすでにランダム化されているため、Taprootのアウトプットの構築においてリーフ位置のランダム化に特別な対策を講じる必要が無い。これはリーフの深さを通して情報が漏れるのを避けるものではないため、バランスの取れた(サブ)ツリーにのみ適用される。さらに、すべてのリーフは、他のリーフとは異なるキーのセットを持つ必要がある。この理由は、リーフのエントロピーを増加させ、ブルートフォース検索を使って観察者が未知のスクリプトを学習するのを防ぐためだ。

Test Vector

作成トランザクションと使用トランザクションの有効、無効のペアの例。

sighashモード毎のsighashのプリイメージの例。

まだないっぽい。

展開

まだ展開方法は未定義。

後方互換

ソフトフォークとして、古いソフトウェアは変更なく動作し続ける。ただし、アップグレードしていないノードではSegwitのバージョン1のwitness programを誰でも使用可能なスクリプトとみなす。新しいprogramを完全に検証するためにアップグレードすることを推奨する。

アップグレードされていないウォレットは、Segwitのバージョン0のprogramおよび従来のpay-to-pubkey-hashなどを使って、アップグレードしたウォレットおよびアップグレードしていないウォレットからBitcoinの受信、送金ができる。実装によっては、BIP 173のBech32アドレスへの送金をサポートしている場合、アップグレードされていないウォレットがSegwit version 1のprogramに送金できる場合がある。

論拠


  1. セキュリティの仮定を追加しないというのはどういう意味か?署名が偽造不可能であることは、盗難を防ぐための必須要件だ。少なくともスクリプトの実行をデジタル署名スキーム自体として扱う場合、離散対数問題の困難性を仮定とするランダムオラクルモデルで偽造不可能性が証明できる。現在のスクリプトシステムにおけるECDSAの偽造不可能性の証明には、さらにその上に非標準の仮定が必要だ。ウォレットソフトウェアで使用されるポリシーとプロトコルに依存するため、スクリプトのセキュリティが意味するものを正確にモデル化することは一般的に難しいことに注意してほしい。

  2. 公開鍵がアウトプットに直接含まれるのはなぜか? 初期の典型的な構造ではスクリプトや公開鍵のハッシュをアウトプットに格納するが、公開鍵が常に含まれている場合これはかなり無駄になる。バッチ検証が可能であることを保証するために、公開鍵は全ての検証者に知られている必要があり、したがって出力としてそのハッシュを明らかにすることだけが、witnessに追加の32バイトを追加することを意味する。さらに、アウトプットに対して128ビットの衝突の安全性を維持するためには、とくかく256ビットのハッシュが必要になる。これはサイズが(送信者にとってはコストが)、直接公開鍵を明らかにするのに匹敵する。公開鍵ハッシュの使用は、ECDLPの破壊や量子コンピューターへの保護になるとよく言われるが、その保護は非常に弱い。トランザクションは承認される間保護されず、通貨の供給の大部分はそのような保護の対象にならない。そのようなシステムへの実際の抵抗は、異なる暗号仮定に依存することで実現できる。しかしこの提案は、セキュリティモデルを変更しない改善にフォーカスしている。

  3. なぜP2SHによるラップをサポートしないのか? P2SHでラップしたアウトプットの使用は、160ビットのハッシュが使用されるため80ビットの衝突の安全性のみを提供する。この数値は低いとみなされアウトプットに複数のパーティのデータが含まれる場合、常にセキュリティリスクになる。

  4. annexの先頭バイトが0x50なのはなぜか? 有効なP2WPKHもしくはP2WSHのアウトプットと混同しないように0x50が選択されている。Control Blockの先頭バイトの最下位ビットが公開鍵のYのパリティを示すのに使われるため、各リーフバージョンには偶数バイト値とP2WPKHおよびP2WSHの使用時にまだ使われていない奇数バイト値を必要とする。annexを示すには、0x50のようにペアでない利用可能なバイトのみが必要だ。この選択は将来のスクリプトバージョンのために利用可能なオプションを最大にする。

  5. annexの目的は何か? annexは、使用されるアウトプットについて知らなくても認識できるような方法で、計算的に高価な新しいopcodeの検証コストを示すなど、将来の拡張のために予約されたスペースだ。このフィールドの意味が他のソフトフォークによって定義されるまでは、ユーザーはトランザクションannexを含めるべきではなく、或いは永久的な資金の喪失に繋がるかもしれない。

  6. マークルパスの長さが128までに制限されている理由は? ハフマンアルゴリズムを使用して、リーフのスクリプトの確立に基づき最適な空間効率のマークルツリーを構築できるため。このアルゴリズムはlog2(1/probability)にほぼ等しい長さのブランチを構築するが、128を超えるブランチを作成するには、1/2^{128}未満の実行チャンスを持つスクリプトが必要になり、このような低い可能性はおそらく完全に削除できる。

  7. リーフバージョンにはどのような制約がある? まずc[0] & 0xfeは常に偶数であるため、リーフバージョンを奇数にすることはできず、また0x50にすることもできない(0x50にした場合annexとの曖昧さが生じるため)。さらに、使用されるアウトプットにアクセスすることなくスクリプトの使用を特定できることに依存する静的分析のいくつかの形式をサポートするために、有効なP2WPKH公開鍵や有効なP2WSHスクリプトのいずれかの有効な先頭バイトと競合するリーフバージョンの使用を避けることを推奨する(つまり、vとv|1の両方とも未定義、無効、無効なopcodeもしくは最初のopcodeとして無効なものでなければならない)。このルールに準拠する値は、0xc0から0xfeまでの偶数値、0x66, 0x7e, 0x80, 0x84, 0x96, 0x98, 0xba, 0xbc 0xbeだ。またこの制約はリーフバージョンを異なるwitness version間で共有する必要があることを意味するこtに注意すること。witness versionを知るには使用されるアウトプットにアクセスする必要がある。

  8. マークルツリー内でハッシュされる前に子要素がソートされるのはなぜ? そうすることでマークルブランチのハッシュを明らかにする際に一緒に左右の方向を明らかにする必要がなくなるため。これが可能なのは、ツリー内の特定のスクリプトの位置を実際には気にしておらず、それらが実際にコミットされているということだけを気にしているためだ。

  9. 内部のマークルノードにとってもっと効率的なハッシュ構造を使わないのはなぜ? 選択された構造は、SHA256圧縮関数を2回呼び出す必要があり、2回のうち1回は理論的に避けることができる(BIP-98参照)。しかし、実装の単純さと分析可能性のため、標準の暗号プリミティブを使って実装できる構造に固執するのが好ましいように思われる。必要なら、64バイトの入力用に特化することで2つ目の圧縮関数の大部分を最適化できる。

  10. 使用するスクリプトパスに適用できるスクリプトルールは何? BIP342はリーフバージョンが0xc0の場合に適用される有効性ルールを指定するが、将来の提案では他のリーフバージョンに対してルールを導入できる。

  11. なぜ未知のhash_typeを拒否するのか? そうすることで、十分なキャッシングを持つ実装が実行しなければならない署名ハッシュのワーストケースを推論するのが簡単になる。

  12. どの拡張機能がext_flagの仕組みを利用するのか? BIP342は同じ共通の署名メッセージアルゴリズムを再利用するが、BIP342固有のデータを最後に追加する。これをext_flag = 1を使って明示する。

  13. SigMsg()の出力長は? SigMsg()の長さは次の式を使って計算できる。174 - is_anyonecanpay * 49 - is_none * 32 + has_annex * 32

  14. 署名メッセージのシリアライゼーションが変更されたのはなぜ? 署名メッセージとメッセージ自体に送られるハッシュががdouble SHA256ではなく単一のSHA256で計算されるようになった。SHA256を二回しても安全性の向上は期待できない。double SHA256は伸長攻撃への保護になるのみで、署名メッセージには秘密のデータは無いので意味がない。したがって、SHA256を2回するのはリソースの無駄である。メッセージの計算は、最初にトランザクションレベルのデータ、次にインプットのデータ、アウトプットのデータと論理的な順に行われる。これによりSHA256のmidstateを使って、異なるインプットに渡るメッセージのトランザクション部分を効率的にキャッシュできる。さらにメッセージの計算で、ハッシュの前に一部にゼロをセットするBIP 143ダイジェストとは対照的にサブハッシュをスキップできる。そのようにしても、可変長データの前に(hash_typespend_typeに暗黙的に含まれる)データの長さにコミットすることで衝突は不可能になる。

  15. 署名メッセージがscriptPubKeyにコミットするのはなぜ? これにより、実際に実行されたスクリプトが正しい場合でも(BIP 143のscriptCode)、使用されるアウトプットのタイプについてオフライン署名デバイスに嘘を付くのを防ぐ。つまり、ハードウェアウォレットに対して、どの(未使用の)実行パスが存在したかをコンパクトに証明することが可能だ。さらに使用済みの全てのscriptPubkeyにコミットすることで、オフライン署名デバイスが自身のウォレットに属するサブセットを決めるのに役立つ。これは自動化されたCoinJoinにとっても役立つ。

  16. 署名メッセージがすべてのインプットの量にコミットするのはなぜ? これにより取引の手数料についてオフラインの署名デバイスに嘘を付く可能性がなくなる。

  17. SIGHASH_SINGLEまたはSIGHASH_NONEがセットされている場合、署名メッセージがすべてのインプットのnSequenceにコミットするのはなぜ? これらをセットすると、すでにすべてのトランザクションインプットのprevoutにメッセージがコミットするため、nSequenceを別の値にすると役に立たなくなる。さらにこの変更により、nSequenceSIGHASH_SINGLESIGHASH_NONEトランザクションアウトプットに対してのみ署名メッセージの変更を許可し、インプットに関しては署名メッセージを変更できないという観点と一致する。

  18.  {hash_{TapSighash}}への入力の先頭に0x00が付いているのはなぜ? このプレフィックスはsighash epochと呼ばれ、ハッシュの実行方法に外来性の変更を加える将来の署名アルゴリズム {hash_{TapSighash}}タグ付きハッシュを再利用できる(インクリメンタル拡張に使われているext_flagとは対照的)。別の方法として異なるタグを使用することもできるが、サポートするタグの数が増えると望ましくない場合がある。

  19. 65バイトの署名でhash_typeを0x00できないのはなぜ? これを許可すると64バイトの署名を65バイトに変更することができ、作者が意図したものとは異なるwtxidと手数料が発生する。

  20. 2つの署名の長さを許可するのはなぜ? 最も一般的なタイプのhash_typeを暗黙的にすることで、多くの場合1バイト節約できる。

  21. スクリプトパスが無い場合でも、アウトプットキーが常にtaprootコミットメントを持つのはなぜ? taprootが鍵の集約である場合、悪意ある参加者が他の参加者に気付かれずにスクリプトパスを追加する可能性がある。これによりマルチパーティポリシーを迂回してコインを盗むことができる。MuSigの鍵集約には、内部キーが既にランダム化されているため、この問題はない。攻撃は次のように機能する。アリスとマロリーはスクリプトパスを使用せずに鍵をtaprootアウトプットキーに集約すると仮定する。鍵のキャンセルと関連する攻撃を防ぐのにMuSigの代わりにMSDL-popを使用する。MSDL-popプロトコルでは、全ての関係者が、対応する秘密鍵の所有の証明を提供する必要があり、集約鍵は各鍵の合計に過ぎない。マロリーがアリスの鍵Aを受け取った後、 {M = M_0 + int(t)G}を作成する。ここで {M_0}はマロリーの元々の鍵で、tは内部キー {P = A + M_0}スクリプトパスで、スクリプトはマロリーの鍵のみを含む。マロリーはMの所有の証明をアリスに送信し、両者はアウトプットキーQ = A + M = P + int(t)Gを計算する。アリスはスクリプトパスを認識できないが、マロリーはアウトプットキーQを持つコインを一方的に使用できる。

LNDに実装されたStatic Channel Backup

先日リリースされたLND 0.6-beta↓

Release lnd v0.6-beta · lightningnetwork/lnd · GitHub

で新しくStatic Channel Backup機能が導入された。

通常のオンチェーンの場合であれば、全てのトランザクションはチェーン上に記録されているためバックアップは簡単で、BIP-32ベースのマスターシードさえ管理できていれば、自身の資金を失うことはない。

techmedia-think.hatenablog.com

ただ、Lightning Networkのようなペイメントチャネルを利用したオフチェーン決済の場合、これが難しくなる。オフチェーンの決済情報は二者間でしか管理していない。

オフチェーン決済は、二者間のマルチシグにロックされた資金をインプットとし、決済の度にアウトプットの両者の残高を更新していく。つまり、決済が行われる度に、このチャネルの状態が変化していく。古い状態がブロードキャストされるのを防ぐため、両者は古い状態のトランザクションを無効にするためのシークレットを交換する。もし古いチャネル状態のトランザクションがブロードキャストされると、その取引相手は交換したシークレットを使ってチャネルの資金を全て総取りすることができる。このペナルティが不正の防止策として機能する。

このため、両者は各チャネルの状態で使用している鍵や相手の署名、旧状態を無効化するためのシークレットを管理しなければならない。これらのデータを失うと、自分の資金が取り戻せなくなったり、バックアップが古かった場合、不正をする意図はなくても最新の状態と勘違いをして古いチャネル状態のトランザクションをブロードキャストしてしまうかもしれない。このようにチャネルのバックアップには課題がある。

Static Channel Backupの仕組み

Static Channel Backup(SCB)の仕組みはシンプルで、最新のチャネル状態を維持するための仕組みではなく、チャネルのオープン時にバックアップを作成しておき実際のリカバリーはData Loss Protection (DLP)プロトコルを使用する。

いくらバックアップをしていたとしても、データを失ったらそのバックアップがチャネルの最新状態を保管したものであるかの保証はなく、古いコミットメントをブロードキャストしてしまうとチャネルの資金を失ってしまうので、そういうリスクを冒さないようDLPプロトコルを使って資金をリカバリーする。DLPプロトコルは、チャネルを接続していたリモートピアに再接続し、リモートピアにチャネルを強制的にクローズするよう通知する仕組み。そうすることで、自分が誤って古い状態のトランザクションをブロードキャストするのを防ぐ。

このため、SCBを使ったバックアップは特定のチャネルについて1回のみ取得すればよく、チャネルが閉じられるまで有効である。完全にデータをロスした場合、このバックアップが最終的なリカバリー方法となる。資金を完全に回収するには、チャネルを閉じる必要があることに注意すること。

バックアップ/リカバリー方法

SCB機能では、チャネルをバックアップおよびリカバリーするための複数の安全な方法を公開している。

ファイルを使ったバックアップ

最も簡単なバックアップ+リカバリーの方法。

lndは現在、その他のすべてのファイルを格納している場所と同じ場所(.lnd/data/chain/bitcoin/mainnet/channel.backup)でchannels.backupファイルを管理している。ユーザーはいつでもこのファイルを安全にコピーおよびバックアップできる。

チャネルがオープンまたはクローズする度に、lndはこのファイルを最新のチャネル状態で更新するので、ユーザーはファイルへの変更を検知し、それらを自分にバックアップ場所にアップロードするスクリプトを作っておくだけで良い。ファイルの変更検知については、fsnotifyなどを利用すればいい。

また、ファイルはユーザーのシードから導出した鍵を使ってAEAD方式で暗号化されているため、クラウドストレージやSDカードなどにそのまま安全に保存できる。

リカバリーする際は、restorechanbackupコマンドを使う。

$ lncli restorechanbackup --multi_file /path/to/copied/channel.backup

上記コマンドにより、リモートピアに強制的にチャネルをクローズするよう通知がいく。リモートピアがチャネルをクローズすると、資金がオンチェーンウォレットに戻ってくる。

gRPCを使ったバックアップ

2つめの仕組みは、新しく追加されたSubscribeChanBackups ストリーミングgRPCを使う、より上級者向けの方法。このgRPCを使うと、ベースとなるSCBの状態が変わる度に、新しい通知を受け取ることができる。上記のようなフィルシステムの通知よりも、より複雑なバックアップの仕組みを構築することができる。

cliやRPCを使ってバックアップを要求

最後の方法は、単一のチャネルまたは全てのチャネルのバックアップをcliやRPCで要求する方法。以下のようにcliで要求できる。

$ lncli --network=simnet exportchanbackup --chan_point=29be6d259dc71ebdf0a3a0e83b240eda78f9023d8aeaae13c89250c7e59467d5:0
{
    "chan_point": "29be6d259dc71ebdf0a3a0e83b240eda78f9023d8aeaae13c89250c7e59467d5:0",
    "chan_backup": "02e7b423c8cf11038354732e9696caff9d5ac9720440f70a50ca2b9fcef5d873c8e64d53bdadfe208a86c96c7f31dc4eb370a02631bb02dce6611c435753a0c1f86c9f5b99006457f0dc7ee4a1c19e0d31a1036941d65717a50136c877d66ec80bb8f3e67cee8d9a5cb3f4081c3817cd830a8d0cf851c1f1e03fee35d790e42d98df5b24e07e6d9d9a46a16352e9b44ad412571c903a532017a5bc1ffe1369c123e1e17e1e4d52cc32329aa205d73d57f846389a6e446f612eeb2dcc346e4590f59a4c533f216ee44f09c1d2298b7d6c"
}

$ lncli --network=simnet exportchanbackup --all
{
    "chan_points": [
        "29be6d259dc71ebdf0a3a0e83b240eda78f9023d8aeaae13c89250c7e59467d5:0"
    ],
    "multi_chan_backup": "fd73e992e5133aa085c8e45548e0189c411c8cfe42e902b0ee2dec528a18fb472c3375447868ffced0d4812125e4361d667b7e6a18b2357643e09bbe7e9110c6b28d74f4f55e7c29e92419b52509e5c367cf2d977b670a2ff7560f5fe24021d246abe30542e6c6e3aa52f903453c3a2389af918249dbdb5f1199aaecf4931c0366592165b10bdd58eaf706d6df02a39d9323a0c65260ffcc84776f2705e4942d89e4dbefa11c693027002c35582d56e295dcf74d27e90873699657337696b32c05c8014911a7ec8eb03bdbe526fe658be8abdf50ab12c4fec9ddeefc489cf817721c8e541d28fbe71e32137b5ea066a9f4e19814deedeb360def90eff2965570aab5fedd0ebfcd783ce3289360953680ac084b2e988c9cbd0912da400861467d7bb5ad4b42a95c2d541653e805cbfc84da401baf096fba43300358421ae1b43fd25f3289c8c73489977592f75bc9f73781f41718a752ab325b70c8eb2011c5d979f6efc7a76e16492566e43d94dbd42698eb06ff8ad4fd3f2baabafded"
}

$ lncli --network=simnet exportchanbackup --all --output_file=channels.backup

リカバリープロセス

リカバリープロセスが開始されるとlndは以下のプロセスを実行する。

  1. リカバリーするチャネルのセットが与えられると、データベースにchannel shellを挿入する。これにはDLPプロトコルを開始するのに必要な情報だけが含まれる。結果、これらのチャネルはデータベース内で「recovered」とマークされ、他のプロセスがそのチャネルを使用するのを防ぐ。
  2. channel shellが「recovered」になると、chanbackupパッケージは、ピアに到達できた以前の全てのアドレスを含むLinkNodeを挿入しようとする。その過程でそのチャネルのエッジ(送信方向にのみ)もデータベースに挿入される。
  3. 続いてlndが起動し、いつもどおりチャネルを開いている全てのピアとの接続を確立しようとする。lndがすでに実行されている場合は、新しい接続の試行が開始される。
  4. ピアと繋がったら、次にDLPプロトコルを開始する。リモートピアはデータが失われたことを知り、すぐに強制的にチャネルを閉じる。チャネルを閉じる前に、こちら側が資金を回収するのに必要な鍵を導出するために必要な、最新のコミットメントポイントを含むチャネル再確立ハンドシェイクメッセージを送り返す。
  5. リモートピアがブロードキャストしたコミットメントトランザクションがチェーン上で確認されたら、SCBの情報から資金を回収するのに必要な鍵を再導出し、資金を回収する。

バックアップ/リカバリ−される資金

1点、注意が必要なのは、SCBにより回収可能な資金はベースコミットメントのアウトプットにある資金のみであるということ。つまりHTLCの資金は含まれない。このバックアップファイルはチャネル作成時に作られるが、将来作られるであろうHTLCの情報は当然含まれないので、HTLCの情報は欠落しており、未処理のHTLCがあれば、その資金は回収できない。

Atomic Swapを拡張してオプション、証拠金、先物取引などを可能にするAtomic Swaptions

Atomic Swapプロトコルは第三者を信頼することなく、異なるブロックチェーン間で暗号通貨の交換を可能にするプロトコル。単なる暗号通貨の交換だけでなく、オプションや先物のようなデリバティブ資産の保有を希望する参加者向けにAtomic Swapプロトコルを拡張したものがAtomic Swaption↓

https://arxiv.org/pdf/1807.08644.pdf

昨年のScaling Bitcoin 2018のOlaoluwa Osuntokunのセッション「Multi-party Channels in the UTXO Model: Challenges and Opportunities」の一部でも紹介されてる↓

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

以下、Atomic Swaptionsのホワイトペーパーの意訳↓

トラストレスな取引所

Bitcoinなどの暗号通貨の安全性は暗号プロトコルに依存している。しかし、もともとそう設計されているように、異なるブロックチェーンが互いに安全に通信するための仕組みはなかった。したがって、異なる暗号通貨の取引はチェーン外で行われるか、関連するブロックチェーンの範囲外で行われてきた。取引を希望する両者は、通常は集中型の暗号通貨取引所などの信頼できる第三者に資金を預けなければならない。

集中型の取引所を信頼する必要性は、長い間暗号通貨コミュニティ内ではこまった事実であった。取引所の参加者は資金が凍結されたり、差し押さえられたり、盗まれたりすることを恐れており、それが根拠あることであることは歴史的に示されている。逆に評判の良い取引所は特定の暗号通貨の潜在的な新規ユーザー/投資家に対して独占的な力を持っているため、使用料を取り、暗号通貨の開発全般に過度の影響を与えることができる。例えば、自身の取引所でどのアルトコインをサポートするかを選ぶことができ、おそらく上場手数料を要求するだろう。新しいアルトコインの作成者はその要求を受け入れるか、評判の悪い取引所と協力してチャンスを得る必要がある。これらの摩擦は新規参加者が最初にコインを入手するのを困難にし、暗号通貨の採用に対する障害となっている。

暗号通貨のボラティリティは、暗号通貨の採用、ギャンブラーおよび投機家に対して別の障害を提供する。潜在的な利用者は金融オプションや先物市場を利用してボラティリティリスクをヘッジすることができれば、特定の暗号通貨を採用する傾向が強くなるかもしれない。例えば暗号通貨のマイナーは暗号通貨建ての収益を持っているが、一般的には既存の通貨建ての費用を持つ。しかし、デリバティブのトレードを集中型の取引所に依存すると、スポット取引業者が直面する問題と同じ問題にさらされることになる。実際、この問題は拡大するだろう。デリバティブの長期的な性質は、ユーザーが契約期間中、資産を取引所に保管しなければならないことを意味する。さらに取引所が提供するどんなレバレッジも直接的および間接的なカウンターパーティリスクを招くことになる。極端な市場変動の場合、取引所が破綻するリスクもある。

Atomic Swapにより、ユーザーは取引時に集中型の取引所に固有のリスクや不利益を回避することができる。Atomic Swapでは暗号技術を使ったエスクローの一種であるHashed Time Lock Contracts (HTLCs)を使用し、ブロックチェーン自体が信頼できる第三者として機能するようになる。別の見方をすると、Atomic Swapは暗号のシークレットを利用して、特定のイベントが別のブロックチェーンで発生したことをユーザーが1つのブロックチェーン上で証明できるようにする。

このペーパーではAtomic Swapの機能をさらに拡張してオプション契約を作成できるようにすることを示す。これをAtmic Swaptionと呼ぶ。特に当事者は元本総額の代わりに小額の証拠金のみをデポジットすることができる。これは、先物契約を可能にし、ユーザーが1つの暗号通貨でレバレッジポジションまたはショートポジションを他の通貨に対してとることができるようにする。

Atomic Swap

アリスはAコイン、ボブはBコインをそれぞれ持っているものとする。アリスとボブそれぞれが持つAコインとBコインを交換したい場合、通常はHTLCを使って各チェーンで取引を行う。手順は簡単で、

  1. アリスはシークレットxをランダムに選択し、そのハッシュ値H(x)をボブに伝える。
  2. アリスはH(x)のプリイメージxが分かればAコインをボブに支払い、有効期間T + Aを過ぎたらアリスに払い戻されるスクリプト宛にAコインを送る。
  3. ボブも同様にH(x)のプリイメージxが分かればBコインをアリスに送り、有効期間Tを過ぎたらボブに払い戻されるスクリプト宛にBコインを送る。
  4. アリスは自分だけが知っているxを使ってBコインを入手するトランザクションをブロードキャストする。
  5. ボブはアリスがブロードキャストしたトランザクションを見て、xの値を知り、それを使ってAコインを入手するトランザクションをブロードキャストする。

スワップを完了できる。

Atomic Swaption

上記のAtomic Swapでは3でボブがBコインをデポジットした後に、アリスが意図的に約束を破ることができるこのに注意する必要がある。例えばボブと合意した後に、Aコインの価格がBコインに対して上昇した場合、アリスは交換をやめるかもしれない。アリスはこの決定をタイムロックの期間Tまでに決めればいい。

※ ちなみにあくまでAtomic Swapと見た場合に、公平な交換をしようと担保型のAtomic Swapなんかの提案もある↓

techmedia-think.hatenablog.com

ただ今回はそれとは別に、それをオプション取引に利用する話。

上記のシチュエーションを言い換えるとAtomic Swapは、アリスがアリスにとって価値のある固定価格でAコインをBコインと交換することができる経済的選択肢と見なすことができる。有効期間Tが小さければ、市場価格が短期間で大きく変動する可能性は低いため、「オプション値」は一般的に小さくなる。

Tが大きければ、アリスは大きなオプション値を得る。見返りにアリスはこのスワップション契約を締結するためにボブにプレミアムを支払うことができる。問題はスワップションをプレミアムと安全に交換する方法。これはスワップションを別のAtomic Swapにネストすることで実現する。アリスはAコインでプレミアムを支払うが、Atomic Swapは任意の数のブロックチェーンを含むことができるので、任意の暗号通貨を使って支払うことも可能。

このフレームワークはいくつかのバリエーションを可能にする。例えばコンパウンド・オプションは単にオプションを原資産とするオプション取引で、HTCLsをさらにネストすることで実装できる。同様に、Collar option(カラーオプション)は複数のスワップションをトレードする代わりに、ネストすることで効率的に実装できる。discreet log contractを結ぶオプションを作成することも可能だ。ここでは、スワップションのトレーダーにとって役に立つ可能性の高い、早期キャンセルとマージンについて分析する。

早期キャンセル

アリスがスワップションを没収され、Aコインを早期に取り戻したいと思った場合、アリスとボブがスワップションをキャンセルできるよう、払い戻しトランザクションを作成することは可能だが、ボブが協力的ではなく有効期限が切れるまでアリスを待たせるケースが考えられる。代わりにアリスが別のシークレットを使ってそれを取り消すことができるようにスワップションを設定できる。アリスがスワップションの行使とキャンセルを同時に行うことができないように、資金はRevocable Sequence Maturity Contract(RSMC)に送られる。アリスの行使に対応するRSMCは、もしアリスがキャンセルトランザクションを公開したらボブがBコインを入手できるようにする。したがって、アリスがチートをしようとするとボブが罰則として全ての資金を受け取れる。

証拠金

アリスとボブは、彼らの資金の全てをコントラクトに預ける代わりに、全資金(元本)の一部(証拠金)を預けることに同意することができる。これは証拠金契約の使用で実現できる。ボブの証拠金契約はボブがスワップションコントラクトに証拠金を送ることを可能にするが、これを可能にするトランザクションはアウトプットが全元本と同額であることを求めるため、ボブは残りの資金も同様に預けるよう要求される。Bitcoinでは、SIGHASH ANYONECANPAYを使ってよく実現される。スクリプティングのルールが異なるブロックチェーンでは、ボブが最初に残りの元本をプレースホルダコントラクトに送信する必要がある。

ボブが証拠金の有効期限までにスワップションコントラクトに資金をデポジットしない場合、アリスは証拠金を受け取ることができ、シークレットを明らかにする必要もなくなる。つまり行使しないことでAコインを取り戻すことができる。アリスの証拠金契約も同様に機能するが、ボブの証拠金契約より前に有効期間切れになるはずだ。アリスが元本のデポジットに失敗した場合は、ボブはアリスの証拠金を受け取り、ボブ自身のも失う。

ブロックチェーン間の通信プロトコルとしてのHTLCの制限のためだけに、アリスの証拠金のデポジットが必要であることに注意すること。つまりボブはアリスがAコインのスワップションコントラクトに資金をデポジットしなかったことをBコインのブロックチェーン上で証明することはできない(そして、アリスは自分がAコインをデポジットしたことをBコインのブロックチェーン上で証明することはできない)。この証明が可能なら、Bコインのブロックチェーン上で行われるアリスの行使トランザクションは、アリスがAコインをAコインのスワップションコントラクトにデポジットするまで行使できないよう制限することができる。しかしAコインとBコインが同じブロックチェーン上にある場合(ERC20トークンなど)、アリスが行使を決定するまでアリスが資金をデポジットする必要が無いようコントラクトを設計することができる。

条件を交渉する際、アリスとボブはデフォルト戦略の可能性を考慮に入れるべきで、それはスワップションのペイオフ機能を変更する。アリスの証拠金と元本の比率をボブのものと同じにすることは、アリスがデフォルトに対するインセンティブを持たないことを保証するのに十分である。しかし、ボブのマージンが元本と等しくない限り、ボブのデフォルトに対するインセンティブは存在する。それを踏まえて、アリスとボブの証拠金比率を同じにして、証拠金のスワップションを設計するための2つのアプローチについて概説する。固定証拠金アプローチは本質的に「set and forget」だ。スワップションが設定されると、アリスとボブはスワップションが有効期間切れになるまで何もすることがない。変動証拠金アプローチは、コールまたはプットオプションの従来のペイオフ機能を厳密に再現しようとするが、アリスとボブの間で頻繁なやりとりが必要になる。

固定証拠金スワップション

ここでは、証拠金の有効期限Mがスワップションの有効期限(遅くともE-2)の前に設定されているので、ボブは有効期限がデフォルトになるかどうか決定する。 {m_B} {p_B}をそれぞれ証拠金と元本の量とし、 {p_A}も同様にする。スワップションの本質的な値は、証拠金のデポジット分を含まない {max(0, min(m_B, p_B −p_A))}で与えられる。これによりFigure 1に示すよじれのあるペイオフ機能が得られる。これは価格設定の目的のために2つの伝統的な選択肢に分解できる。

f:id:techmedia-think:20190407135824p:plain
Figure 1:固定証拠金スワップションのペイオフ機能。両方のグラフは同じスワップションを説明している。Aコインで示されているのはコールオプション、Bコインで示されているのはプットオプション

変動証拠金スワップション

ここでは、アリスとボブは戦略的デフォルトからの期待利益が小さいため、予め決められた閾値を下回るように必要な証拠金量を増減することで、オプションを継続的に「時価評価」することに同意できる。これは新しいスワップションを作成中に古いスワップションをアトミックキャンセルすることで行われる。証拠金の有効期限はおそらく1日から1週間の範囲の中間値Mに設定される。アリスとボブが協力すれば証拠金の有効期限が切れるまでの時間はこの値で継続的にリセットされるが、一方が協力するのを止めると、両者はデフォルトを避けるための時間が来る。Mの選択はトレードオフで、Mの値が小さいほど証拠金も小さくなり、戦略的デフォルトが発生する確率が低くなるが、非協力的なケースにおいて対応する時間は短くなる。

変動証拠金はLightning Networkのようなレイヤー2のオフチェーンスケーリングソリューションで最も効果的に実行される。不正行為の場合を除いて、ユーザーはトランザクションを公開すること無く安全なコントラクトを結ぶことができる。そうでないと、チェーン上のトランザクションを使って、スワップションコントラクトを繰り返し作成、キャンセルするのはとてもコストがかかる可能性がある。

先物

変動証拠金は、コール/プットオプションペイオフを忠実に複製するため、先物取引の作成に使用することができる。プット・コール・パリティのバージョンでは、長期の先物取引はロングコールオプションとショートプットオプション(早期行使を無視して)と同等であると述べている。言い換えると先物取引を作成するため、アリスとボブは同じ権利行使価格と満了日で同時に、アリスは1Aコインを1Bコインと交換し、ボブは1Bコインを1Aコインと交換することが可能な、2つの変動証拠金スワップションをアトミックに開始できる。アリスが両方の契約でAコインを提供しているため、この構成は重要ではない。必要な証拠金の額を最小限に抑えるため、行使価格は先物市場価格に近づけるべきだ(より正確には、満期時の予想スポット価格に)。

Lightning Networkのルーティング

この分析の多くは、1つのブロックチェーンのみを使うため処理が簡単なdiscreet log contract(DLC)のような長期契約にも適用できる。

アリスとボブが中間者を経由してスワップション契約を結ぶことも可能で、これはアリスからボブへのAコインの支払いをHashed Time Lock Contract (HTLC) で連鎖させ、Bコインの支払いをループバックさせるAtomic Swapと同じ方法で実現できる。

スワップションの各ステップはHTLCおよび/またはHTLCに送信するコントラクトで構成されているため、必要に応じてRevocable Sequence Maturity Contractsを追加することで、LNにアトミックスワップを実装するのは簡単だ。中間者の離脱は無益で、他の参加者の損失にはならない。またペイメントチャネル上でいくつかのHTLCを同時に開始できることも明らかになっている。したがって、長期のスワップションでは、スワップションコントラクトに拘束される特定の金額を超えてチャネルの運用が中断されることはない。

ペイメントチャネル内では、ユーザーはスワップションコントラクトに参加するための証拠金をまかなうのに十分な資金を持っていればいい。ただし、チャネルに十分な資金がない場合、元本を支払うためにペイメントチャネルを閉じる必要が出てくる。

三者によるデカップリングとアンワインド

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

三者のキャロルが、アリスとボブの間でAコインとBコイン両方のパスのノードとして登場する場合、キャロルは代わりにボブとの独自のシークレットを使ってスワップションをデカップルできる。この場合、キャロルはアリス、ボブ両方の取引相手として(または同様に行動する仲介者として)機能し、ネットポジションはゼロである。これによりスワップションの実行に必要な時間が短縮される(HTLC支払いのチェーンの各ステップは1タイムステップ後に期限切れになるので)。さらに、キャロルはアリスがそうするのを待たずに、一方的にボブとのロングオプションポジションを閉じることができ、それによりキャロルの資金がより早く解放される。完全に協調的な場合は、オプションのアンワインドプロセスも単純化する。

キャロルがアリスに対してロングポジションを開始するように同一の取引がアリスとキャロルを通過すると仮定する。アリスとキャロルはお互いにゼロポジションであるため、両方のスワップションがキャンセルされ、彼らの資金が不必要にロックされなようプロセスを終了するのが理想的だ。キャロルは同じシークレットを使って、アリスの(キャロルとの)スワップションの1タイムステップ後に有効期限切れになるようなアリスとのスワップションをセットすることがdけいる。その後アリスが自身のシークレットを明らかにすると、循環的なリバランスで元に戻すことができる循環的な資金の流れを引き起こす。

同様に、自己売買をすることで、2人のユーザーがコントラクトをブロックチェーンにいれずにチャネルを閉じたい時などに、既存のポジションを再ルーティングできる。これらの手法を使うことで、LN上で一連のスワップションポジションをオープニンにしたまま維持するコストを最小限に抑えることができる。

経済的考察

アリスとボブは、プロセスに内在するリスクとネットワークトランザクション手数料だけでなく、ロックされた資金の時間的価値について仲介者前任に補償しなければならないtめ、長いパスは高価になりがちである。また証拠金スワップションのデフォルトを回避するために、仲介者は十分な流動性を維持する必要があり、これを補償する必要がある。スワップション、DLCおよびその他の長期契約は全て同じネットワークの資金のキャパシティを巡って競合する。

これは、LNベースのデリバティブ取引は、密集した大規模トレーダー/取引所の少数のセットと大規模なトレーダーに直接接続した多くの小規模なトレーダーで、ほぼ集中化されることを示唆している。

同一のスワップションをアンワインドする機能は、権利行使価格や規模、有効期限などの契約条件の標準化を促進する。

実用的な考慮事項

他の長期契約と同様、ハッシュや署名アルゴリズムなど、ブロックチェーン上で緊急的なルールの変更が発生するというリスクがある。おそらく動かされないコインは燃やされる(使えなくなる)。この場合、アリスとボブにはいくつか選択肢がある。ダイナミクスは特定の契約とルール変更のタイミングに影響される。両者はおそらく、一方が他方に超過分の支払いを強要することで、新しいルールの下で協力して契約を最下位することができる。もしボブが協力しなければ、アリスは移動の期日までに行使するか取り消すかを選択しなければならず、スワップションの付帯価値を失うことになる。しかしアリスはコインが償却されるまで何もせずボブを苦しめることもでき、その後もう一方のブロックチェーンからコインを取得する。

レバレッジ取引は、オプションの価格設定式/ソフトウェアにかなり精通している必要があるため、ユーザーフレンドリーである可能性は低い。さらに、証拠金のユーザーは、証拠金が喪失されると取引相手が実質的に利益を得ることが多いので、相手が協力的であることに依存すべきではない。十分なスワップション市場を考えると、証拠金の有効期限が同時にハッセするような大きなスワップポジションを累積することで、ショートスクイズを誘導する可能性さえある。それらはLNの仲介者の長いパスを使って援助されるかもしれない。

タイムロックは意図した順序で期限切れになるよう、ブロック高ではなく経過時間を使用するように設定する必要がある。しかし、2つのブロックチェーン間で将来のブロックの相対的なタイミングを予測するのは困難である。もちろんそれが可能であれば、多くのユーザーがブロック高を使用するのを好むのは明らかではない。

法定通過にペグした暗号通貨を作成するためのさまざまな試みがある。ボラティリティを最小限に使用としているユーザーは、スワップション取引でペグされた暗号通貨の使用を好む可能性があるが、これにはもちろん基礎となるペグを信頼する必要がある。

結論

新しいブロックチェーンの設計と採用は継続的なプロセスで、その上でのアプリケーションの開発を簡単にするため、ブロックチェーンがどの操作をサポートすべきかは未解決の問題だ。アトミックスワップを実装するのに必要な機能だけで、幅広いブロックチェーン間で派生物を実装できることを実証した。このようにアトミックスワップションでは、既存の暗号通貨でのデリバティブ取引への実用的な道筋を提供し、将来のブロックチェーン設計者が特殊なデリバティブアプリケーションを作成する必要性を排除する。